siesa-ui-kit 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +115 -115
  2. package/bin/install.cjs +502 -502
  3. package/bin/prepare-publish.cjs +28 -28
  4. package/bin/restore-folders.cjs +28 -28
  5. package/claude/agents/siesa-ui-kit-specialist.md +2445 -0
  6. package/claude/prompts/component-template.md +121 -0
  7. package/claude/prompts/siesa-ui-kit.md +28 -0
  8. package/claude/settings.local.json +67 -2
  9. package/dist/components/Button/icons.d.ts +6 -5
  10. package/dist/components/Button/icons.d.ts.map +1 -1
  11. package/dist/components/DropdownItemCollapsible/DropdownItemCollapsible.d.ts.map +1 -1
  12. package/dist/components/DropdownItemCollapsible/DropdownItemCollapsible.types.d.ts +21 -0
  13. package/dist/components/DropdownItemCollapsible/DropdownItemCollapsible.types.d.ts.map +1 -1
  14. package/dist/components/NavigationRailCommercial/NavigationRailCommercial.d.ts +122 -0
  15. package/dist/components/NavigationRailCommercial/NavigationRailCommercial.d.ts.map +1 -0
  16. package/dist/components/NavigationRailCommercial/NavigationRailCommercial.types.d.ts +139 -0
  17. package/dist/components/NavigationRailCommercial/NavigationRailCommercial.types.d.ts.map +1 -0
  18. package/dist/components/NavigationRailCommercial/icons.d.ts +33 -0
  19. package/dist/components/NavigationRailCommercial/icons.d.ts.map +1 -0
  20. package/dist/components/NavigationRailCommercial/index.d.ts +4 -0
  21. package/dist/components/NavigationRailCommercial/index.d.ts.map +1 -0
  22. package/dist/components/NavigationRailItem/NavigationRailItem.d.ts.map +1 -1
  23. package/dist/components/NavigationRailItem/NavigationRailItem.types.d.ts +7 -0
  24. package/dist/components/NavigationRailItem/NavigationRailItem.types.d.ts.map +1 -1
  25. package/dist/components/NavigationRailTypes/NavigationRailTypes.d.ts.map +1 -1
  26. package/dist/components/NavigationRailTypes/NavigationRailTypes.types.d.ts +41 -0
  27. package/dist/components/NavigationRailTypes/NavigationRailTypes.types.d.ts.map +1 -1
  28. package/dist/components/NavigationRailTypes/icons.d.ts +15 -29
  29. package/dist/components/NavigationRailTypes/icons.d.ts.map +1 -1
  30. package/dist/components/Select/Select.d.ts.map +1 -1
  31. package/dist/components/Select/icons.d.ts +6 -2
  32. package/dist/components/Select/icons.d.ts.map +1 -1
  33. package/dist/index.d.ts +32 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/siesa-ui-kit.cjs +404 -190
  36. package/dist/siesa-ui-kit.cjs.map +1 -1
  37. package/dist/siesa-ui-kit.mjs +6590 -1506
  38. package/dist/siesa-ui-kit.mjs.map +1 -1
  39. package/dist/views/LayoutCommercial/LayoutCommercial.d.ts +48 -0
  40. package/dist/views/LayoutCommercial/LayoutCommercial.d.ts.map +1 -0
  41. package/dist/views/LayoutCommercial/LayoutCommercial.types.d.ts +49 -0
  42. package/dist/views/LayoutCommercial/LayoutCommercial.types.d.ts.map +1 -0
  43. package/dist/views/LayoutCommercial/index.d.ts +3 -0
  44. package/dist/views/LayoutCommercial/index.d.ts.map +1 -0
  45. package/docs/icons.md +12 -31
  46. package/package.json +111 -110
  47. package/src/components/Avatar/Avatar.stories.tsx +494 -494
  48. package/src/components/Button/Button.stories.tsx +950 -950
  49. package/src/components/Button/Button.tsx +337 -337
  50. package/src/components/Button/Button.types.ts +180 -180
  51. package/src/components/Button/icons.tsx +23 -62
  52. package/src/components/DescriptionList/DescriptionList.stories.tsx +250 -250
  53. package/src/components/Divider/Divider.stories.tsx +263 -263
  54. package/src/components/DropdownItemCollapsible/DropdownItemCollapsible.stories.tsx +317 -317
  55. package/src/components/DropdownItemCollapsible/DropdownItemCollapsible.tsx +307 -287
  56. package/src/components/DropdownItemCollapsible/DropdownItemCollapsible.types.ts +136 -111
  57. package/src/components/DropdownItemCollapsible/README.md +264 -264
  58. package/src/components/DropdownItemCollapsible/icons.tsx +57 -57
  59. package/src/components/DropdownItemCollapsible/index.ts +12 -12
  60. package/src/components/DropdownItemHeading/DropdownItemHeading.stories.tsx +386 -386
  61. package/src/components/DropdownItemHeading/DropdownItemHeading.tsx +216 -216
  62. package/src/components/DropdownItemHeading/DropdownItemHeading.types.ts +93 -93
  63. package/src/components/DropdownItemHeading/README.md +573 -573
  64. package/src/components/DropdownItemHeading/icons.tsx +125 -125
  65. package/src/components/DropdownItemHeading/index.ts +3 -3
  66. package/src/components/Input/Input.stories.tsx +583 -583
  67. package/src/components/LoginView/LoginView.stories.tsx +148 -148
  68. package/src/components/LoginView/LoginView.tsx +426 -426
  69. package/src/components/LoginView/LoginView.types.ts +52 -52
  70. package/src/components/LoginView/README.md +396 -396
  71. package/src/components/LoginView/icons.tsx +85 -85
  72. package/src/components/LoginView/index.ts +3 -3
  73. package/src/components/Navbar/Navbar.stories.tsx +810 -810
  74. package/src/components/Navbar/Navbar.tsx +755 -755
  75. package/src/components/Navbar/Navbar.types.ts +219 -219
  76. package/src/components/Navbar/README.md +279 -279
  77. package/src/components/Navbar/index.ts +8 -8
  78. package/src/components/NavigationRailCommercial/NavigationRailCommercial.stories.tsx +464 -0
  79. package/src/components/NavigationRailCommercial/NavigationRailCommercial.tsx +301 -0
  80. package/src/components/NavigationRailCommercial/NavigationRailCommercial.types.ts +162 -0
  81. package/src/components/NavigationRailCommercial/README.md +251 -0
  82. package/src/components/NavigationRailCommercial/icons.tsx +54 -0
  83. package/src/components/NavigationRailCommercial/index.ts +6 -0
  84. package/src/components/NavigationRailItem/NavigationRailItem.stories.tsx +667 -667
  85. package/src/components/NavigationRailItem/NavigationRailItem.tsx +314 -313
  86. package/src/components/NavigationRailItem/NavigationRailItem.types.ts +175 -167
  87. package/src/components/NavigationRailItem/README.md +476 -476
  88. package/src/components/NavigationRailItem/index.ts +2 -2
  89. package/src/components/NavigationRailPanel/NavigationRailPanel.stories.tsx +462 -462
  90. package/src/components/NavigationRailPanel/NavigationRailPanel.tsx +332 -332
  91. package/src/components/NavigationRailPanel/NavigationRailPanel.types.ts +178 -178
  92. package/src/components/NavigationRailPanel/README.md +461 -461
  93. package/src/components/NavigationRailPanel/index.ts +6 -6
  94. package/src/components/NavigationRailTypes/NavigationRailTypes.stories.tsx +682 -528
  95. package/src/components/NavigationRailTypes/NavigationRailTypes.tsx +363 -378
  96. package/src/components/NavigationRailTypes/NavigationRailTypes.types.ts +178 -130
  97. package/src/components/NavigationRailTypes/README.md +573 -573
  98. package/src/components/NavigationRailTypes/icons.tsx +76 -141
  99. package/src/components/NavigationRailTypes/index.ts +7 -7
  100. package/src/components/Notification/Notification.stories.tsx +513 -513
  101. package/src/components/Notification/Notification.tsx +145 -145
  102. package/src/components/Notification/Notification.types.ts +142 -142
  103. package/src/components/Notification/README.md +409 -409
  104. package/src/components/POSConvention/POSConvention.stories.tsx +235 -235
  105. package/src/components/POSConvention/POSConvention.tsx +129 -129
  106. package/src/components/POSConvention/POSConvention.types.ts +38 -38
  107. package/src/components/POSConvention/README.md +123 -123
  108. package/src/components/POSConvention/icons.tsx +45 -45
  109. package/src/components/POSConvention/index.ts +3 -3
  110. package/src/components/POSLocationButton/POSLocationButton.stories.tsx +531 -531
  111. package/src/components/POSLocationButton/POSLocationButton.tsx +247 -247
  112. package/src/components/POSLocationButton/POSLocationButton.types.ts +87 -87
  113. package/src/components/POSLocationButton/README.md +253 -253
  114. package/src/components/POSLocationButton/icons.tsx +120 -120
  115. package/src/components/POSLocationButton/index.ts +14 -14
  116. package/src/components/POSNumberButton/POSNumberButton.stories.tsx +415 -415
  117. package/src/components/POSNumberButton/POSNumberButton.tsx +179 -179
  118. package/src/components/POSNumberButton/POSNumberButton.types.ts +51 -51
  119. package/src/components/POSNumberButton/README.md +321 -321
  120. package/src/components/POSNumberButton/index.ts +3 -3
  121. package/src/components/POSProductButton/POSProductButton.stories.tsx +318 -318
  122. package/src/components/POSProductCard/POSProductCard.stories.tsx +642 -642
  123. package/src/components/POSProductCard/POSProductCard.tsx +208 -208
  124. package/src/components/POSProductCard/POSProductCard.types.ts +76 -76
  125. package/src/components/POSProductCard/README.md +179 -179
  126. package/src/components/POSProductCard/icons.tsx +26 -26
  127. package/src/components/POSProductCard/index.ts +2 -2
  128. package/src/components/POSProductSidebarItems/POSProductSidebarItems.stories.tsx +753 -753
  129. package/src/components/POSProductSidebarItems/POSProductSidebarItems.tsx +332 -332
  130. package/src/components/POSProductSidebarItems/POSProductSidebarItems.types.ts +119 -119
  131. package/src/components/POSProductSidebarItems/README.md +198 -198
  132. package/src/components/POSProductSidebarItems/icons.tsx +21 -21
  133. package/src/components/POSProductSidebarItems/index.ts +3 -3
  134. package/src/components/POSTable/POSTable.stories.tsx +737 -737
  135. package/src/components/POSTable/POSTable.tsx +401 -401
  136. package/src/components/POSTable/README.md +286 -286
  137. package/src/components/Quantity/Quantity.stories.tsx +457 -457
  138. package/src/components/Radio/Radio.stories.tsx +523 -523
  139. package/src/components/Radio/Radio.tsx +1 -1
  140. package/src/components/Select/Select.stories.tsx +32 -0
  141. package/src/components/Select/Select.tsx +457 -454
  142. package/src/components/Select/icons.tsx +16 -41
  143. package/src/components/SignUpView/SignUpView.stories.tsx +129 -129
  144. package/src/components/SignUpView/SignUpView.tsx +503 -503
  145. package/src/components/SignUpView/SignUpView.types.ts +58 -58
  146. package/src/components/SignUpView/icons.tsx +71 -71
  147. package/src/components/SignUpView/index.ts +3 -3
  148. package/src/components/Switch/README.md +112 -112
  149. package/src/components/Switch/Switch.stories.tsx +550 -550
  150. package/src/components/Switch/Switch.tsx +246 -246
  151. package/src/components/Switch/Switch.types.ts +67 -67
  152. package/src/components/Table/Table.stories.tsx +805 -805
  153. package/src/components/Tabs/README.md +201 -201
  154. package/src/components/Tabs/Tabs.stories.tsx +580 -580
  155. package/src/components/Tabs/Tabs.tsx +356 -356
  156. package/src/components/Tabs/Tabs.types.ts +127 -127
  157. package/src/components/Tabs/icons.tsx +129 -129
  158. package/src/components/Tabs/index.ts +11 -11
  159. package/src/components/Textarea/Textarea.stories.tsx +535 -535
  160. package/src/index.ts +133 -102
  161. package/src/views/LayoutCommercial/LayoutCommercial.stories.tsx +374 -0
  162. package/src/views/LayoutCommercial/LayoutCommercial.tsx +125 -0
  163. package/src/views/LayoutCommercial/LayoutCommercial.types.ts +54 -0
  164. package/src/views/LayoutCommercial/README.md +286 -0
  165. package/src/views/LayoutCommercial/index.ts +2 -0
  166. package/src/views/ListView/ListView.stories.tsx +329 -329
  167. package/src/views/ListView/ListView.tsx +570 -570
  168. package/src/views/ListView/ListView.types.ts +211 -211
  169. package/src/views/ListView/icons.tsx +282 -282
  170. package/src/views/ListView/index.ts +11 -11
  171. package/src/views/LoginView/LoginView.tsx +426 -426
  172. package/src/views/ProductsView/ProductsView.stories.tsx +344 -344
  173. package/src/views/ProductsView/ProductsView.tsx +480 -480
  174. package/src/views/ProductsView/ProductsView.types.ts +238 -238
  175. package/src/views/ProductsView/README.md +312 -312
  176. package/src/views/ProductsView/icons.tsx +38 -38
  177. package/src/views/ProductsView/index.ts +8 -8
  178. package/src/views/RecoverPasswordView/RecoverPasswordView.tsx +376 -376
  179. package/src/views/SignUpView/SignUpView.tsx +503 -503
  180. package/src/views/TableLayoutView/README.md +268 -268
  181. package/src/views/TableLayoutView/TableLayoutView.stories.tsx +235 -235
  182. package/src/views/TableLayoutView/TableLayoutView.tsx +461 -461
  183. package/src/views/TableLayoutView/TableLayoutView.types.ts +209 -209
  184. package/src/views/TableLayoutView/icons.tsx +113 -113
  185. package/src/views/TableLayoutView/index.ts +6 -6
  186. package/storybook/main.ts +19 -19
  187. package/storybook/preview.tsx +84 -84
  188. package/storybook/vitest.setup.ts +6 -6
  189. package/tailwind.config.js +128 -128
@@ -1,337 +1,337 @@
1
- import React from 'react';
2
- import type { ButtonProps } from './Button.types';
3
-
4
- /**
5
- * Componente Button del sistema de diseño Siesa
6
- *
7
- * Implementación pixel-perfect basada en Figma (node 4001-17240)
8
- * con soporte completo para todos los estados, tamaños y variantes.
9
- *
10
- * **Variantes (type):**
11
- * - `default`: Botón primario con fondo sólido (#0e79fd) y borde (#3c9bf6)
12
- * - Sombra interna para efecto de profundidad
13
- * - Usar para acciones principales (Guardar, Enviar, Confirmar)
14
- * - `outline`: Botón secundario con borde (#93d1fd) y shadow-sm
15
- * - Usar para acciones secundarias (Cancelar, Volver)
16
- * - `plain`: Botón terciario sin borde visible
17
- * - Hover overlay sutil
18
- * - Usar para acciones sutiles (Cerrar, Ver más, Links)
19
- *
20
- * **Tamaños (size):**
21
- * - `xs` (24px): Espacios muy compactos, inline actions. Padding: 8px h, 4px v
22
- * - `sm` (28px): Barras de herramientas, acciones secundarias. Padding: 8px h, 4px v
23
- * - `base` (32px): Tamaño estándar para la mayoría de casos. Padding: 10px h, 6px v
24
- * - `l` (36px): Botones destacados, CTAs. Padding: 12px h, 8px v
25
- * - `xl` (40px): Heroes, landing pages. Padding: 16px h, 8px v
26
- *
27
- * **Estados:**
28
- * - `default`: Estado normal con colores base
29
- * - `hover`: Overlay visual sutil (bg-primary-custom-500 para default)
30
- * - `focus`: Focus ring de 4px (#60b6fa) con offset de 2px (#dbeefe)
31
- * - `active`: Scale animation (scale-95) para feedback táctil
32
- * - `disabled`: Opacity 50% con pointer-events-none
33
- *
34
- * **Badges de notificación:**
35
- * - `badge`: Muestra un dot de notificación en la esquina superior derecha
36
- * - `badgeCount`: Muestra un badge con número (99+ para >99)
37
- * - `badgeColor`: Color del badge (por defecto: red - #b91c1c)
38
- *
39
- * **Especificaciones de Figma:**
40
- * - Border radius: 6px (rounded-md)
41
- * - Tipografía: Label Small (14px Bold) para sm/base/l/xl, Label Tiny (12px Bold) para xs
42
- * - Iconos: 16x16px en todos los tamaños
43
- * - Gap entre elementos: 8px (xs/sm/base), 12px (l/xl)
44
- *
45
- * **Dark Mode:**
46
- * Los botones invierten colores en dark mode:
47
- * - Default: fondo celeste claro (#bfe2fe), texto azul (#0e79fd), borde celeste (#93d1fd)
48
- * - Outline: texto celeste (#93d1fd), borde azul (#0f6ae3)
49
- * - Plain: texto celeste (#93d1fd), hover overlay blanco 20%
50
- * - Focus ring adaptativo con offset oscuro
51
- *
52
- * @see docs/colors.md - Sistema de colores
53
- * @see docs/shadows.md - Sistema de sombras (shadow-button-inset, shadow-sm)
54
- * @see docs/typography.md - Sistema tipográfico (Label Small/Tiny)
55
- * @see docs/spacing.md - Sistema de espaciado
56
- * @see https://www.figma.com/design/5XNqf2YTxvwemxwo1LMQ6j/Siesa-UI-Kit?node-id=4001-17240 - Diseño Figma
57
- *
58
- * @example
59
- * ```tsx
60
- * // Botón primario con icono
61
- * <Button type="default" size="base" leftIcon={<SaveIcon />}>
62
- * Guardar
63
- * </Button>
64
- *
65
- * // Botón secundario
66
- * <Button type="outline" size="base">
67
- * Cancelar
68
- * </Button>
69
- *
70
- * // Botón solo icono para barra de herramientas
71
- * <Button type="plain" size="sm" iconOnly leftIcon={<CloseIcon />} ariaLabel="Cerrar" />
72
- *
73
- * // Botón con badge de notificación (dot)
74
- * <Button type="default" size="base" badge>
75
- * Notificaciones
76
- * </Button>
77
- *
78
- * // Botón con badge contador
79
- * <Button type="default" size="base" badgeCount={5} badgeColor="red">
80
- * Mensajes
81
- * </Button>
82
- * ```
83
- */
84
- export const Button: React.FC<ButtonProps> = ({
85
- type = 'default',
86
- size = 'base',
87
- iconOnly = false,
88
- leftIcon,
89
- rightIcon,
90
- children,
91
- disabled = false,
92
- className = '',
93
- onClick,
94
- htmlType = 'button',
95
- fullWidth = false,
96
- ariaLabel,
97
- badge = false,
98
- badgeCount,
99
- badgeColor = 'red',
100
- ...rest
101
- }) => {
102
- // ===== CLASES DE TAMAÑO =====
103
- const sizeClasses = {
104
- xs: iconOnly ? 'h-6 w-6 p-1' : 'h-6 py-1 px-2 gap-2',
105
- sm: iconOnly ? 'h-7 w-7 p-1.5' : 'h-7 py-1 px-2 gap-2',
106
- base: iconOnly ? 'h-8 w-8 p-2' : 'h-8 py-1.5 px-2.5 gap-2',
107
- l: iconOnly ? 'h-9 w-9 p-2.5' : 'h-9 py-2 px-3 gap-3',
108
- xl: iconOnly ? 'h-10 w-10 p-3' : 'h-10 py-2 px-4 gap-3',
109
- };
110
-
111
- // ===== CLASES DE TAMAÑO DE ICONO =====
112
- const iconSizeClasses = {
113
- xs: 'w-4 h-4',
114
- sm: 'w-4 h-4',
115
- base: 'w-4 h-4',
116
- l: 'w-4 h-4',
117
- xl: 'w-4 h-4',
118
- };
119
-
120
- // ===== CLASES DE TAMAÑO DE TEXTO =====
121
- // Usando el sistema de tipografía Label del design system (typography.md)
122
- const textSizeClasses = {
123
- xs: 'text-xs', // Label Tiny - 12px
124
- sm: 'text-sm', // Label Small - 14px
125
- base: 'text-sm', // Label Small - 14px (default para base)
126
- l: 'text-sm', // Label Small - 14px (corregido según Figma)
127
- xl: 'text-sm', // Label Small - 14px (corregido según Figma)
128
- };
129
-
130
- // ===== CLASES DE TIPO (Default, Outline, Plain) =====
131
- // Especificaciones de Figma node 4001-17240
132
- // Orden de modificadores: {responsive}:{dark}:{state}:{utility}
133
- // Dark mode: Los botones invierten colores (fondo claro, texto oscuro)
134
- // - Light: fondo #0e79fd (azul), texto #eff8ff (blanco)
135
- // - Dark: fondo #bfe2fe (celeste claro), texto #0e79fd (azul)
136
- const typeClasses = {
137
- default: `
138
- bg-primary-custom-600
139
- text-primary-inverse-content
140
- border
141
- border-primary-inverse-border
142
- shadow-button-inset
143
- hover:bg-primary-custom-500
144
- active:scale-95
145
- transition-all
146
- duration-150
147
- dark:bg-dark-bg-inverse
148
- dark:text-dark-content-inverse
149
- dark:border-dark-border-inverse
150
- dark:hover:bg-dark-bg-inverse/90
151
- `,
152
- outline: `
153
- bg-transparent
154
- text-primary-custom-600
155
- border
156
- border-primary-custom-300
157
- shadow-sm
158
- hover:bg-primary-custom-100
159
- active:scale-95
160
- transition-all
161
- duration-150
162
- dark:text-dark-content-custom
163
- dark:border-dark-border-custom
164
- dark:hover:bg-dark-bg-custom/20
165
- `,
166
- plain: `
167
- bg-transparent
168
- text-primary-custom-600
169
- border
170
- border-transparent
171
- hover:bg-hover-overlay
172
- active:scale-95
173
- transition-all
174
- duration-150
175
- dark:text-dark-content-custom
176
- dark:hover:bg-hover-overlay-dark
177
- `,
178
- };
179
-
180
- // ===== CLASES BASE =====
181
- // Especificaciones de Figma: Focus ring = 4px spread primary-custom-400 + 2px offset primary-custom-100
182
- // Dark mode: Focus ring adaptativo con offset oscuro
183
- const baseClasses = `
184
- inline-flex
185
- items-center
186
- justify-center
187
- rounded-md
188
- font-bold
189
- whitespace-nowrap
190
- focus:outline-none
191
- focus:ring-4
192
- focus:ring-primary-custom-400
193
- focus:ring-offset-2
194
- focus:ring-offset-primary-custom-100
195
- dark:focus:ring-dark-border-custom
196
- dark:focus:ring-offset-dark-bg-primary
197
- disabled:opacity-50
198
- disabled:cursor-not-allowed
199
- disabled:pointer-events-none
200
- `;
201
-
202
- // ===== CLASE FULL WIDTH =====
203
- const widthClass = fullWidth ? 'w-full' : '';
204
-
205
- // ===== COMBINAR TODAS LAS CLASES =====
206
- const buttonClasses = [
207
- baseClasses,
208
- sizeClasses[size],
209
- typeClasses[type],
210
- widthClass,
211
- className,
212
- ]
213
- .join(' ')
214
- .replace(/\s+/g, ' ')
215
- .trim();
216
-
217
- // ===== RENDERIZAR ICONO =====
218
- const renderIcon = (icon: React.ReactNode) => {
219
- if (!icon) return null;
220
- return (
221
- <span className={`inline-flex items-center justify-center ${iconSizeClasses[size]}`}>
222
- {icon}
223
- </span>
224
- );
225
- };
226
-
227
- // ===== RENDERIZAR CONTENIDO =====
228
- const renderContent = () => {
229
- // Si es iconOnly, solo mostrar leftIcon
230
- if (iconOnly) {
231
- return renderIcon(leftIcon);
232
- }
233
-
234
- // Si tiene texto y/o iconos
235
- return (
236
- <>
237
- {leftIcon && renderIcon(leftIcon)}
238
- {children && <span className={textSizeClasses[size]}>{children}</span>}
239
- {rightIcon && renderIcon(rightIcon)}
240
- </>
241
- );
242
- };
243
-
244
- // ===== MAPA DE COLORES DE BADGE =====
245
- // Basado en el componente Badge existente
246
- const badgeColorClasses: Record<string, { bg: string; text: string }> = {
247
- zinc: { bg: 'bg-zinc-600', text: 'text-white' },
248
- red: { bg: 'bg-red-700', text: 'text-white' },
249
- orange: { bg: 'bg-orange-700', text: 'text-white' },
250
- amber: { bg: 'bg-amber-700', text: 'text-white' },
251
- yellow: { bg: 'bg-yellow-700', text: 'text-white' },
252
- lime: { bg: 'bg-lime-700', text: 'text-white' },
253
- green: { bg: 'bg-green-700', text: 'text-white' },
254
- emerald: { bg: 'bg-emerald-700', text: 'text-white' },
255
- teal: { bg: 'bg-teal-700', text: 'text-white' },
256
- cyan: { bg: 'bg-cyan-700', text: 'text-white' },
257
- sky: { bg: 'bg-sky-700', text: 'text-white' },
258
- blue: { bg: 'bg-blue-700', text: 'text-white' },
259
- indigo: { bg: 'bg-indigo-700', text: 'text-white' },
260
- violet: { bg: 'bg-violet-700', text: 'text-white' },
261
- purple: { bg: 'bg-purple-700', text: 'text-white' },
262
- fuchsia: { bg: 'bg-fuchsia-700', text: 'text-white' },
263
- pink: { bg: 'bg-pink-700', text: 'text-white' },
264
- rose: { bg: 'bg-rose-700', text: 'text-white' },
265
- primary: { bg: 'bg-primary-custom-600', text: 'text-white' },
266
- secondary: { bg: 'bg-zinc-600', text: 'text-white' },
267
- tertiary: { bg: 'bg-zinc-600', text: 'text-white' },
268
- };
269
-
270
- const badgeColors = badgeColorClasses[badgeColor] || badgeColorClasses.red;
271
-
272
- // ===== RENDERIZAR BADGE =====
273
- const renderBadge = () => {
274
- // Si no hay badge ni badgeCount, no renderizar nada
275
- if (!badge && badgeCount === undefined) return null;
276
-
277
- // Si hay badgeCount, renderizar badge con número
278
- if (badgeCount !== undefined) {
279
- return (
280
- <span
281
- className={`
282
- absolute
283
- -top-1
284
- -right-1
285
- flex
286
- items-center
287
- justify-center
288
- min-w-[16px]
289
- h-4
290
- px-1
291
- rounded-full
292
- text-[10px]
293
- font-bold
294
- leading-none
295
- ${badgeColors.bg}
296
- ${badgeColors.text}
297
- pointer-events-none
298
- `.trim().replace(/\s+/g, ' ')}
299
- aria-label={`${badgeCount} notificaciones`}
300
- >
301
- {badgeCount > 99 ? '99+' : badgeCount}
302
- </span>
303
- );
304
- }
305
-
306
- // Si solo hay badge (sin número), renderizar dot
307
- return (
308
- <span
309
- className={`
310
- absolute
311
- -top-1
312
- -right-1
313
- w-2
314
- h-2
315
- rounded-full
316
- ${badgeColors.bg}
317
- pointer-events-none
318
- `.trim().replace(/\s+/g, ' ')}
319
- aria-label="Notificación"
320
- />
321
- );
322
- };
323
-
324
- return (
325
- <button
326
- type={htmlType}
327
- className={`${buttonClasses} ${(badge || badgeCount !== undefined) ? 'relative' : ''}`}
328
- disabled={disabled}
329
- onClick={onClick}
330
- aria-label={ariaLabel}
331
- {...rest}
332
- >
333
- {renderContent()}
334
- {renderBadge()}
335
- </button>
336
- );
337
- };
1
+ import React from 'react';
2
+ import type { ButtonProps } from './Button.types';
3
+
4
+ /**
5
+ * Componente Button del sistema de diseño Siesa
6
+ *
7
+ * Implementación pixel-perfect basada en Figma (node 4001-17240)
8
+ * con soporte completo para todos los estados, tamaños y variantes.
9
+ *
10
+ * **Variantes (type):**
11
+ * - `default`: Botón primario con fondo sólido (#0e79fd) y borde (#3c9bf6)
12
+ * - Sombra interna para efecto de profundidad
13
+ * - Usar para acciones principales (Guardar, Enviar, Confirmar)
14
+ * - `outline`: Botón secundario con borde (#93d1fd) y shadow-sm
15
+ * - Usar para acciones secundarias (Cancelar, Volver)
16
+ * - `plain`: Botón terciario sin borde visible
17
+ * - Hover overlay sutil
18
+ * - Usar para acciones sutiles (Cerrar, Ver más, Links)
19
+ *
20
+ * **Tamaños (size):**
21
+ * - `xs` (24px): Espacios muy compactos, inline actions. Padding: 8px h, 4px v
22
+ * - `sm` (28px): Barras de herramientas, acciones secundarias. Padding: 8px h, 4px v
23
+ * - `base` (32px): Tamaño estándar para la mayoría de casos. Padding: 10px h, 6px v
24
+ * - `l` (36px): Botones destacados, CTAs. Padding: 12px h, 8px v
25
+ * - `xl` (40px): Heroes, landing pages. Padding: 16px h, 8px v
26
+ *
27
+ * **Estados:**
28
+ * - `default`: Estado normal con colores base
29
+ * - `hover`: Overlay visual sutil (bg-primary-custom-500 para default)
30
+ * - `focus`: Focus ring de 4px (#60b6fa) con offset de 2px (#dbeefe)
31
+ * - `active`: Scale animation (scale-95) para feedback táctil
32
+ * - `disabled`: Opacity 50% con pointer-events-none
33
+ *
34
+ * **Badges de notificación:**
35
+ * - `badge`: Muestra un dot de notificación en la esquina superior derecha
36
+ * - `badgeCount`: Muestra un badge con número (99+ para >99)
37
+ * - `badgeColor`: Color del badge (por defecto: red - #b91c1c)
38
+ *
39
+ * **Especificaciones de Figma:**
40
+ * - Border radius: 6px (rounded-md)
41
+ * - Tipografía: Label Small (14px Bold) para sm/base/l/xl, Label Tiny (12px Bold) para xs
42
+ * - Iconos: 16x16px en todos los tamaños
43
+ * - Gap entre elementos: 8px (xs/sm/base), 12px (l/xl)
44
+ *
45
+ * **Dark Mode:**
46
+ * Los botones invierten colores en dark mode:
47
+ * - Default: fondo celeste claro (#bfe2fe), texto azul (#0e79fd), borde celeste (#93d1fd)
48
+ * - Outline: texto celeste (#93d1fd), borde azul (#0f6ae3)
49
+ * - Plain: texto celeste (#93d1fd), hover overlay blanco 20%
50
+ * - Focus ring adaptativo con offset oscuro
51
+ *
52
+ * @see docs/colors.md - Sistema de colores
53
+ * @see docs/shadows.md - Sistema de sombras (shadow-button-inset, shadow-sm)
54
+ * @see docs/typography.md - Sistema tipográfico (Label Small/Tiny)
55
+ * @see docs/spacing.md - Sistema de espaciado
56
+ * @see https://www.figma.com/design/5XNqf2YTxvwemxwo1LMQ6j/Siesa-UI-Kit?node-id=4001-17240 - Diseño Figma
57
+ *
58
+ * @example
59
+ * ```tsx
60
+ * // Botón primario con icono
61
+ * <Button type="default" size="base" leftIcon={<SaveIcon />}>
62
+ * Guardar
63
+ * </Button>
64
+ *
65
+ * // Botón secundario
66
+ * <Button type="outline" size="base">
67
+ * Cancelar
68
+ * </Button>
69
+ *
70
+ * // Botón solo icono para barra de herramientas
71
+ * <Button type="plain" size="sm" iconOnly leftIcon={<CloseIcon />} ariaLabel="Cerrar" />
72
+ *
73
+ * // Botón con badge de notificación (dot)
74
+ * <Button type="default" size="base" badge>
75
+ * Notificaciones
76
+ * </Button>
77
+ *
78
+ * // Botón con badge contador
79
+ * <Button type="default" size="base" badgeCount={5} badgeColor="red">
80
+ * Mensajes
81
+ * </Button>
82
+ * ```
83
+ */
84
+ export const Button: React.FC<ButtonProps> = ({
85
+ type = 'default',
86
+ size = 'base',
87
+ iconOnly = false,
88
+ leftIcon,
89
+ rightIcon,
90
+ children,
91
+ disabled = false,
92
+ className = '',
93
+ onClick,
94
+ htmlType = 'button',
95
+ fullWidth = false,
96
+ ariaLabel,
97
+ badge = false,
98
+ badgeCount,
99
+ badgeColor = 'red',
100
+ ...rest
101
+ }) => {
102
+ // ===== CLASES DE TAMAÑO =====
103
+ const sizeClasses = {
104
+ xs: iconOnly ? 'h-6 w-6 p-1' : 'h-6 py-1 px-2 gap-2',
105
+ sm: iconOnly ? 'h-7 w-7 p-1.5' : 'h-7 py-1 px-2 gap-2',
106
+ base: iconOnly ? 'h-8 w-8 p-2' : 'h-8 py-1.5 px-2.5 gap-2',
107
+ l: iconOnly ? 'h-9 w-9 p-2.5' : 'h-9 py-2 px-3 gap-3',
108
+ xl: iconOnly ? 'h-10 w-10 p-3' : 'h-10 py-2 px-4 gap-3',
109
+ };
110
+
111
+ // ===== CLASES DE TAMAÑO DE ICONO =====
112
+ const iconSizeClasses = {
113
+ xs: 'w-4 h-4',
114
+ sm: 'w-4 h-4',
115
+ base: 'w-4 h-4',
116
+ l: 'w-4 h-4',
117
+ xl: 'w-4 h-4',
118
+ };
119
+
120
+ // ===== CLASES DE TAMAÑO DE TEXTO =====
121
+ // Usando el sistema de tipografía Label del design system (typography.md)
122
+ const textSizeClasses = {
123
+ xs: 'text-xs', // Label Tiny - 12px
124
+ sm: 'text-sm', // Label Small - 14px
125
+ base: 'text-sm', // Label Small - 14px (default para base)
126
+ l: 'text-sm', // Label Small - 14px (corregido según Figma)
127
+ xl: 'text-sm', // Label Small - 14px (corregido según Figma)
128
+ };
129
+
130
+ // ===== CLASES DE TIPO (Default, Outline, Plain) =====
131
+ // Especificaciones de Figma node 4001-17240
132
+ // Orden de modificadores: {responsive}:{dark}:{state}:{utility}
133
+ // Dark mode: Los botones invierten colores (fondo claro, texto oscuro)
134
+ // - Light: fondo #0e79fd (azul), texto #eff8ff (blanco)
135
+ // - Dark: fondo #bfe2fe (celeste claro), texto #0e79fd (azul)
136
+ const typeClasses = {
137
+ default: `
138
+ bg-primary-custom-600
139
+ text-primary-inverse-content
140
+ border
141
+ border-primary-inverse-border
142
+ shadow-button-inset
143
+ hover:bg-primary-custom-500
144
+ active:scale-95
145
+ transition-all
146
+ duration-150
147
+ dark:bg-dark-bg-inverse
148
+ dark:text-dark-content-inverse
149
+ dark:border-dark-border-inverse
150
+ dark:hover:bg-dark-bg-inverse/90
151
+ `,
152
+ outline: `
153
+ bg-transparent
154
+ text-primary-custom-600
155
+ border
156
+ border-primary-custom-300
157
+ shadow-sm
158
+ hover:bg-primary-custom-100
159
+ active:scale-95
160
+ transition-all
161
+ duration-150
162
+ dark:text-dark-content-custom
163
+ dark:border-dark-border-custom
164
+ dark:hover:bg-dark-bg-custom/20
165
+ `,
166
+ plain: `
167
+ bg-transparent
168
+ text-primary-custom-600
169
+ border
170
+ border-transparent
171
+ hover:bg-hover-overlay
172
+ active:scale-95
173
+ transition-all
174
+ duration-150
175
+ dark:text-dark-content-custom
176
+ dark:hover:bg-hover-overlay-dark
177
+ `,
178
+ };
179
+
180
+ // ===== CLASES BASE =====
181
+ // Especificaciones de Figma: Focus ring = 4px spread primary-custom-400 + 2px offset primary-custom-100
182
+ // Dark mode: Focus ring adaptativo con offset oscuro
183
+ const baseClasses = `
184
+ inline-flex
185
+ items-center
186
+ justify-center
187
+ rounded-md
188
+ font-bold
189
+ whitespace-nowrap
190
+ focus:outline-none
191
+ focus:ring-4
192
+ focus:ring-primary-custom-400
193
+ focus:ring-offset-2
194
+ focus:ring-offset-primary-custom-100
195
+ dark:focus:ring-dark-border-custom
196
+ dark:focus:ring-offset-dark-bg-primary
197
+ disabled:opacity-50
198
+ disabled:cursor-not-allowed
199
+ disabled:pointer-events-none
200
+ `;
201
+
202
+ // ===== CLASE FULL WIDTH =====
203
+ const widthClass = fullWidth ? 'w-full' : '';
204
+
205
+ // ===== COMBINAR TODAS LAS CLASES =====
206
+ const buttonClasses = [
207
+ baseClasses,
208
+ sizeClasses[size],
209
+ typeClasses[type],
210
+ widthClass,
211
+ className,
212
+ ]
213
+ .join(' ')
214
+ .replace(/\s+/g, ' ')
215
+ .trim();
216
+
217
+ // ===== RENDERIZAR ICONO =====
218
+ const renderIcon = (icon: React.ReactNode) => {
219
+ if (!icon) return null;
220
+ return (
221
+ <span className={`inline-flex items-center justify-center ${iconSizeClasses[size]}`}>
222
+ {icon}
223
+ </span>
224
+ );
225
+ };
226
+
227
+ // ===== RENDERIZAR CONTENIDO =====
228
+ const renderContent = () => {
229
+ // Si es iconOnly, solo mostrar leftIcon
230
+ if (iconOnly) {
231
+ return renderIcon(leftIcon);
232
+ }
233
+
234
+ // Si tiene texto y/o iconos
235
+ return (
236
+ <>
237
+ {leftIcon && renderIcon(leftIcon)}
238
+ {children && <span className={textSizeClasses[size]}>{children}</span>}
239
+ {rightIcon && renderIcon(rightIcon)}
240
+ </>
241
+ );
242
+ };
243
+
244
+ // ===== MAPA DE COLORES DE BADGE =====
245
+ // Basado en el componente Badge existente
246
+ const badgeColorClasses: Record<string, { bg: string; text: string }> = {
247
+ zinc: { bg: 'bg-zinc-600', text: 'text-white' },
248
+ red: { bg: 'bg-red-700', text: 'text-white' },
249
+ orange: { bg: 'bg-orange-700', text: 'text-white' },
250
+ amber: { bg: 'bg-amber-700', text: 'text-white' },
251
+ yellow: { bg: 'bg-yellow-700', text: 'text-white' },
252
+ lime: { bg: 'bg-lime-700', text: 'text-white' },
253
+ green: { bg: 'bg-green-700', text: 'text-white' },
254
+ emerald: { bg: 'bg-emerald-700', text: 'text-white' },
255
+ teal: { bg: 'bg-teal-700', text: 'text-white' },
256
+ cyan: { bg: 'bg-cyan-700', text: 'text-white' },
257
+ sky: { bg: 'bg-sky-700', text: 'text-white' },
258
+ blue: { bg: 'bg-blue-700', text: 'text-white' },
259
+ indigo: { bg: 'bg-indigo-700', text: 'text-white' },
260
+ violet: { bg: 'bg-violet-700', text: 'text-white' },
261
+ purple: { bg: 'bg-purple-700', text: 'text-white' },
262
+ fuchsia: { bg: 'bg-fuchsia-700', text: 'text-white' },
263
+ pink: { bg: 'bg-pink-700', text: 'text-white' },
264
+ rose: { bg: 'bg-rose-700', text: 'text-white' },
265
+ primary: { bg: 'bg-primary-custom-600', text: 'text-white' },
266
+ secondary: { bg: 'bg-zinc-600', text: 'text-white' },
267
+ tertiary: { bg: 'bg-zinc-600', text: 'text-white' },
268
+ };
269
+
270
+ const badgeColors = badgeColorClasses[badgeColor] || badgeColorClasses.red;
271
+
272
+ // ===== RENDERIZAR BADGE =====
273
+ const renderBadge = () => {
274
+ // Si no hay badge ni badgeCount, no renderizar nada
275
+ if (!badge && badgeCount === undefined) return null;
276
+
277
+ // Si hay badgeCount, renderizar badge con número
278
+ if (badgeCount !== undefined) {
279
+ return (
280
+ <span
281
+ className={`
282
+ absolute
283
+ -top-1
284
+ -right-1
285
+ flex
286
+ items-center
287
+ justify-center
288
+ min-w-[16px]
289
+ h-4
290
+ px-1
291
+ rounded-full
292
+ text-[10px]
293
+ font-bold
294
+ leading-none
295
+ ${badgeColors.bg}
296
+ ${badgeColors.text}
297
+ pointer-events-none
298
+ `.trim().replace(/\s+/g, ' ')}
299
+ aria-label={`${badgeCount} notificaciones`}
300
+ >
301
+ {badgeCount > 99 ? '99+' : badgeCount}
302
+ </span>
303
+ );
304
+ }
305
+
306
+ // Si solo hay badge (sin número), renderizar dot
307
+ return (
308
+ <span
309
+ className={`
310
+ absolute
311
+ -top-1
312
+ -right-1
313
+ w-2
314
+ h-2
315
+ rounded-full
316
+ ${badgeColors.bg}
317
+ pointer-events-none
318
+ `.trim().replace(/\s+/g, ' ')}
319
+ aria-label="Notificación"
320
+ />
321
+ );
322
+ };
323
+
324
+ return (
325
+ <button
326
+ type={htmlType}
327
+ className={`${buttonClasses} ${(badge || badgeCount !== undefined) ? 'relative' : ''}`}
328
+ disabled={disabled}
329
+ onClick={onClick}
330
+ aria-label={ariaLabel}
331
+ {...rest}
332
+ >
333
+ {renderContent()}
334
+ {renderBadge()}
335
+ </button>
336
+ );
337
+ };