Saltar al contenido principal

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).

PaqueteWorkspacePuertoFramework builderPropósito
packages/ui@repo/ui6006@storybook/react-webpack5Primitivas compartidas del sistema de diseño + páginas de tokens de diseño
apps/frontend-nextjsfrontend-nextjs6007@storybook/nextjsComponentes a nivel de app dependientes de providers (auth, chat, monedas, publicaciones…)
apps/frontend-adminfrontend-admin6008@storybook/nextjsComponentes 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 swc personalizado fuerza jsc.transform.react.runtime = 'automatic' (de lo contrario, el JSX dentro del render: de una historia lanza "Can't find variable: React").
  • Un webpackFinal personalizado agrega postcss-loader a la regla de CSS para que las directivas @tailwind en .storybook/tailwind.css se compilen a través de postcss.config.js.

.storybook/preview.ts:

  • Importa ./tailwind.css globalmente.
  • controls.matchers detecta automáticamente props de color/fecha.
  • backgrounds: light (#ffffff, por defecto) y dark (#0c1014).
  • decorators: [withThemeByClassName({ themes: { light: '', dark: 'dark' }, defaultTheme: 'light' })] — un toggle en la toolbar que agrega/quita la clase dark en <html> para que las variantes dark: 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 sin component, usando helpers de render locales Swatch/Group sobre las clases reales cg-* 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 de packages/ui).
  • addons: ['@storybook/addon-themes'], staticDirs: ['../public']
  • Un webpackFinal alía @/lib/firebase.storybook/mocks/firebase.ts (que exporta firebaseApp = null, auth = null, un googleProvider de prueba) para que Storybook nunca inicie Firebase real en componentes como Login.

.storybook/preview.tsx:

  • Importa ../src/app/globals.css y ../src/i18n/config (se autoinicializa i18next, así que useTranslation() / t('key', 'fallback') renderizan texto real, no claves crudas).
  • parameters.nextjs = { appDirectory: true } — monta el App Router simulado de @storybook/nextjs para que useRouter/usePathname no lancen "invariant expected app router to be mounted".
  • Los mismos backgrounds (light #ffffff / dark #0c1014) y el decorator de modo oscuro withThemeByClassName que packages/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 (CommandPalette usa useRouter/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 usando meta.args; las historias de variantes solo sobrescriben los args que difieren.
  • Las historias de galería/comparación (AllVariants, Grid) usan una función render: personalizada que itera sobre un enum/array para mostrar cada estado visual lado a lado.
  • Las páginas de tokens/foundations omiten component por completo (Meta = { title } únicamente) y definen helpers locales Swatch/Group inline.
  • 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 variantes dark: de Tailwind.
  • Los componentes de app con muchos providers (nextjs/admin) envuelven MockedProvider (mocks: []) y providers de contexto de la app en un decorator withProviders por 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 en packages/ui) una compuerta automatizada de render-smoke mediante test-storybook.