@imj_media/ui 1.10.5 → 1.10.7

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 (71) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -1
  3. package/catalog/design-index.json +225 -22
  4. package/catalog/design-index.schema.json +15 -2
  5. package/dist/index.css +1 -1
  6. package/dist/index.esm.js +1537 -1557
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.js +6 -6
  9. package/dist/index.js.map +1 -1
  10. package/dist/modules/Alert/components/atoms/AlertHeader.d.ts.map +1 -1
  11. package/dist/modules/Button/components/organisms/Button.d.ts.map +1 -1
  12. package/dist/modules/Button/stories/Button.stories.d.ts.map +1 -1
  13. package/dist/modules/Dropdown/components/Dropdown.d.ts.map +1 -1
  14. package/dist/modules/FileUploader/components/organisms/FileUploader.d.ts.map +1 -1
  15. package/dist/modules/Ghantt/Ghantt.d.ts.map +1 -1
  16. package/dist/modules/Ghantt/GhanttTimelineHeader.d.ts.map +1 -1
  17. package/dist/modules/InnerButton/InnerButton.d.ts.map +1 -1
  18. package/dist/modules/InnerButton/stories/InnerButton.stories.d.ts.map +1 -1
  19. package/dist/modules/LegacyButton/InnerLegacyButton.d.ts.map +1 -1
  20. package/dist/modules/Message/Message.api.d.ts +1 -1
  21. package/dist/modules/Message/Message.api.d.ts.map +1 -1
  22. package/dist/modules/Message/Message.types.d.ts +1 -1
  23. package/dist/modules/Message/Message.types.d.ts.map +1 -1
  24. package/dist/modules/Message/components/molecules/NotificationItemStackDeck.d.ts.map +1 -1
  25. package/dist/modules/Message/components/molecules/NotificationPositions.d.ts.map +1 -1
  26. package/dist/modules/Message/components/molecules/notificationItemCardMapping.d.ts.map +1 -1
  27. package/dist/modules/Message/components/molecules/notificationItemStackLayout.d.ts.map +1 -1
  28. package/dist/modules/Message/hooks/useNotificationItemDeck.d.ts.map +1 -1
  29. package/dist/modules/Message/services/notificationServiceNormalizeOptions.d.ts.map +1 -1
  30. package/dist/modules/Message/stories/message.stories.d.ts.map +1 -1
  31. package/dist/modules/Message/stories/notification-storycode.stories.d.ts.map +1 -1
  32. package/dist/modules/Message/stories/notification.stories.d.ts.map +1 -1
  33. package/dist/modules/Modal/Modal.d.ts.map +1 -1
  34. package/dist/modules/Modal/components/molecules/ModalBody.d.ts.map +1 -1
  35. package/dist/modules/Modal/context/ModalContext.d.ts.map +1 -1
  36. package/dist/modules/Modal/stories/Modal.stories.d.ts +13 -0
  37. package/dist/modules/Modal/stories/Modal.stories.d.ts.map +1 -1
  38. package/dist/modules/Notification/components/molecules/NotificationContentBody.d.ts.map +1 -1
  39. package/dist/modules/Notification/index.d.ts.map +1 -1
  40. package/dist/modules/Notification/stories/notification.stories.d.ts.map +1 -1
  41. package/dist/modules/Switch/stories/switch.stories.d.ts.map +1 -1
  42. package/dist/modules/Table/Table.d.ts.map +1 -1
  43. package/dist/modules/Table/components/atoms/GanttCell.d.ts.map +1 -1
  44. package/dist/modules/Table/components/molecules/CellRenderer.d.ts.map +1 -1
  45. package/dist/modules/Table/components/organisms/TableContent.d.ts.map +1 -1
  46. package/dist/modules/Table/components/organisms/TableToolbar.d.ts.map +1 -1
  47. package/dist/modules/Table/hooks/useTableColumns.d.ts.map +1 -1
  48. package/dist/modules/Table/hooks/useTableSelection.d.ts.map +1 -1
  49. package/dist/modules/Table/stories/Table.stories.d.ts.map +1 -1
  50. package/dist/modules/Table/utils/columnLockable.d.ts.map +1 -1
  51. package/dist/modules/Tabs/components/index.d.ts +2 -0
  52. package/dist/modules/Tabs/components/index.d.ts.map +1 -0
  53. package/dist/modules/Tabs/components/organisms/Tabs.d.ts +23 -0
  54. package/dist/modules/Tabs/components/organisms/Tabs.d.ts.map +1 -0
  55. package/dist/modules/Tabs/components/organisms/index.d.ts +2 -0
  56. package/dist/modules/Tabs/components/organisms/index.d.ts.map +1 -0
  57. package/dist/modules/Tabs/hooks/index.d.ts +2 -0
  58. package/dist/modules/Tabs/hooks/index.d.ts.map +1 -0
  59. package/dist/modules/Tabs/hooks/useTabs.d.ts +24 -0
  60. package/dist/modules/Tabs/hooks/useTabs.d.ts.map +1 -0
  61. package/dist/modules/Tabs/index.d.ts +4 -0
  62. package/dist/modules/Tabs/index.d.ts.map +1 -0
  63. package/dist/modules/Tabs/stories/tabs.stories.d.ts +20 -0
  64. package/dist/modules/Tabs/stories/tabs.stories.d.ts.map +1 -0
  65. package/dist/modules/Tabs/types/index.d.ts +73 -0
  66. package/dist/modules/Tabs/types/index.d.ts.map +1 -0
  67. package/dist/modules/TextListWithData/components/organisms/TextListWithData.d.ts.map +1 -1
  68. package/dist/shared/ghantt/GhanttSlotStrip.d.ts.map +1 -1
  69. package/dist/shared/types/modal.d.ts +10 -1
  70. package/dist/shared/types/modal.d.ts.map +1 -1
  71. package/package.json +6 -5
package/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/lang/es/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.10.7] - 2026-05-26
11
+
12
+ ### Added
13
+
14
+ - **`Modal`:** nueva prop `scrollMode` (`'paper'` | `'scroll'`). El modo `paper` (por defecto) fija el panel a la altura del viewport y delega el scroll al `Modal.Body`; el modo `scroll` mantiene el comportamiento clásico de scroll externo en el overlay.
15
+ - **Catálogo:** templates de uso y recetas de composición por componente en `design-index.json`.
16
+
17
+ ## [1.10.6] - 2026-05-25
18
+
19
+ ### Added
20
+
21
+ - **`Tabs`:** nuevo módulo composable con componente `Tabs`, hook `useTabs`, tipos y stories.
22
+
23
+ ### Changed
24
+
25
+ - **ESLint / Prettier:** reglas de formato (comillas, semicolons, trailing commas, indentación, espaciado) delegadas a Prettier vía `eslint-config-prettier`; ESLint ya no reporta conflictos de formato.
26
+ - **Formato global:** Prettier aplicado en todos los módulos (trailing commas en args, indentación, orden de clases Tailwind vía `prettier-plugin-tailwindcss`).
27
+
10
28
  ## [1.10.5] - 2026-05-25
11
29
 
12
30
  ### Fixed
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Biblioteca de componentes UI moderna y accesible para React, construida con TypeScript y Tailwind CSS.
6
6
 
7
- > **Versión publicada:** `1.10.5` — actualizar este número y `CHANGELOG.md` antes de cada release (orden y scripts: regla **`imj-ui-obligations-release`**; atajo `npm run publish:patch|minor|major` o pasos `bump:*` → `release:git` → `release:publish`).
7
+ > **Versión publicada:** `1.10.7` — actualizar este número y `CHANGELOG.md` antes de cada release (orden y scripts: regla **`imj-ui-obligations-release`**; atajo `npm run publish:patch|minor|major` o pasos `bump:*` → `release:git` → `release:publish`).
8
8
 
9
9
  ## 📦 Instalación
10
10
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
3
  "package": "@imj_media/ui",
4
- "version": "1.10.5",
5
- "indexedAt": "2026-05-25T20:20:35.330Z",
4
+ "version": "1.10.7",
5
+ "indexedAt": "2026-05-26T18:28:52.846Z",
6
6
  "orbitTokensVersion": "1.3.1",
7
7
  "modules": [
8
8
  {
@@ -44,7 +44,8 @@
44
44
  "Accordion.stories.tsx:Default"
45
45
  ],
46
46
  "snippet": "<Accordion\n id=\"section-1\"\n title=\"Sección\"\n subtitle=\"Opcional\"\n defaultOpen={false}\n>\n Contenido colapsable\n</Accordion>"
47
- }
47
+ },
48
+ "standaloneSnippet": "import React, { useState } from 'react';\n\nexport function Accordion({\n items,\n defaultOpen,\n}: {\n items: { id: string; title: string; content: React.ReactNode }[];\n defaultOpen?: string;\n}) {\n const [openId, setOpenId] = useState<string | undefined>(defaultOpen);\n\n const toggle = (id: string) => {\n setOpenId((prev) => (prev === id ? undefined : id));\n };\n\n return (\n <div\n style={{\n border: '1px solid rgb(199, 199, 204)',\n borderRadius: '8px',\n overflow: 'hidden',\n }}\n >\n {items.map((item, index) => {\n const isOpen = openId === item.id;\n return (\n <div key={item.id}>\n {index > 0 && (\n <div style={{ height: '1px', backgroundColor: 'rgb(199, 199, 204)' }} />\n )}\n <button\n onClick={() => toggle(item.id)}\n style={{\n width: '100%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 4px',\n backgroundColor: 'rgb(255, 255, 255)',\n border: 'none',\n cursor: 'pointer',\n fontSize: '16px',\n fontWeight: '500',\n color: 'rgb(48, 51, 54)',\n fontFamily: 'inherit',\n textAlign: 'left',\n }}\n >\n <span>{item.title}</span>\n <span\n style={{\n transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',\n transition: 'transform 200ms',\n fontSize: '12px',\n color: 'rgb(89, 89, 94)',\n }}\n >\n ▼\n </span>\n </button>\n <div\n style={{\n display: isOpen ? 'block' : 'none',\n padding: '12px 4px',\n backgroundColor: 'rgb(247, 247, 250)',\n color: 'rgb(48, 51, 54)',\n fontSize: '14px',\n lineHeight: '1.5',\n }}\n >\n {item.content}\n </div>\n </div>\n );\n })}\n </div>\n );\n}\n"
48
49
  },
49
50
  {
50
51
  "id": "Alert",
@@ -69,7 +70,8 @@
69
70
  "<Alert color=\"success\" variant=\"contained\" message=\"Variante contained (por defecto)\" title=\"Contained\" open={true} />\n <Alert color=\"info\" variant=\"contained\" message=\"Variante contained (por defecto)\" title=\"Contained\" open={true} />\n <Alert color=\"warning\" variant=\"contained\" message=\"Variante contained (por defecto)\" title=\"Contained\" open={true} />\n <Alert color=\"danger\" variant=\"contained\" message=\"Variante contained (por defecto)\" title=\"Contained\" open={true} />\n <Alert color=\"success\" variant=\"outlined\" message=\"Variante outlined con borde\" title=\"Outlined\" open={true} />\n <Alert color=\"info\" variant=\"outlined\" message=\"Variante outlined con borde\" title=\"Outlined\" open={true} />\n <Alert color=\"warning\" variant=\"outlined\" message=\"Variante outlined con borde\" title=\"Outlined\" open={true} />\n <Alert color=\"danger\" variant=\"outlined\" message=\"Variante outlined con borde\" title=\"Outlined\" open={true} />"
70
71
  ]
71
72
  }
72
- ]
73
+ ],
74
+ "standaloneSnippet": "import React, { useState } from 'react';\n\nconst variantMap = {\n info: { border: 'rgb(5, 181, 212)', bg: 'rgba(0, 0, 0, 0.08)', text: 'rgb(5, 181, 212)' },\n success: { border: 'rgb(33, 196, 94)', bg: 'rgba(0, 0, 0, 0.08)', text: 'rgb(33, 196, 94)' },\n warning: { border: 'rgb(235, 179, 8)', bg: 'rgba(0, 0, 0, 0.08)', text: 'rgb(235, 179, 8)' },\n error: { border: 'rgb(240, 69, 69)', bg: 'rgba(0, 0, 0, 0.08)', text: 'rgb(240, 69, 69)' },\n};\n\nexport function Alert({\n children,\n variant = 'info',\n title,\n closable = false,\n onClose,\n}: {\n children: React.ReactNode;\n variant?: keyof typeof variantMap;\n title?: string;\n closable?: boolean;\n onClose?: () => void;\n}) {\n const [visible, setVisible] = useState(true);\n if (!visible) return null;\n\n const v = variantMap[variant];\n\n const handleClose = () => {\n setVisible(false);\n onClose?.();\n };\n\n return (\n <div\n role=\"alert\"\n style={{\n position: 'relative',\n padding: '12px 4px',\n borderLeft: `4px solid ${v.border}`,\n backgroundColor: v.bg,\n borderRadius: '8px',\n color: 'rgb(48, 51, 54)',\n fontSize: '14px',\n lineHeight: '1.5',\n }}\n >\n {closable && (\n <button\n onClick={handleClose}\n aria-label=\"Close\"\n style={{\n position: 'absolute',\n top: '2px',\n right: '2px',\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n fontSize: '16px',\n color: 'rgb(89, 89, 94)',\n padding: '4px',\n lineHeight: '1',\n }}\n >\n ×\n </button>\n )}\n {title && (\n <div style={{ fontWeight: '700', marginBottom: '4px', color: v.text }}>\n {title}\n </div>\n )}\n <div>{children}</div>\n </div>\n );\n}\n"
73
75
  },
74
76
  {
75
77
  "id": "AlertDialog",
@@ -113,7 +115,8 @@
113
115
  },
114
116
  "examples": []
115
117
  }
116
- ]
118
+ ],
119
+ "standaloneSnippet": "import React, { useState } from 'react';\n\nconst sizeMap = {\n sm: { dimension: '32px', fontSize: '12px' },\n md: { dimension: '40px', fontSize: '14px' },\n lg: { dimension: '56px', fontSize: '18px' },\n xl: { dimension: '80px', fontSize: '20px' },\n};\n\nexport function Avatar({\n src,\n alt = '',\n size = 'md',\n fallback,\n}: {\n src?: string;\n alt?: string;\n size?: keyof typeof sizeMap;\n fallback?: string;\n}) {\n const [imgError, setImgError] = useState(false);\n const s = sizeMap[size];\n const showImage = src && !imgError;\n\n const baseStyle: React.CSSProperties = {\n width: s.dimension,\n height: s.dimension,\n borderRadius: '9999px',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n overflow: 'hidden',\n flexShrink: 0,\n };\n\n if (showImage) {\n return (\n <img\n src={src}\n alt={alt}\n onError={() => setImgError(true)}\n style={{\n ...baseStyle,\n objectFit: 'cover',\n }}\n />\n );\n }\n\n return (\n <div\n style={{\n ...baseStyle,\n backgroundColor: 'rgb(247, 247, 250)',\n color: 'rgb(48, 51, 54)',\n fontSize: s.fontSize,\n fontWeight: '500',\n userSelect: 'none',\n }}\n >\n {fallback || '?'}\n </div>\n );\n}\n"
117
120
  },
118
121
  {
119
122
  "id": "Badge",
@@ -139,7 +142,8 @@
139
142
  "import { Badge } from '@/modules/Badge';\n\nexport default function Example() {\n return (\n <>\n <Badge appearance={{ size: 'sm', color: 'accent' }} text={{ label: '5' }} />\n <Badge appearance={{ size: 'sm', color: 'gray' }} text={{ label: '5' }} />\n <Badge appearance={{ size: 'sm', color: 'success' }} text={{ label: '5' }} />\n <Badge appearance={{ size: 'sm', color: 'warning' }} text={{ label: '5' }} />\n <Badge appearance={{ size: 'sm', color: 'danger' }} text={{ label: '5' }} />\n <Badge appearance={{ size: 'sm', color: 'info' }} text={{ label: '5' }} />\n <Badge appearance={{ size: 'sm' }} text={{ label: '5' }} state={{ disabled: true }} />\n </>\n )\n}"
140
143
  ]
141
144
  }
142
- ]
145
+ ],
146
+ "standaloneSnippet": "import React from 'react';\n\nconst colorMap = {\n primary: { solid: 'rgb(54, 89, 194)', text: 'rgb(54, 89, 194)' },\n secondary: { solid: 'rgb(240, 69, 69)', text: 'rgb(240, 69, 69)' },\n tertiary: { solid: 'rgb(33, 196, 94)', text: 'rgb(33, 196, 94)' },\n destructive: { solid: 'rgb(235, 179, 8)', text: 'rgb(235, 179, 8)' },\n neutral: { solid: 'rgb(89, 89, 94)', text: 'rgb(89, 89, 94)' },\n};\n\nconst sizeMap = {\n sm: { padding: '2px 6px', fontSize: '12px' },\n md: { padding: '4px 2px', fontSize: '12px' },\n lg: { padding: '4px 12px', fontSize: '14px' },\n};\n\nfunction hexToRgba(hex: string, alpha: number): string {\n const h = hex.replace('#', '');\n const n = parseInt(h, 16);\n return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;\n}\n\nexport function Badge({\n children,\n color = 'primary',\n size = 'md',\n theme = 'soft',\n}: {\n children: React.ReactNode;\n color?: keyof typeof colorMap;\n size?: keyof typeof sizeMap;\n theme?: 'soft' | 'solid' | 'outlined';\n}) {\n const c = colorMap[color];\n const s = sizeMap[size];\n\n const styles: Record<string, string | number> = {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: s.padding,\n fontSize: s.fontSize,\n fontWeight: '500',\n lineHeight: '1.25',\n borderRadius: '9999px',\n whiteSpace: 'nowrap',\n };\n\n if (theme === 'solid') {\n styles.backgroundColor = c.solid;\n styles.color = 'rgb(255, 255, 255)';\n styles.border = 'none';\n } else if (theme === 'outlined') {\n styles.backgroundColor = 'transparent';\n styles.color = c.text;\n styles.border = `1px solid ${c.solid}`;\n } else {\n styles.backgroundColor = hexToRgba(c.solid, 0.1);\n styles.color = c.text;\n styles.border = 'none';\n }\n\n return <span style={styles}>{children}</span>;\n}\n"
143
147
  },
144
148
  {
145
149
  "id": "Badges",
@@ -213,7 +217,8 @@
213
217
  "import { Button } from '@imj_media/ui';\n\n export default function Example() {\n return (\n <>\n <Button color=\"primary\" tooltip=\"Tooltip\">Blue</Button>\n <Button color=\"secondary\" tooltip=\"Tooltip\">Red</Button>\n <Button color=\"tertiary\" tooltip=\"Tooltip\">Green</Button>\n <Button color=\"destructive\" tooltip=\"Tooltip\">Orange</Button>\n </>\n )\n }"
214
218
  ]
215
219
  }
216
- ]
220
+ ],
221
+ "standaloneSnippet": "import React from 'react';\n\nconst colorMap = {\n primary: {\n solid: { bg: 'rgb(54, 89, 194)', text: 'rgb(255, 255, 255)', hover: 'rgb(51, 82, 176)', pressed: 'rgb(46, 74, 161)', border: 'transparent' },\n outlined: { bg: 'transparent', text: 'rgb(54, 89, 194)', hover: 'rgb(51, 82, 176)', pressed: 'rgb(46, 74, 161)', border: 'rgb(54, 89, 194)' },\n text: { bg: 'transparent', text: 'rgb(54, 89, 194)', hover: 'rgb(51, 82, 176)', pressed: 'rgb(46, 74, 161)', border: 'transparent' },\n ghost: { bg: 'transparent', text: 'rgb(54, 89, 194)', hover: 'rgba(0,0,0,0.04)', pressed: 'rgba(0,0,0,0.08)', border: 'transparent' },\n },\n secondary: {\n solid: { bg: 'rgb(240, 69, 69)', text: 'rgb(255, 255, 255)', hover: 'rgb(212, 61, 61)', pressed: 'rgb(184, 51, 51)', border: 'transparent' },\n outlined: { bg: 'transparent', text: 'rgb(240, 69, 69)', hover: 'rgb(212, 61, 61)', pressed: 'rgb(184, 51, 51)', border: 'rgb(240, 69, 69)' },\n text: { bg: 'transparent', text: 'rgb(240, 69, 69)', hover: 'rgb(212, 61, 61)', pressed: 'rgb(184, 51, 51)', border: 'transparent' },\n ghost: { bg: 'transparent', text: 'rgb(240, 69, 69)', hover: 'rgba(0,0,0,0.04)', pressed: 'rgba(0,0,0,0.08)', border: 'transparent' },\n },\n tertiary: {\n solid: { bg: 'rgb(33, 196, 94)', text: 'rgb(255, 255, 255)', hover: 'rgb(31, 181, 87)', pressed: 'rgb(28, 163, 79)', border: 'transparent' },\n outlined: { bg: 'transparent', text: 'rgb(33, 196, 94)', hover: 'rgb(31, 181, 87)', pressed: 'rgb(28, 163, 79)', border: 'rgb(33, 196, 94)' },\n text: { bg: 'transparent', text: 'rgb(33, 196, 94)', hover: 'rgb(31, 181, 87)', pressed: 'rgb(28, 163, 79)', border: 'transparent' },\n ghost: { bg: 'transparent', text: 'rgb(33, 196, 94)', hover: 'rgba(0,0,0,0.04)', pressed: 'rgba(0,0,0,0.08)', border: 'transparent' },\n },\n destructive: {\n solid: { bg: 'rgb(235, 179, 8)', text: 'rgb(255, 255, 255)', hover: 'rgb(209, 158, 8)', pressed: 'rgb(181, 140, 5)', border: 'transparent' },\n outlined: { bg: 'transparent', text: 'rgb(235, 179, 8)', hover: 'rgb(209, 158, 8)', pressed: 'rgb(181, 140, 5)', border: 'rgb(235, 179, 8)' },\n text: { bg: 'transparent', text: 'rgb(235, 179, 8)', hover: 'rgb(209, 158, 8)', pressed: 'rgb(181, 140, 5)', border: 'transparent' },\n ghost: { bg: 'transparent', text: 'rgb(235, 179, 8)', hover: 'rgba(0,0,0,0.04)', pressed: 'rgba(0,0,0,0.08)', border: 'transparent' },\n },\n};\n\nconst sizeMap = {\n xs: { padding: '4px 2px', fontSize: '12px' },\n sm: { padding: '2px 12px', fontSize: '14px' },\n md: { padding: '10px 4px', fontSize: '16px' },\n lg: { padding: '12px 6px', fontSize: '18px' },\n};\n\nexport function Button({\n children,\n color = 'primary',\n size = 'sm',\n theme = 'solid',\n disabled = false,\n fullWidth = false,\n rounded = false,\n onClick,\n}: {\n children: React.ReactNode;\n color?: keyof typeof colorMap;\n size?: keyof typeof sizeMap;\n theme?: 'solid' | 'outlined' | 'text' | 'ghost';\n disabled?: boolean;\n fullWidth?: boolean;\n rounded?: boolean;\n onClick?: () => void;\n}) {\n const c = colorMap[color][theme];\n const s = sizeMap[size];\n const [hovered, setHovered] = React.useState(false);\n const [pressed, setPressed] = React.useState(false);\n\n const bgColor = disabled ? c.bg : pressed ? (theme === 'solid' ? c.pressed : 'rgba(0,0,0,0.06)') : hovered ? (theme === 'solid' ? c.hover : 'rgba(0,0,0,0.03)') : c.bg;\n const textColor = disabled ? c.text : hovered && theme !== 'solid' ? c.hover : c.text;\n\n return (\n <button\n onClick={disabled ? undefined : onClick}\n disabled={disabled}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => { setHovered(false); setPressed(false); }}\n onMouseDown={() => setPressed(true)}\n onMouseUp={() => setPressed(false)}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: s.padding,\n fontSize: s.fontSize,\n fontWeight: '600',\n fontFamily: 'inherit',\n lineHeight: '1.25',\n borderRadius: rounded ? '9999px' : '8px',\n border: theme === 'outlined' ? `1px solid ${c.border}` : 'none',\n backgroundColor: bgColor,\n color: textColor,\n cursor: disabled ? 'not-allowed' : 'pointer',\n opacity: disabled ? 0.5 : 1,\n width: fullWidth ? '100%' : 'auto',\n transition: 'background-color 150ms, color 150ms',\n boxSizing: 'border-box',\n }}\n >\n {children}\n </button>\n );\n}\n"
217
222
  },
218
223
  {
219
224
  "id": "ButtonGroup",
@@ -396,7 +401,7 @@
396
401
  "description": {
397
402
  "primary": "storybook",
398
403
  "storybook": "El componente Drawer permite mostrar contenido en un panel lateral deslizable con diferentes tamaños, posiciones y configuraciones de botones.",
399
- "jsdoc": "DrawerFooter - Subcomponente de footer para Drawer\n@internal\n/\nconst DrawerFooter = ({ children, className }: DrawerFooterProps) => {\n return (\n <Card.Footer\n className={cn('ui-flex ui-items-center ui-justify-end ui-gap-x-16 ui-p-16', className)}\n >\n {children}\n </Card.Footer>\n );\n};\n\n// Nombre para identificar el componente\nDrawerFooter.displayName = 'DrawerFooter';\n\n// Función auxiliar para extraer el footer de los children\nconst extractFooterFromChildren = (\n children: ReactNode,\n): { content: ReactNode[]; footer: ReactElement | null } => {\n const content: ReactNode[] = [];\n let footer: ReactElement | null = null;\n\n Children.forEach(children, (child) => {\n if (\n isValidElement(child) &&\n (child.type as { displayName?: string })?.displayName === 'Dra",
404
+ "jsdoc": "DrawerFooter - Subcomponente de footer para Drawer\n@internal\n/\nconst DrawerFooter = ({ children, className }: DrawerFooterProps) => {\n return (\n <Card.Footer\n className={cn('ui-flex ui-items-center ui-justify-end ui-gap-x-16 ui-p-16', className)}\n >\n {children}\n </Card.Footer>\n );\n};\n\n// Nombre para identificar el componente\nDrawerFooter.displayName = 'DrawerFooter';\n\n// Función auxiliar para extraer el footer de los children\nconst extractFooterFromChildren = (\n children: ReactNode\n): { content: ReactNode[]; footer: ReactElement | null } => {\n const content: ReactNode[] = [];\n let footer: ReactElement | null = null;\n\n Children.forEach(children, (child) => {\n if (\n isValidElement(child) &&\n (child.type as { displayName?: string })?.displayName === 'Draw",
400
405
  "confidence": "high"
401
406
  },
402
407
  "examples": [
@@ -821,7 +826,7 @@
821
826
  "description": {
822
827
  "primary": "storybook",
823
828
  "storybook": "Tabla estándar con una columna Gantt. La API voluminosa va en el objeto",
824
- "jsdoc": "X del puntero respecto al borde izquierdo del **mismo** elemento cuyo `scrollLeft` define el desplazamiento\n(viewport de cabecera). Si se mide con el padre del track del cuerpo, thead/tbody pueden desalinearse\n(p. ej. scrollbar vertical solo en el cuerpo) y `scrollLeft + x` cae en el inicio del contenido → anclas en ~2015.\n/\nfunction ghanttWheelTimelineViewportClientX(\n scrollViewportEl: HTMLElement | null,\n clientX: number,\n): number {\n const el = scrollViewportEl;\n if (!el) return 0;\n const r = el.getBoundingClientRect();\n const w = el.clientWidth > 0 ? el.clientWidth : r.width;\n return Math.max(0, Math.min(w, clientX - r.left));\n}\n\nconst DEFAULT_APPEARANCE = {\n trackMinHeightPx: 44,\n} as const;\n\nconst DEFAULT_EVENTS = {} as const;\n\n/**\nTabla estándar {@link Table} con **una colu",
829
+ "jsdoc": "X del puntero respecto al borde izquierdo del **mismo** elemento cuyo `scrollLeft` define el desplazamiento\n(viewport de cabecera). Si se mide con el padre del track del cuerpo, thead/tbody pueden desalinearse\n(p. ej. scrollbar vertical solo en el cuerpo) y `scrollLeft + x` cae en el inicio del contenido → anclas en ~2015.\n/\nfunction ghanttWheelTimelineViewportClientX(\n scrollViewportEl: HTMLElement | null,\n clientX: number\n): number {\n const el = scrollViewportEl;\n if (!el) return 0;\n const r = el.getBoundingClientRect();\n const w = el.clientWidth > 0 ? el.clientWidth : r.width;\n return Math.max(0, Math.min(w, clientX - r.left));\n}\n\nconst DEFAULT_APPEARANCE = {\n trackMinHeightPx: 44,\n} as const;\n\nconst DEFAULT_EVENTS = {} as const;\n\n/**\nTabla estándar {@link Table} con **una colum",
825
830
  "confidence": "high"
826
831
  },
827
832
  "examples": [
@@ -1128,7 +1133,8 @@
1128
1133
  "<Input \n label=\"Input Deshabilitado\"\n disabled={true}\n value=\"Valor no editable\"\n />"
1129
1134
  ]
1130
1135
  }
1131
- ]
1136
+ ],
1137
+ "standaloneSnippet": "import React from 'react';\n\nconst sizeMap = {\n sm: { padding: '6px 2px', fontSize: '14px' },\n md: { padding: '2px 12px', fontSize: '16px' },\n lg: { padding: '12px 4px', fontSize: '18px' },\n};\n\nexport function Input({\n value,\n onChange,\n placeholder,\n disabled = false,\n size = 'md',\n error,\n type = 'text',\n}: {\n value?: string;\n onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;\n placeholder?: string;\n disabled?: boolean;\n size?: keyof typeof sizeMap;\n error?: string;\n type?: 'text' | 'password' | 'email' | 'number';\n}) {\n const s = sizeMap[size];\n const [focused, setFocused] = React.useState(false);\n\n const borderColor = error\n ? 'rgb(240, 69, 69)'\n : focused\n ? 'rgb(54, 89, 194)'\n : 'rgb(199, 199, 204)';\n\n const input = (\n <input\n type={type}\n value={value}\n onChange={onChange}\n placeholder={placeholder}\n disabled={disabled}\n onFocus={() => setFocused(true)}\n onBlur={() => setFocused(false)}\n style={{\n width: '100%',\n padding: s.padding,\n fontSize: s.fontSize,\n fontFamily: 'inherit',\n lineHeight: '1.5',\n color: 'rgb(48, 51, 54)',\n backgroundColor: 'rgb(255, 255, 255)',\n border: `1px solid ${borderColor}`,\n borderRadius: '8px',\n outline: 'none',\n opacity: disabled ? 0.5 : 1,\n cursor: disabled ? 'not-allowed' : 'text',\n transition: 'border-color 150ms',\n boxSizing: 'border-box',\n }}\n />\n );\n\n if (error) {\n return (\n <div>\n {input}\n <div\n style={{\n marginTop: '4px',\n fontSize: '12px',\n color: 'rgb(240, 69, 69)',\n }}\n >\n {error}\n </div>\n </div>\n );\n }\n\n return input;\n}\n"
1132
1138
  },
1133
1139
  {
1134
1140
  "id": "LegacyButton",
@@ -1436,7 +1442,8 @@
1436
1442
  "Modal.stories.tsx:WithTabs"
1437
1443
  ],
1438
1444
  "snippet": "<Modal isOpen={open} onClose={() => setOpen(false)} title=\"Título\">\n <Modal.Body>Contenido</Modal.Body>\n <Modal.Footer>\n <Button onClick={() => setOpen(false)}>Cerrar</Button>\n </Modal.Footer>\n</Modal>"
1439
- }
1445
+ },
1446
+ "standaloneSnippet": "import React from 'react';\n\nconst sizeMap = {\n sm: '400px',\n md: '560px',\n lg: '720px',\n};\n\nexport function Modal({\n isOpen,\n onClose,\n title,\n children,\n size = 'md',\n}: {\n isOpen: boolean;\n onClose: () => void;\n title?: string;\n children: React.ReactNode;\n size?: keyof typeof sizeMap;\n}) {\n if (!isOpen) return null;\n\n const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {\n if (e.target === e.currentTarget) onClose();\n };\n\n return (\n <div\n onClick={handleOverlayClick}\n style={{\n position: 'fixed',\n inset: 0,\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 1000,\n }}\n >\n <div\n style={{\n backgroundColor: 'rgb(255, 255, 255)',\n borderRadius: '10px',\n boxShadow: '0 16px 24px -8px rgba(0, 0, 0, 0.3)',\n width: '100%',\n maxWidth: sizeMap[size],\n maxHeight: '85vh',\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '4px 6px',\n borderBottom: `1px solid ${'rgb(199, 199, 204)'}`,\n }}\n >\n {title && (\n <h2\n style={{\n margin: 0,\n fontSize: '18px',\n fontWeight: '600',\n color: 'rgb(48, 51, 54)',\n }}\n >\n {title}\n </h2>\n )}\n <button\n onClick={onClose}\n aria-label=\"Close\"\n style={{\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n fontSize: '20px',\n color: 'rgb(89, 89, 94)',\n padding: '4px',\n lineHeight: '1',\n marginLeft: 'auto',\n }}\n >\n ×\n </button>\n </div>\n <div\n style={{\n padding: '6px',\n overflowY: 'auto',\n color: 'rgb(48, 51, 54)',\n fontSize: '16px',\n lineHeight: '1.5',\n }}\n >\n {children}\n </div>\n </div>\n </div>\n );\n}\n"
1440
1447
  },
1441
1448
  {
1442
1449
  "id": "Notification",
@@ -1461,7 +1468,8 @@
1461
1468
  "import { Notification } from '@imj_media/ui';\n\nexport default function Example() {\n return (\n <Notification\n open\n content={{\n title: 'Notification Title',\n subtitle: 'Subtitle',\n description:\n 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet.',\n }}\n leading={{ source: 'general' }}\n appearance={{ intent: 'preventiva', showCloseButton: true }}\n meta={{ presentation: 'popup', stackSummary: '5 notificaciones más' }}\n events={{}}\n />\n );\n}"
1462
1469
  ]
1463
1470
  }
1464
- ]
1471
+ ],
1472
+ "standaloneSnippet": "import React, { useState } from 'react';\n\nconst variantMap: Record<string, { color: string; icon: string }> = {\n info: { color: 'rgb(5, 181, 212)', icon: 'ℹ' },\n success: { color: 'rgb(33, 196, 94)', icon: '✓' },\n warning: { color: 'rgb(235, 179, 8)', icon: '⚠' },\n error: { color: 'rgb(240, 69, 69)', icon: '✕' },\n};\n\ninterface NotificationProps {\n title: string;\n message?: string;\n variant?: 'info' | 'success' | 'warning' | 'error';\n closable?: boolean;\n onClose?: () => void;\n}\n\nexport function Notification({\n title,\n message,\n variant = 'info',\n closable = false,\n onClose,\n}: NotificationProps) {\n const [visible, setVisible] = useState(true);\n const v = variantMap[variant];\n\n if (!visible) return null;\n\n const handleClose = () => {\n setVisible(false);\n onClose?.();\n };\n\n return (\n <div\n role=\"alert\"\n style={{\n display: 'flex',\n alignItems: 'flex-start',\n gap: '12px',\n padding: '4px',\n backgroundColor: 'rgb(255, 255, 255)',\n borderRadius: '8px',\n borderLeft: `4px solid ${v.color}`,\n boxShadow: '0 4px 8px -2px rgba(0, 0, 0, 0.2)',\n }}\n >\n <span\n style={{\n flexShrink: 0,\n width: 24,\n height: 24,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: '16px',\n color: v.color,\n }}\n >\n {v.icon}\n </span>\n <div style={{ flex: 1, minWidth: 0 }}>\n <div\n style={{\n fontWeight: '600',\n fontSize: '14px',\n color: 'rgb(48, 51, 54)',\n marginBottom: message ? '4px' : 0,\n }}\n >\n {title}\n </div>\n {message && (\n <div\n style={{\n fontSize: '14px',\n color: 'rgb(89, 89, 94)',\n }}\n >\n {message}\n </div>\n )}\n </div>\n {closable && (\n <button\n type=\"button\"\n onClick={handleClose}\n aria-label=\"Close notification\"\n style={{\n flexShrink: 0,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'rgb(89, 89, 94)',\n fontSize: '16px',\n padding: '4px',\n lineHeight: 1,\n }}\n >\n \\u00d7\n </button>\n )}\n </div>\n );\n}\n"
1465
1473
  },
1466
1474
  {
1467
1475
  "id": "Pagination",
@@ -1659,7 +1667,7 @@
1659
1667
  "kind": "component",
1660
1668
  "description": {
1661
1669
  "primary": "jsdoc",
1662
- "jsdoc": "Acota el puntero al rectángulo del trigger en coordenadas de viewport.\nDevuelve la esquina superior izquierda de un ancla 1×1 lógica dentro del trigger\n(recorrido píxel a píxel cuando el trigger tiene tamaño entero en CSS px).\n/\nexport function clampClientPointForPopupAnchor(\n triggerRect: DOMRectReadOnly,\n clientX: number,\n clientY: number,\n): { x: number; y: number } {\n const rw = Math.max(triggerRect.width, 0);\n const rh = Math.max(triggerRect.height, 0);\n const insetX = rw > 0 ? Math.min(1, rw) : 0;\n const insetY = rh > 0 ? Math.min(1, rh) : 0;\n const maxX = rw > 0 ? triggerRect.right - insetX : triggerRect.left;\n const maxY = rh > 0 ? triggerRect.bottom - insetY : triggerRect.top;\n const x = Math.min(Math.max(clientX, triggerRect.left), maxX);\n const y = Math.min(Math.max(c",
1670
+ "jsdoc": "Acota el puntero al rectángulo del trigger en coordenadas de viewport.\nDevuelve la esquina superior izquierda de un ancla 1×1 lógica dentro del trigger\n(recorrido píxel a píxel cuando el trigger tiene tamaño entero en CSS px).\n/\nexport function clampClientPointForPopupAnchor(\n triggerRect: DOMRectReadOnly,\n clientX: number,\n clientY: number\n): { x: number; y: number } {\n const rw = Math.max(triggerRect.width, 0);\n const rh = Math.max(triggerRect.height, 0);\n const insetX = rw > 0 ? Math.min(1, rw) : 0;\n const insetY = rh > 0 ? Math.min(1, rh) : 0;\n const maxX = rw > 0 ? triggerRect.right - insetX : triggerRect.left;\n const maxY = rh > 0 ? triggerRect.bottom - insetY : triggerRect.top;\n const x = Math.min(Math.max(clientX, triggerRect.left), maxX);\n const y = Math.min(Math.max(cl",
1663
1671
  "readme": "# Popup Component\n\nComponente de popup/dropdown posicionable y configurable que se posiciona relativamente a un trigger, con soporte para múltiples posiciones, cierre automático, tooltips e iconos.\n\n## Estructura\n\n```\nPopup/\n├── components/\n│ └── organisms/\n│ └── Popup.tsx # Componente principal\n├── hooks/ # Hooks personalizados\n├── stories/\n│ └── Popup.stories.tsx # Storybook stories\n└── index.ts # Export del módulo\n```\n\n## Uso Básico\n\n```tsx\nimport { Popup } from '@imj_media/ui';\nimport { useRef } from 'react';\n\n// Popup simple\nconst popupRef = useRef<PopupRef>(null);\n\n<Popup ref={popupRef} label=\"Abrir popup\" position=\"bottom-right\">\n <div>Contenido del popup</div>\n</Popup>;\n```\n\n## Props Principales\n\n### Control del Popup\n\n- `label?: string` - Texto del botón trigger\n- `position?: PopupPosition` - Posición del popup relativa al trigger (default: `'bottom-right'`)\n- `offset?: number` - Distancia en píxeles entre el trigger y el popup (default: `6`)\n- `popupId?: string` - ID único del popup (auto-generado si no se proporciona)\n\n### Comportamiento\n\n- `closeOnClickOutside?: boolean` - Cerrar al hacer clic fuera (default: `true`)\n- `closeOnEscape?: boolean` - Cerrar con tecla Escape (default: `true`)\n- `closeOnClick?: boolean` - Cerrar al hacer clic en el trigger (default: `true`)\n- `disabled?: boolean` - Deshabilitar el popup (default: `false`)\n\n### Estilo del Trigger\n\n- `theme?: 'solid' | 'outlined' | 'text' | 'ghost'` - Tema del botón trigger (default: `'solid'`)\n- `color?: ButtonColors` - Color del botón trigger (default: `'tertiary'`)\n- `size?: 'xs' | 'sm' | 'md' | 'lg'` - Tamaño del botón trigger (default: `'xs'`)\n- `rounded?: boolean` - Bordes redondeados (default: `false`)\n- `pill?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'` - Radio de borde (default: `'xs'`)\n- `borderRadius?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'` - Radio de borde del popup (default: `'sm'`)\n\n### Slots e Iconos\n\n- `icon?: I",
1664
1672
  "confidence": "medium"
1665
1673
  },
@@ -1676,7 +1684,7 @@
1676
1684
  "kind": "component",
1677
1685
  "description": {
1678
1686
  "primary": "jsdoc",
1679
- "jsdoc": "Acota el puntero al rectángulo del trigger en coordenadas de viewport.\nDevuelve la esquina superior izquierda de un ancla 1×1 lógica dentro del trigger\n(recorrido píxel a píxel cuando el trigger tiene tamaño entero en CSS px).\n/\nexport function clampClientPointForPopupAnchor(\n triggerRect: DOMRectReadOnly,\n clientX: number,\n clientY: number,\n): { x: number; y: number } {\n const rw = Math.max(triggerRect.width, 0);\n const rh = Math.max(triggerRect.height, 0);\n const insetX = rw > 0 ? Math.min(1, rw) : 0;\n const insetY = rh > 0 ? Math.min(1, rh) : 0;\n const maxX = rw > 0 ? triggerRect.right - insetX : triggerRect.left;\n const maxY = rh > 0 ? triggerRect.bottom - insetY : triggerRect.top;\n const x = Math.min(Math.max(clientX, triggerRect.left), maxX);\n const y = Math.min(Math.max(c",
1687
+ "jsdoc": "Acota el puntero al rectángulo del trigger en coordenadas de viewport.\nDevuelve la esquina superior izquierda de un ancla 1×1 lógica dentro del trigger\n(recorrido píxel a píxel cuando el trigger tiene tamaño entero en CSS px).\n/\nexport function clampClientPointForPopupAnchor(\n triggerRect: DOMRectReadOnly,\n clientX: number,\n clientY: number\n): { x: number; y: number } {\n const rw = Math.max(triggerRect.width, 0);\n const rh = Math.max(triggerRect.height, 0);\n const insetX = rw > 0 ? Math.min(1, rw) : 0;\n const insetY = rh > 0 ? Math.min(1, rh) : 0;\n const maxX = rw > 0 ? triggerRect.right - insetX : triggerRect.left;\n const maxY = rh > 0 ? triggerRect.bottom - insetY : triggerRect.top;\n const x = Math.min(Math.max(clientX, triggerRect.left), maxX);\n const y = Math.min(Math.max(cl",
1680
1688
  "readme": "# Popup Component\n\nComponente de popup/dropdown posicionable y configurable que se posiciona relativamente a un trigger, con soporte para múltiples posiciones, cierre automático, tooltips e iconos.\n\n## Estructura\n\n```\nPopup/\n├── components/\n│ └── organisms/\n│ └── Popup.tsx # Componente principal\n├── hooks/ # Hooks personalizados\n├── stories/\n│ └── Popup.stories.tsx # Storybook stories\n└── index.ts # Export del módulo\n```\n\n## Uso Básico\n\n```tsx\nimport { Popup } from '@imj_media/ui';\nimport { useRef } from 'react';\n\n// Popup simple\nconst popupRef = useRef<PopupRef>(null);\n\n<Popup ref={popupRef} label=\"Abrir popup\" position=\"bottom-right\">\n <div>Contenido del popup</div>\n</Popup>;\n```\n\n## Props Principales\n\n### Control del Popup\n\n- `label?: string` - Texto del botón trigger\n- `position?: PopupPosition` - Posición del popup relativa al trigger (default: `'bottom-right'`)\n- `offset?: number` - Distancia en píxeles entre el trigger y el popup (default: `6`)\n- `popupId?: string` - ID único del popup (auto-generado si no se proporciona)\n\n### Comportamiento\n\n- `closeOnClickOutside?: boolean` - Cerrar al hacer clic fuera (default: `true`)\n- `closeOnEscape?: boolean` - Cerrar con tecla Escape (default: `true`)\n- `closeOnClick?: boolean` - Cerrar al hacer clic en el trigger (default: `true`)\n- `disabled?: boolean` - Deshabilitar el popup (default: `false`)\n\n### Estilo del Trigger\n\n- `theme?: 'solid' | 'outlined' | 'text' | 'ghost'` - Tema del botón trigger (default: `'solid'`)\n- `color?: ButtonColors` - Color del botón trigger (default: `'tertiary'`)\n- `size?: 'xs' | 'sm' | 'md' | 'lg'` - Tamaño del botón trigger (default: `'xs'`)\n- `rounded?: boolean` - Bordes redondeados (default: `false`)\n- `pill?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'` - Radio de borde (default: `'xs'`)\n- `borderRadius?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'` - Radio de borde del popup (default: `'sm'`)\n\n### Slots e Iconos\n\n- `icon?: I",
1681
1689
  "confidence": "medium"
1682
1690
  },
@@ -1693,7 +1701,7 @@
1693
1701
  "kind": "component",
1694
1702
  "description": {
1695
1703
  "primary": "jsdoc",
1696
- "jsdoc": "Acota el puntero al rectángulo del trigger en coordenadas de viewport.\nDevuelve la esquina superior izquierda de un ancla 1×1 lógica dentro del trigger\n(recorrido píxel a píxel cuando el trigger tiene tamaño entero en CSS px).\n/\nexport function clampClientPointForPopupAnchor(\n triggerRect: DOMRectReadOnly,\n clientX: number,\n clientY: number,\n): { x: number; y: number } {\n const rw = Math.max(triggerRect.width, 0);\n const rh = Math.max(triggerRect.height, 0);\n const insetX = rw > 0 ? Math.min(1, rw) : 0;\n const insetY = rh > 0 ? Math.min(1, rh) : 0;\n const maxX = rw > 0 ? triggerRect.right - insetX : triggerRect.left;\n const maxY = rh > 0 ? triggerRect.bottom - insetY : triggerRect.top;\n const x = Math.min(Math.max(clientX, triggerRect.left), maxX);\n const y = Math.min(Math.max(c",
1704
+ "jsdoc": "Acota el puntero al rectángulo del trigger en coordenadas de viewport.\nDevuelve la esquina superior izquierda de un ancla 1×1 lógica dentro del trigger\n(recorrido píxel a píxel cuando el trigger tiene tamaño entero en CSS px).\n/\nexport function clampClientPointForPopupAnchor(\n triggerRect: DOMRectReadOnly,\n clientX: number,\n clientY: number\n): { x: number; y: number } {\n const rw = Math.max(triggerRect.width, 0);\n const rh = Math.max(triggerRect.height, 0);\n const insetX = rw > 0 ? Math.min(1, rw) : 0;\n const insetY = rh > 0 ? Math.min(1, rh) : 0;\n const maxX = rw > 0 ? triggerRect.right - insetX : triggerRect.left;\n const maxY = rh > 0 ? triggerRect.bottom - insetY : triggerRect.top;\n const x = Math.min(Math.max(clientX, triggerRect.left), maxX);\n const y = Math.min(Math.max(cl",
1697
1705
  "readme": "# Popup Component\n\nComponente de popup/dropdown posicionable y configurable que se posiciona relativamente a un trigger, con soporte para múltiples posiciones, cierre automático, tooltips e iconos.\n\n## Estructura\n\n```\nPopup/\n├── components/\n│ └── organisms/\n│ └── Popup.tsx # Componente principal\n├── hooks/ # Hooks personalizados\n├── stories/\n│ └── Popup.stories.tsx # Storybook stories\n└── index.ts # Export del módulo\n```\n\n## Uso Básico\n\n```tsx\nimport { Popup } from '@imj_media/ui';\nimport { useRef } from 'react';\n\n// Popup simple\nconst popupRef = useRef<PopupRef>(null);\n\n<Popup ref={popupRef} label=\"Abrir popup\" position=\"bottom-right\">\n <div>Contenido del popup</div>\n</Popup>;\n```\n\n## Props Principales\n\n### Control del Popup\n\n- `label?: string` - Texto del botón trigger\n- `position?: PopupPosition` - Posición del popup relativa al trigger (default: `'bottom-right'`)\n- `offset?: number` - Distancia en píxeles entre el trigger y el popup (default: `6`)\n- `popupId?: string` - ID único del popup (auto-generado si no se proporciona)\n\n### Comportamiento\n\n- `closeOnClickOutside?: boolean` - Cerrar al hacer clic fuera (default: `true`)\n- `closeOnEscape?: boolean` - Cerrar con tecla Escape (default: `true`)\n- `closeOnClick?: boolean` - Cerrar al hacer clic en el trigger (default: `true`)\n- `disabled?: boolean` - Deshabilitar el popup (default: `false`)\n\n### Estilo del Trigger\n\n- `theme?: 'solid' | 'outlined' | 'text' | 'ghost'` - Tema del botón trigger (default: `'solid'`)\n- `color?: ButtonColors` - Color del botón trigger (default: `'tertiary'`)\n- `size?: 'xs' | 'sm' | 'md' | 'lg'` - Tamaño del botón trigger (default: `'xs'`)\n- `rounded?: boolean` - Bordes redondeados (default: `false`)\n- `pill?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'` - Radio de borde (default: `'xs'`)\n- `borderRadius?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'` - Radio de borde del popup (default: `'sm'`)\n\n### Slots e Iconos\n\n- `icon?: I",
1698
1706
  "confidence": "medium"
1699
1707
  },
@@ -1718,7 +1726,7 @@
1718
1726
  "kind": "component",
1719
1727
  "description": {
1720
1728
  "primary": "jsdoc",
1721
- "jsdoc": "Tamaños válidos para ProgressBar */\nconst VALID_SIZES: ProgressBarSize[] = ['xxs', 'xs', 'sm', 'md', 'lg'];\n\n/**\nHook interno para manejar la cuenta regresiva del ProgressBar.\nAnima el progreso desde un valor inicial hasta 0 en un tiempo determinado.\n/\nconst useCountdown = (\n initialProgress: number,\n countdownSeconds: number | undefined,\n onComplete?: () => void,\n) => {\n const [currentProgress, setCurrentProgress] = useState(initialProgress);\n const intervalRef = useRef<NodeJS.Timeout | null>(null);\n const startTimeRef = useRef<number>(0);\n const hasCompletedRef = useRef(false);\n // Guardar el callback en una ref para evitar que esté en las dependencias del useEffect\n const onCompleteRef = useRef(onComplete);\n\n const INTERVAL_MS = 16; // ~60fps\n\n // Mantener la ref actualizada",
1729
+ "jsdoc": "Tamaños válidos para ProgressBar */\nconst VALID_SIZES: ProgressBarSize[] = ['xxs', 'xs', 'sm', 'md', 'lg'];\n\n/**\nHook interno para manejar la cuenta regresiva del ProgressBar.\nAnima el progreso desde un valor inicial hasta 0 en un tiempo determinado.\n/\nconst useCountdown = (\n initialProgress: number,\n countdownSeconds: number | undefined,\n onComplete?: () => void\n) => {\n const [currentProgress, setCurrentProgress] = useState(initialProgress);\n const intervalRef = useRef<NodeJS.Timeout | null>(null);\n const startTimeRef = useRef<number>(0);\n const hasCompletedRef = useRef(false);\n // Guardar el callback en una ref para evitar que esté en las dependencias del useEffect\n const onCompleteRef = useRef(onComplete);\n\n const INTERVAL_MS = 16; // ~60fps\n\n // Mantener la ref actualizada",
1722
1730
  "confidence": "medium"
1723
1731
  },
1724
1732
  "examples": [
@@ -1954,7 +1962,8 @@
1954
1962
  "import { Spinner } from '@imj_media/ui';\n\n export default function Example() {\n return <Spinner />\n }"
1955
1963
  ]
1956
1964
  }
1957
- ]
1965
+ ],
1966
+ "standaloneSnippet": "import React from 'react';\n\nconst sizeMap: Record<string, number> = {\n sm: 16,\n md: 24,\n lg: 40,\n};\n\nconst colorMap: Record<string, string> = {\n primary: 'rgb(54, 89, 194)',\n secondary: 'rgb(240, 69, 69)',\n tertiary: 'rgb(33, 196, 94)',\n destructive: 'rgb(235, 179, 8)',\n};\n\ninterface SpinnerProps {\n size?: 'sm' | 'md' | 'lg';\n color?: 'primary' | 'secondary' | 'tertiary' | 'destructive';\n}\n\nexport function Spinner({ size = 'md', color = 'primary' }: SpinnerProps) {\n const px = sizeMap[size];\n const borderWidth = Math.max(2, Math.round(px / 8));\n const c = colorMap[color];\n\n return (\n <>\n <style>{`@keyframes ui-spin { to { transform: rotate(360deg) } }`}</style>\n <div\n role=\"status\"\n aria-label=\"Loading\"\n style={{\n width: px,\n height: px,\n border: `${borderWidth}px solid ${c}33`,\n borderTopColor: c,\n borderRadius: '50%',\n animation: 'ui-spin 0.6s linear infinite',\n display: 'inline-block',\n }}\n />\n </>\n );\n}\n"
1958
1967
  },
1959
1968
  {
1960
1969
  "id": "Stepper",
@@ -2014,7 +2023,7 @@
2014
2023
  "kind": "component",
2015
2024
  "description": {
2016
2025
  "primary": "jsdoc",
2017
- "jsdoc": "Tipografía del label en control `md`: tamaño/interlineado/tracking Regular + peso Bold (spec Type/Body/SM). */\nconst SWITCH_MD_LABEL_TYPOGRAPHY =\n 'ui-text-body-sm-regular ui-font-body-sm-bold ui-leading-body-sm-regular ui-tracking-body-sm';\n\nfunction optionSlotToButtonSlotConfig(\n icon: SwitchOption['leftSlot'],\n colorIcon: IconFontColor | undefined,\n duotone: Pick<\n ButtonSlotConfig,\n | 'iconDuotonePrimary'\n | 'iconDuotoneSecondary'\n | 'iconDuotoneOpacityPrimary'\n | 'iconDuotoneOpacitySecondary'\n >,\n defaultIconFontSize: IconFontSize,\n optionIconFontSize?: IconFontSize,\n): ButtonSlotConfig | undefined {\n if (icon === null || icon === undefined) {\n return undefined;\n }\n return {\n icon,\n ...(colorIcon !== undefined ? { colorIcon } : {}),\n ...duotone,",
2026
+ "jsdoc": "Tipografía del label en control `md`: tamaño/interlineado/tracking Regular + peso Bold (spec Type/Body/SM). */\nconst SWITCH_MD_LABEL_TYPOGRAPHY =\n 'ui-text-body-sm-regular ui-font-body-sm-bold ui-leading-body-sm-regular ui-tracking-body-sm';\n\nfunction optionSlotToButtonSlotConfig(\n icon: SwitchOption['leftSlot'],\n colorIcon: IconFontColor | undefined,\n duotone: Pick<\n ButtonSlotConfig,\n | 'iconDuotonePrimary'\n | 'iconDuotoneSecondary'\n | 'iconDuotoneOpacityPrimary'\n | 'iconDuotoneOpacitySecondary'\n >,\n defaultIconFontSize: IconFontSize,\n optionIconFontSize?: IconFontSize\n): ButtonSlotConfig | undefined {\n if (icon === null || icon === undefined) {\n return undefined;\n }\n return {\n icon,\n ...(colorIcon !== undefined ? { colorIcon } : {}),\n ...duotone,",
2018
2027
  "confidence": "medium"
2019
2028
  },
2020
2029
  "examples": [
@@ -2067,6 +2076,112 @@
2067
2076
  }
2068
2077
  ]
2069
2078
  },
2079
+ {
2080
+ "id": "Tabs",
2081
+ "path": "src/modules/Tabs",
2082
+ "legacy": false,
2083
+ "compositionType": 1,
2084
+ "exports": [
2085
+ {
2086
+ "name": "Tabs",
2087
+ "kind": "component",
2088
+ "description": {
2089
+ "primary": "storybook",
2090
+ "storybook": "Componente de navegación con pestañas. Soporta modos controlado (",
2091
+ "jsdoc": "Tabs — navigation component with size and pill variants.\nRenders a tablist with selectable tabs, optional icons, optional add button,\nand accessible keyboard navigation (ArrowLeft/Right, Home, End).",
2092
+ "confidence": "high"
2093
+ },
2094
+ "examples": [
2095
+ "import { Tabs } from '@imj_media/ui';\nimport { useState } from 'react';\nimport { faShapes, faCode, faEye, faGear } from '@fortawesome/pro-regular-svg-icons';\n\nconst items = [\n { id: 'design', label: 'Design', icon: faShapes },\n { id: 'code', label: 'Code', icon: faCode },\n { id: 'preview', label: 'Preview', icon: faEye },\n { id: 'settings', label: 'Settings', icon: faGear },\n];\n\nexport default function EditorTabs() {\n const [active, setActive] = useState('design');\n\n return (\n <Tabs\n items={items}\n appearance={{ size: 'md', pill: true }}\n selection={{ activeTab: active, onTabChange: setActive }}\n addButton={{ show: true, onClick: () => addNewTab() }}\n />\n );\n}",
2096
+ "<Tabs\n items={items}\n appearance={{ size: 'lg', pill: true }}\n selection={{ defaultActiveTab: 'code' }}\n addButton={{ show: true }}\n/>",
2097
+ "<Tabs\n items={items}\n appearance={{ size: 'md', pill: true }}\n selection={{ defaultActiveTab: 'design' }}\n addButton={{ show: true }}\n/>",
2098
+ "<Tabs\n items={items}\n appearance={{ size: 'sm', pill: true }}\n selection={{ defaultActiveTab: 'design' }}\n addButton={{ show: true }}\n/>",
2099
+ "<Tabs\n items={items}\n appearance={{ size: 'xs', pill: true }}\n selection={{ defaultActiveTab: 'design' }}\n addButton={{ show: true }}\n/>"
2100
+ ],
2101
+ "props": {
2102
+ "groups": {
2103
+ "tabsAddButton": {
2104
+ "props": {
2105
+ "show": {
2106
+ "name": "show",
2107
+ "type": "boolean",
2108
+ "required": false
2109
+ },
2110
+ "onClick": {
2111
+ "name": "onClick",
2112
+ "type": "() => void",
2113
+ "required": false
2114
+ }
2115
+ }
2116
+ },
2117
+ "tabs": {
2118
+ "props": {
2119
+ "items": {
2120
+ "name": "items",
2121
+ "type": "TabItem[]",
2122
+ "required": true
2123
+ },
2124
+ "className": {
2125
+ "name": "className",
2126
+ "type": "string",
2127
+ "required": false
2128
+ },
2129
+ "appearance": {
2130
+ "name": "appearance",
2131
+ "type": "TabsAppearanceProps",
2132
+ "required": false
2133
+ },
2134
+ "selection": {
2135
+ "name": "selection",
2136
+ "type": "TabsSelectionProps",
2137
+ "required": false
2138
+ },
2139
+ "addButton": {
2140
+ "name": "addButton",
2141
+ "type": "TabsAddButtonProps",
2142
+ "required": false
2143
+ }
2144
+ }
2145
+ }
2146
+ },
2147
+ "deprecatedRoot": [],
2148
+ "flat": {
2149
+ "items": {
2150
+ "name": "items",
2151
+ "type": "TabItem[]",
2152
+ "required": true,
2153
+ "description": "Array of tab items to render."
2154
+ },
2155
+ "className": {
2156
+ "name": "className",
2157
+ "type": "string",
2158
+ "required": false,
2159
+ "description": "Additional CSS classes for the root container."
2160
+ },
2161
+ "appearance": {
2162
+ "name": "appearance",
2163
+ "type": "TabsAppearanceProps",
2164
+ "required": false,
2165
+ "description": "Appearance configuration (size and pill shape)."
2166
+ },
2167
+ "selection": {
2168
+ "name": "selection",
2169
+ "type": "TabsSelectionProps",
2170
+ "required": false,
2171
+ "description": "Selection/state control."
2172
+ },
2173
+ "addButton": {
2174
+ "name": "addButton",
2175
+ "type": "TabsAddButtonProps",
2176
+ "required": false,
2177
+ "description": "Add button configuration."
2178
+ }
2179
+ }
2180
+ }
2181
+ }
2182
+ ],
2183
+ "standaloneSnippet": "import React from 'react';\n\ninterface TabItem {\n id: string;\n label: string;\n content: React.ReactNode;\n}\n\ninterface TabsProps {\n items: TabItem[];\n activeTab: string;\n onChange: (id: string) => void;\n}\n\nexport function Tabs({ items, activeTab, onChange }: TabsProps) {\n const activeItem = items.find((item) => item.id === activeTab);\n\n return (\n <div>\n <div\n style={{\n display: 'flex',\n borderBottom: '1px solid rgb(199, 199, 204)',\n gap: '4px',\n }}\n >\n {items.map((item) => {\n const isActive = item.id === activeTab;\n return (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => onChange(item.id)}\n style={{\n padding: '2px 4px',\n border: 'none',\n borderBottom: isActive\n ? '2px solid rgb(54, 89, 194)'\n : '2px solid transparent',\n background: 'none',\n cursor: 'pointer',\n fontSize: '14px',\n fontWeight: isActive\n ? '600'\n : '400',\n color: isActive\n ? 'rgb(54, 89, 194)'\n : 'rgb(89, 89, 94)',\n transition: 'color 0.15s, border-color 0.15s',\n }}\n onMouseEnter={(e) => {\n if (!isActive) e.currentTarget.style.color = 'rgb(48, 51, 54)';\n }}\n onMouseLeave={(e) => {\n if (!isActive) e.currentTarget.style.color = 'rgb(89, 89, 94)';\n }}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n <div style={{ padding: '4px 0' }}>\n {activeItem ? activeItem.content : null}\n </div>\n </div>\n );\n}\n"
2184
+ },
2070
2185
  {
2071
2186
  "id": "Tag",
2072
2187
  "path": "src/modules/Tag",
@@ -2090,7 +2205,8 @@
2090
2205
  "import {\n faCircleInfo,\n faFilter,\n faSearch,\n } from '@fortawesome/pro-regular-svg-icons';\n\n <Tag\n label='\"query\"'\n color=\"success\"\n infoIcon={{ name: faSearch }}\n onClose={() => {}}\n />\n <Tag\n label=\"Custom\"\n color=\"info\"\n infoIcon={{ name: faCircleInfo, size: 'sm', color: 'warning' }}\n />"
2091
2206
  ]
2092
2207
  }
2093
- ]
2208
+ ],
2209
+ "standaloneSnippet": "import React from 'react';\n\nconst colorMap: Record<string, { bg: string; text: string }> = {\n primary: { bg: 'rgb(54, 89, 194)1a', text: 'rgb(54, 89, 194)' },\n secondary: { bg: 'rgb(240, 69, 69)1a', text: 'rgb(240, 69, 69)' },\n tertiary: { bg: 'rgb(33, 196, 94)1a', text: 'rgb(33, 196, 94)' },\n destructive: { bg: 'rgb(235, 179, 8)1a', text: 'rgb(235, 179, 8)' },\n neutral: { bg: 'rgb(247, 247, 250)', text: 'rgb(89, 89, 94)' },\n};\n\nconst sizeMap: Record<string, { fontSize: string; padding: string }> = {\n sm: { fontSize: '12px', padding: '2px 6px' },\n md: { fontSize: '14px', padding: '4px 2px' },\n};\n\ninterface TagProps {\n children: React.ReactNode;\n color?: 'primary' | 'secondary' | 'tertiary' | 'destructive' | 'neutral';\n size?: 'sm' | 'md';\n closable?: boolean;\n onClose?: () => void;\n}\n\nexport function Tag({\n children,\n color = 'primary',\n size = 'md',\n closable = false,\n onClose,\n}: TagProps) {\n const c = colorMap[color];\n const s = sizeMap[size];\n\n return (\n <span\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: '4px',\n backgroundColor: c.bg,\n color: c.text,\n borderRadius: '8px',\n padding: s.padding,\n fontSize: s.fontSize,\n fontWeight: '500',\n lineHeight: 1.4,\n }}\n >\n {children}\n {closable && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Remove\"\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'inherit',\n padding: 0,\n fontSize: 'inherit',\n lineHeight: 1,\n opacity: 0.7,\n }}\n >\n \\u00d7\n </button>\n )}\n </span>\n );\n}\n"
2094
2210
  },
2095
2211
  {
2096
2212
  "id": "Text",
@@ -2521,7 +2637,8 @@
2521
2637
  },
2522
2638
  "examples": []
2523
2639
  }
2524
- ]
2640
+ ],
2641
+ "standaloneSnippet": "import React from 'react';\n\nconst sizeDims: Record<string, { track: [number, number]; knob: number; offset: number }> = {\n sm: { track: [28, 16], knob: 12, offset: 2 },\n md: { track: [36, 20], knob: 16, offset: 2 },\n lg: { track: [44, 24], knob: 20, offset: 2 },\n};\n\ninterface ToggleProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n disabled?: boolean;\n size?: 'sm' | 'md' | 'lg';\n}\n\nexport function Toggle({\n checked,\n onChange,\n disabled = false,\n size = 'md',\n}: ToggleProps) {\n const d = sizeDims[size];\n const [w, h] = d.track;\n\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n style={{\n position: 'relative',\n display: 'inline-flex',\n alignItems: 'center',\n width: w,\n height: h,\n borderRadius: h,\n border: 'none',\n padding: 0,\n cursor: disabled ? 'not-allowed' : 'pointer',\n backgroundColor: checked ? 'rgb(54, 89, 194)' : 'rgb(247, 247, 250)',\n opacity: disabled ? 0.5 : 1,\n transition: 'background-color 0.2s',\n }}\n >\n <span\n style={{\n position: 'absolute',\n top: d.offset,\n left: checked ? w - d.knob - d.offset : d.offset,\n width: d.knob,\n height: d.knob,\n borderRadius: '50%',\n backgroundColor: 'rgb(255, 255, 255)',\n transition: 'left 0.2s',\n boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.1)',\n }}\n />\n </button>\n );\n}\n"
2525
2642
  },
2526
2643
  {
2527
2644
  "id": "Toolbar",
@@ -2599,7 +2716,8 @@
2599
2716
  "<Tooltip label=\"Tooltip arriba\" position=\"top\">\n <InnerButton color=\"primary\">Top</InnerButton>\n </Tooltip>\n <Tooltip label=\"Tooltip abajo\" position=\"bottom\">\n <InnerButton color=\"secondary\">Bottom</InnerButton>\n </Tooltip>\n <Tooltip label=\"Tooltip izquierda\" position=\"left\">\n <InnerButton color=\"destructive\">Left</InnerButton>\n </Tooltip>\n <Tooltip label=\"Tooltip derecha\" position=\"right\">\n <InnerButton color=\"tertiary\">Right</InnerButton>\n </Tooltip>"
2600
2717
  ]
2601
2718
  }
2602
- ]
2719
+ ],
2720
+ "standaloneSnippet": "import React, { useState } from 'react';\n\nconst positionStyles: Record<string, React.CSSProperties> = {\n top: {\n bottom: '100%',\n left: '50%',\n transform: 'translateX(-50%)',\n marginBottom: '4px',\n },\n bottom: {\n top: '100%',\n left: '50%',\n transform: 'translateX(-50%)',\n marginTop: '4px',\n },\n left: {\n right: '100%',\n top: '50%',\n transform: 'translateY(-50%)',\n marginRight: '4px',\n },\n right: {\n left: '100%',\n top: '50%',\n transform: 'translateY(-50%)',\n marginLeft: '4px',\n },\n};\n\ninterface TooltipProps {\n children: React.ReactNode;\n content: React.ReactNode;\n position?: 'top' | 'bottom' | 'left' | 'right';\n}\n\nexport function Tooltip({ children, content, position = 'top' }: TooltipProps) {\n const [show, setShow] = useState(false);\n\n return (\n <div\n style={{ position: 'relative', display: 'inline-block' }}\n onMouseEnter={() => setShow(true)}\n onMouseLeave={() => setShow(false)}\n >\n {children}\n {show && (\n <span\n style={{\n position: 'absolute',\n whiteSpace: 'nowrap',\n backgroundColor: 'rgb(48, 51, 54)',\n color: 'rgb(255, 255, 255)',\n borderRadius: '8px',\n padding: '4px 2px',\n fontSize: '12px',\n pointerEvents: 'none',\n zIndex: 9999,\n ...positionStyles[position],\n }}\n >\n {content}\n </span>\n )}\n </div>\n );\n}\n"
2603
2721
  },
2604
2722
  {
2605
2723
  "id": "Visual",
@@ -2853,5 +2971,90 @@
2853
2971
  "title": "Imports del paquete",
2854
2972
  "body": "Importa desde @imj_media/ui; usa import type para tipos. Revisa deprecations en get_deprecations."
2855
2973
  }
2856
- ]
2974
+ ],
2975
+ "tokenPalette": {
2976
+ "colors": {
2977
+ "brand": {
2978
+ "default": "rgb(54, 89, 194)",
2979
+ "hover": "rgb(51, 82, 176)",
2980
+ "pressed": "rgb(46, 74, 161)"
2981
+ },
2982
+ "secondary": {
2983
+ "default": "rgb(240, 69, 69)",
2984
+ "hover": "rgb(212, 61, 61)",
2985
+ "pressed": "rgb(184, 51, 51)"
2986
+ },
2987
+ "tertiary": {
2988
+ "default": "rgb(33, 196, 94)",
2989
+ "hover": "rgb(31, 181, 87)",
2990
+ "pressed": "rgb(28, 163, 79)"
2991
+ },
2992
+ "destructive": {
2993
+ "default": "rgb(235, 179, 8)",
2994
+ "hover": "rgb(209, 158, 8)",
2995
+ "pressed": "rgb(181, 140, 5)"
2996
+ },
2997
+ "text": {
2998
+ "primary": "rgb(48, 51, 54)",
2999
+ "secondary": "rgb(89, 89, 94)",
3000
+ "disabled": "rgb(186, 189, 191)",
3001
+ "onFill": "rgb(255, 255, 255)"
3002
+ },
3003
+ "bg": {
3004
+ "canvas": "rgb(247, 247, 250)",
3005
+ "surface": "rgb(255, 255, 255)",
3006
+ "muted": "rgb(247, 247, 250)"
3007
+ },
3008
+ "border": {
3009
+ "default": "rgb(199, 199, 204)",
3010
+ "strong": "rgb(77, 79, 84)"
3011
+ },
3012
+ "status": {
3013
+ "success": "rgb(33, 196, 94)",
3014
+ "warning": "rgb(235, 179, 8)",
3015
+ "error": "rgb(240, 69, 69)",
3016
+ "info": "rgb(5, 181, 212)"
3017
+ }
3018
+ },
3019
+ "spacing": {
3020
+ "0": "0",
3021
+ "2": "2px",
3022
+ "4": "4px",
3023
+ "6": "6px",
3024
+ "8": "8px",
3025
+ "12": "12px",
3026
+ "16": "16px",
3027
+ "20": "20px",
3028
+ "24": "24px",
3029
+ "32": "32px",
3030
+ "40": "40px",
3031
+ "48": "48px",
3032
+ "64": "64px",
3033
+ "96": "96px"
3034
+ },
3035
+ "radius": {
3036
+ "sm": "8px",
3037
+ "md": "8px",
3038
+ "lg": "10px",
3039
+ "full": "9999px"
3040
+ },
3041
+ "fontSize": {
3042
+ "xs": "12px",
3043
+ "sm": "14px",
3044
+ "md": "16px",
3045
+ "lg": "18px",
3046
+ "xl": "20px"
3047
+ },
3048
+ "fontWeight": {
3049
+ "normal": "400",
3050
+ "medium": "500",
3051
+ "semibold": "600",
3052
+ "bold": "700"
3053
+ },
3054
+ "shadow": {
3055
+ "sm": "0 1px 2px 0 rgba(0, 0, 0, 0.1)",
3056
+ "md": "0 4px 8px -2px rgba(0, 0, 0, 0.2)",
3057
+ "lg": "0 16px 24px -8px rgba(0, 0, 0, 0.3)"
3058
+ }
3059
+ }
2857
3060
  }
@@ -30,7 +30,8 @@
30
30
  "consumerChecklist": {
31
31
  "type": "array",
32
32
  "items": { "$ref": "#/$defs/consumerChecklistItem" }
33
- }
33
+ },
34
+ "tokenPalette": { "$ref": "#/$defs/tokenPalette" }
34
35
  },
35
36
  "$defs": {
36
37
  "mergedDescription": {
@@ -97,7 +98,19 @@
97
98
  "legacyReason": { "type": "string" },
98
99
  "compositionType": { "enum": [1, 2] },
99
100
  "exports": { "type": "array", "items": { "$ref": "#/$defs/moduleExport" } },
100
- "compositionRecipe": { "$ref": "#/$defs/compositionRecipe" }
101
+ "compositionRecipe": { "$ref": "#/$defs/compositionRecipe" },
102
+ "standaloneSnippet": { "type": "string" }
103
+ }
104
+ },
105
+ "tokenPalette": {
106
+ "type": "object",
107
+ "properties": {
108
+ "colors": { "type": "object" },
109
+ "spacing": { "type": "object" },
110
+ "radius": { "type": "object" },
111
+ "fontSize": { "type": "object" },
112
+ "fontWeight": { "type": "object" },
113
+ "shadow": { "type": "object" }
101
114
  }
102
115
  },
103
116
  "styleIndex": {