Storybook
Closegram tiene tres instalaciones independientes de Storybook, una por cada paquete/app publicable, cada una en su propio puerto. Son una herramienta local/de desarrollo para construir y revisar componentes de forma aislada — ninguna está conectada a CI (.github/workflows/ci.yml no tiene ningún paso de Storybook).
| Paquete | Workspace | Puerto | Framework builder | Propósito |
|---|---|---|---|---|
packages/ui | @repo/ui | 6006 | @storybook/react-webpack5 | Primitivas compartidas del sistema de diseño + páginas de tokens de diseño |
apps/frontend-nextjs | frontend-nextjs | 6007 | @storybook/nextjs | Componentes a nivel de app dependientes de providers (auth, chat, monedas, publicaciones…) |
apps/frontend-admin | frontend-admin | 6008 | @storybook/nextjs | Componentes del kit de UI de administración (Table, Modal, CommandPalette…) |
Las tres fijan storybook/@storybook/* en ^10.5.4 y comparten un addon, @storybook/addon-themes. Ninguna declara @storybook/addon-essentials — Storybook 9+ integró controls/actions/viewport/backgrounds/toolbars/measure/outline al núcleo, así que ya no hace falta.
Para las APIs de componentes y props, consulta UI Components. Para las configuraciones de pruebas automatizadas (Vitest/Jest/Playwright/XCTest), consulta Testing.
Comandos de ejecución
Todos desde la raíz del repo usando npm workspaces (-w <package>), o haciendo cd al paquete primero. Nota la inconsistencia en los nombres de script entre packages/ui y las dos apps:
# packages/ui — sistema de diseño (puerto 6006)
npm run dev -w @repo/ui # storybook dev -p 6006
npm run build -w @repo/ui # storybook build
npm run test-storybook -w @repo/ui # runner de render-smoke (necesita un Storybook corriendo/servido)
# apps/frontend-nextjs — app pública (puerto 6007)
npm run storybook -w frontend-nextjs # storybook dev -p 6007
npm run build-storybook -w frontend-nextjs # storybook build
# apps/frontend-admin — panel de administración (puerto 6008)
npm run storybook -w frontend-admin # storybook dev -p 6008
npm run build-storybook -w frontend-admin # storybook build
En packages/ui el comando de desarrollo es dev y el de build es el build sin prefijo; en ambas apps están explícitamente namespaced como storybook / build-storybook. test-storybook existe solo en packages/ui.
packages/ui (puerto 6006) — el sistema de diseño
El hogar de las primitivas de @repo/ui (Button, Avatar, Badge, Tooltip, Spinner, Form, IconButton, DateTimePicker, Skeleton, AnimatedCount, RollingNumber, StoryRingAvatar, VerifiedBadge, el set de íconos) más las páginas de "Foundations" que documentan los tokens de diseño. También funciona como objetivo de render-smoke mediante test-storybook.
.storybook/main.ts:
stories: ['../src/**/*.stories.@(ts|tsx)']framework: { name: '@storybook/react-webpack5', options: {} }addons: ['@storybook/addon-webpack5-compiler-swc', '@storybook/addon-themes']— el addon del compilador SWC es necesario porque el builder webpack5 de Storybook 8+ no incluye ningún compilador por defecto.core: { disableTelemetry: true }- Un hook
swcpersonalizado fuerzajsc.transform.react.runtime = 'automatic'(de lo contrario, el JSX dentro delrender:de una historia lanza"Can't find variable: React"). - Un
webpackFinalpersonalizado agregapostcss-loadera la regla de CSS para que las directivas@tailwinden.storybook/tailwind.cssse compilen a través depostcss.config.js.
.storybook/preview.ts:
- Importa
./tailwind.cssglobalmente. controls.matchersdetecta automáticamente props de color/fecha.backgrounds:light(#ffffff, por defecto) ydark(#0c1014).decorators: [withThemeByClassName({ themes: { light: '', dark: 'dark' }, defaultTheme: 'light' })]— un toggle en la toolbar que agrega/quita la clasedarken<html>para que las variantesdark:de Tailwind se puedan previsualizar por historia.
Organización de historias — títulos bajo tres grupos de nivel superior:
Primitives/*— un archivo por componente (Primitives/Button,Primitives/Avatar,Primitives/Badge, …).Foundations/Colors(src/Foundations/Colors.stories.tsx) — una página de documentación de tokens sincomponent, usando helpers de render localesSwatch/Groupsobre las clases realescg-*de Tailwind (p. ej.bg-cg-blue-action,bg-cg-dark-elevated) para que la paleta se mantenga sincronizada con la configuración.Icons/Gallery(src/icons/Icons.stories.tsx) — la galería del set de íconos.
El runner de render-smoke test-storybook
packages/ui es el único paquete con @storybook/test-runner configurado ("test-storybook": "test-storybook"). Se ejecuta contra un Storybook construido/servido y verifica que cada historia se renderice sin lanzar errores — una prueba de render smoke a través de todas las historias de Primitives/*, Foundations/* e Icons/*. No existe ningún test-runner.ts / override de Jest personalizado, y ninguna historia en todo el repo define una función play:, así que esto es puramente render-smoke, no pruebas de interacción. Primero inicia (o construye y sirve) Storybook, luego:
npm run test-storybook -w @repo/ui
apps/frontend-nextjs (puerto 6007) — la app pública
Previsualiza componentes a nivel de app dependientes de providers (pantallas de auth, chat, notificaciones, modales de monedas/recompensas, tarjetas de publicaciones, navegación) que necesitan contexto simulado de Apollo/Firebase/Next para renderizarse de forma aislada.
.storybook/main.ts:
stories: ['../src/**/*.stories.@(ts|tsx)']framework: { name: '@storybook/nextjs', options: {} }— maneja de forma nativa los internos del App Router,next/image, etc., así que no se necesita configuración manual de webpack CSS/SWC (a diferencia depackages/ui).addons: ['@storybook/addon-themes'],staticDirs: ['../public']- Un
webpackFinalalía@/lib/firebase→.storybook/mocks/firebase.ts(que exportafirebaseApp = null,auth = null, ungoogleProviderde prueba) para que Storybook nunca inicie Firebase real en componentes comoLogin.
.storybook/preview.tsx:
- Importa
../src/app/globals.cssy../src/i18n/config(se autoinicializa i18next, así queuseTranslation()/t('key', 'fallback')renderizan texto real, no claves crudas). parameters.nextjs = { appDirectory: true }— monta el App Router simulado de@storybook/nextjspara queuseRouter/usePathnameno lancen"invariant expected app router to be mounted".- Los mismos
backgrounds(light#ffffff/ dark#0c1014) y el decorator de modo oscurowithThemeByClassNamequepackages/ui.
Organización de historias — todo se anida bajo App/*, con subnamespaces por funcionalidad. Títulos planos como App/Auth, App/Login, App/Navigation, App/CreatePostModal, App/PostCard, App/Notifications; anidados como App/Chat/MessageBubble, App/Chat/ConversationList, App/Coins/CoinBalanceBadge, App/Coins/Modals, App/Rewards/RewardFanModal.
Los componentes que necesitan contexto de app usan un decorator withProviders por archivo que envuelve MockedProvider + AuthProvider + ThemeProvider + ToastProvider (repetido por archivo en lugar de aplicado globalmente, ya que no todos los componentes necesitan la pila completa); esos metas usualmente también establecen parameters: { layout: 'fullscreen' } para previsualizaciones de modal/pantalla.
Aquí no hay test-runner. vitest.config.ts excluye **/*.stories.tsx de la corrida de pruebas unitarias — estas historias son solo visuales.
apps/frontend-admin (puerto 6008) — el panel de administración
Previsualiza componentes del kit de UI exclusivos de administración (Table, Pagination, Dropdown, Modal, CommandPalette, StatCard, EmptyState, Card, Input, Avatar) más su propia página de tokens de diseño.
.storybook/main.ts — el más simple de los tres, sin webpackFinal/mocks:
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(ts|tsx)'],
addons: ['@storybook/addon-themes'],
framework: { name: '@storybook/nextjs', options: {} },
core: { disableTelemetry: true },
};
.storybook/preview.tsx:
- Importa
../src/app/globals.css; aquí no hay importación de i18n. nextjs: { appDirectory: true }por la misma razón del mock del App Router (CommandPaletteusauseRouter/usePathname).- Una paleta de fondos distinta, con la identidad de administración, en lugar de light/dark genéricos:
backgrounds: { default: 'admin', values: [{ name: 'admin', value: '#f6f6f7' }, { name: 'admin-dark', value: '#0a0b0d' }] }. - El mismo decorator de toolbar de modo oscuro
withThemeByClassName.
Organización de historias — todo bajo Admin/*: Admin/Foundations/Colors (página de tokens, mismo patrón Swatch/Group que packages/ui), más Admin/Avatar, Admin/Card, Admin/CommandPalette, Admin/Dropdown, Admin/EmptyState, Admin/Input, Admin/Modal, Admin/Pagination, Admin/StatCard, Admin/Table.
Convenciones compartidas de CSF
Las tres instalaciones usan CSF3, TypeScript:
import type { Meta, StoryObj } from '@storybook/react';
import { Button, type ButtonVariant } from './Button';
const meta: Meta<typeof Button> = {
title: 'Primitives/Button',
component: Button,
args: { children: 'Guardar' },
argTypes: {
variant: { control: 'select', options: ['primary', 'secondary', 'ghost', 'danger', /* … */] },
size: { control: 'inline-radio', options: ['sm', 'md'] },
disabled: { control: 'boolean' },
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = { args: { variant: 'primary' } };
const ALL: ButtonVariant[] = [/* … */];
export const AllVariants: Story = {
render: () => (
<div className="flex flex-wrap items-center gap-3">
{ALL.map((v) => <Button key={v} variant={v}>{v}</Button>)}
</div>
),
};
- La taxonomía de títulos duplica como namespace del sidebar y señala el alcance:
Primitives/*+Foundations/*+Icons/*(el sistema de diseño),App/*(+ subrutas por funcionalidad) para la app de consumo,Admin/*(+Admin/Foundations/*) para la app de administración. - La historia base suele ser un
export const Default: Story = {}vacío o una exportación de estado primario usandometa.args; las historias de variantes solo sobrescriben losargsque difieren. - Las historias de galería/comparación (
AllVariants,Grid) usan una funciónrender:personalizada que itera sobre un enum/array para mostrar cada estado visual lado a lado. - Las páginas de tokens/foundations omiten
componentpor completo (Meta = { title }únicamente) y definen helpers localesSwatch/Groupinline. - El modo oscuro nunca es una preocupación por historia — se maneja globalmente mediante el decorator
withThemeByClassName+ el toggle de la toolbar, ya que todos los componentes usan variantesdark:de Tailwind. - Los componentes de app con muchos providers (nextjs/admin) envuelven
MockedProvider(mocks: []) y providers de contexto de la app en un decoratorwithProviderspor archivo en lugar de uno global. - No hay funciones
play:en ningún lado — Storybook aquí es documentación / revisión visual, más (solo enpackages/ui) una compuerta automatizada de render-smoke mediantetest-storybook.