# Plan: Creación del Componente Button para BL Consultores DS con Tailwind CSS ## Contexto Se necesita crear el componente de botones para el Design System de BL Consultores. El proyecto es una librería React de componentes (`bl-consultores-ds`) basada en `create-react-component-library`. Actualmente el starter kit incluye un componente `Card` genérico que **debe eliminarse** — es solo el template por defecto del starter kit y no forma parte del diseño system real de BL Consultores. El diseño de los botones proviene de Penpot y está documentado en Docmost con **11 variantes exactas** (colores, tamaños, estados hover). Se usará **Tailwind CSS** para los estilos. --- ## Paso 0: Instalar y configurar Tailwind CSS ### 0.1 Instalar dependencias ```bash npm install -D tailwindcss postcss autoprefixer ``` ### 0.2 Crear `tailwind.config.js` Configurar con los tokens exactos del diseño de Penpot: ```js /** @type {import('tailwindcss').Config} */ module.exports = { content: [ './src/**/*.{js,jsx,ts,tsx}', ], theme: { extend: { colors: { gold: '#EABF2D', amber: '#D4880F', black: '#1A1A2E', danger: '#DE3626', muted: '#9AA0A6', gray: '#6B6B7B', inputBorder: '#DADCE0', }, borderRadius: { pill: '20px', circle: '28px', }, fontSize: { btn: ['11px', { lineHeight: '1', fontWeight: '700' }], btnPrimary: ['12px', { lineHeight: '1', fontWeight: '700' }], btnNormal: ['12px', { lineHeight: '1', fontWeight: '400' }], }, height: { btn: '40px', btnLg: '60px', btnCircle: '56px', btnIcon: '36px', }, width: { btnPrimary: '170px', btnOutlineGold: '230px', btnOutline: '110px', btnOutlineLight: '140px', btnGhost: '110px', btnFileUpload: '200px', btnCircle: '56px', btnIcon: '36px', }, fontFamily: { sans: ['sourcesanspro', 'system-ui', 'sans-serif'], }, }, }, plugins: [], } ``` ### 0.3 Crear `postcss.config.js` ```js module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ``` ### 0.4 Ajustar `webpack.config.ts` - Agregar `postcss-loader` como dependencia - Agregar regla postcss en las rules de webpack (entre css-loader y sass-loader, o antes de them) - Para componentes sin SCSS, el CSS se genera vía Tailwind classes directamente --- ## Paso 1: Eliminar el Card del starter kit | Archivo | Acción | |---|---| | `src/components/Card/` | Eliminar directorio completo | | `src/components/__tests__/Card.test.tsx` | Eliminar | | `src/components/__tests__/Card.test.cy.tsx` | Eliminar | | `src/components/index.tsx` | Remover `export * from './Card'` | --- ## Paso 2: Crear el componente Button ### Estructura de archivos ``` src/components/Button/ ├── index.tsx # Componente React + tipos └── Button.stories.tsx # Storybook stories ``` Sin archivo SCSS separado — los estilos se manejan con clases Tailwind. ### TypeScript — Tipos ```tsx type ButtonVariant = | 'primary' | 'outline-gold' | 'outline-dark' | 'outline-light' | 'ghost' | 'circular' | 'icon-edit' | 'icon-delete' | 'file-upload'; interface ButtonProps { /** Variant style of the button */ variant?: ButtonVariant; /** Button text content */ children?: React.ReactNode; /** Click handler */ onClick?: (e: React.MouseEvent) => void; /** HTML button type */ type?: 'button' | 'submit' | 'reset'; /** Disabled state */ disabled?: boolean; /** For file-upload variant: accept attribute */ accept?: string; /** Optional className for additional styling */ className?: string; } ``` ### JSX — Estructura El componente usa un ` ); }; ``` ### Variantes — Clases Tailwind exactas desde Penpot Cada variante tiene clases Tailwind derivadas de los valores exactos de Penpot: | Variant | Clases Tailwind | |---|---| | **primary** | `w-btnPrimary h-btn bg-gold text-black font-sans font-bold text-btnPrimary px-5 rounded-btn flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | | **outline-gold** | `w-btnOutlineGold h-btn bg-white border-[1.5px] border-gold text-amber font-sans font-bold text-btnPrimary px-5 rounded-btn flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | | **outline-dark** | `w-btnOutline h-btn bg-white border-[1.5px] border-black text-black font-sans font-bold text-btn px-4 rounded-sm flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer hover:bg-amber hover:text-white disabled:opacity-50 disabled:cursor-not-allowed` | | **outline-light** | `w-btnOutlineLight h-btnLg bg-black border-[1.5px] border-white text-white font-sans font-bold text-btn px-4 rounded-sm flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | | **ghost** | `w-btnGhost h-btn bg-white border-[1.5px] border-black text-black font-sans font-bold text-btn px-4 rounded-pill flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer hover:bg-amber hover:text-white disabled:opacity-50 disabled:cursor-not-allowed` | | **circular** | `w-btnCircle h-btnCircle bg-white border-[1.5px] border-black rounded-circle flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | | **icon-edit** | `w-btnIcon h-btnIcon bg-white border-[1.5px] border-gold rounded-sm flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | | **icon-delete** | `w-btnIcon h-btnIcon bg-white border-[1.5px] border-danger rounded-sm flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | | **file-upload** | `relative w-btnFileUpload h-btn bg-white border-[1.5px] border-gold text-amber font-sans font-normal text-btnPrimary px-4 rounded-sm flex items-center justify-center cursor-pointer disabled:opacity-50` | **Hover states** (automáticos en las clases): - `outline-dark` y `ghost`: `hover:bg-amber hover:text-white` **File upload input**: `absolute inset-0 opacity-0 cursor-pointer` --- ## Paso 3: Storybook Stories ```tsx export const Primary: Story = { args: { variant: 'primary', children: '+ Agregar' } }; export const OutlineGold: Story = { args: { variant: 'outline-gold', children: 'Editar Estructura del Clima' } }; export const OutlineDark: Story = { args: { variant: 'outline-dark', children: 'VER MÁS' } }; export const OutlineLight: Story = { args: { variant: 'outline-light', children: 'VER MÁS' } }; export const Ghost: Story = { args: { variant: 'ghost', children: 'VER MÁS' } }; export const Circular: Story = { args: { variant: 'circular', children: '↓' } }; export const IconEdit: Story = { args: { variant: 'icon-edit', children: '✏️' } }; export const IconDelete: Story = { args: { variant: 'icon-delete', children: '✕' } }; export const FileUpload: Story = { args: { variant: 'file-upload', children: 'Seleccionar CV' } }; ``` Incluir también stories de: - **States**: disabled, hover (usando `play` function) - **Sizes**: mostrar cada variante con su tamaño design - **Composition**: botones en grupos, con iconos --- ## Paso 4: Tests ### Jest (`Button.test.tsx`) - Renderiza con variant default (primary) - Click handler se llama al hacer click - Estado disabled deshabilita interacción - File upload renderiza input file - Textos y props se renderizan correctamente - Clases CSS correctas por variante ### Cypress (`Button.test.cy.tsx`) - Mount y verifica visual de cada variant - Hover states (outline-dark, ghost) - Disabled state visual - File upload label structure --- ## Paso 5: Actualizar exports `src/components/index.tsx`: ```tsx export * from './Button'; ``` --- ## Verificación 1. `npm run storybook` — verificar que todas las variantes se muestran correctamente en Storybook 2. `npm run test` — Jest tests pasan 3. `npm run cy:run` — Cypress tests pasan 4. `npm run build` — build exitoso sin errores 5. Verificar que el Card fue eliminado y no queda referencia rota 6. Verificar que Tailwind se compila correctamente en el bundle final