reactigoded 1.0.0-beta.26
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.
- package/CHANGELOG.md +3001 -0
- package/LICENSE +21 -0
- package/README.md +556 -0
- package/dist/Toast-FZUdYH1L.js +594 -0
- package/dist/cn.js +7 -0
- package/dist/components/Accordion/Accordion.d.ts +49 -0
- package/dist/components/Accordion/AccordionContent.d.ts +15 -0
- package/dist/components/Accordion/AccordionContext.d.ts +59 -0
- package/dist/components/Accordion/AccordionHeader.d.ts +22 -0
- package/dist/components/Accordion/AccordionItem.d.ts +11 -0
- package/dist/components/Accordion/index.d.ts +8 -0
- package/dist/components/Alert/Alert.d.ts +39 -0
- package/dist/components/Alert/index.d.ts +2 -0
- package/dist/components/AlertDialog/AlertDialogClose.d.ts +64 -0
- package/dist/components/AlertDialog/AlertDialogContent.d.ts +58 -0
- package/dist/components/AlertDialog/index.d.ts +14 -0
- package/dist/components/Avatar/Avatar.d.ts +62 -0
- package/dist/components/Avatar/AvatarGroup.d.ts +13 -0
- package/dist/components/Avatar/index.d.ts +4 -0
- package/dist/components/Badge/Badge.d.ts +33 -0
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +14 -0
- package/dist/components/Breadcrumb/BreadcrumbItem.d.ts +21 -0
- package/dist/components/Breadcrumb/index.d.ts +4 -0
- package/dist/components/Button/Button.d.ts +61 -0
- package/dist/components/Button/IconButton.d.ts +48 -0
- package/dist/components/Button/index.d.ts +4 -0
- package/dist/components/Card/Card.d.ts +79 -0
- package/dist/components/Card/CardBody.d.ts +8 -0
- package/dist/components/Card/CardDivider.d.ts +8 -0
- package/dist/components/Card/CardFooter.d.ts +8 -0
- package/dist/components/Card/CardHeader.d.ts +8 -0
- package/dist/components/Card/CardImage.d.ts +15 -0
- package/dist/components/Card/index.d.ts +12 -0
- package/dist/components/Checkbox/Checkbox.d.ts +47 -0
- package/dist/components/Checkbox/index.d.ts +2 -0
- package/dist/components/Chip/Chip.d.ts +38 -0
- package/dist/components/Chip/index.d.ts +2 -0
- package/dist/components/Dialog/Dialog.d.ts +80 -0
- package/dist/components/Dialog/DialogBody.d.ts +8 -0
- package/dist/components/Dialog/DialogClose.d.ts +61 -0
- package/dist/components/Dialog/DialogContent.d.ts +36 -0
- package/dist/components/Dialog/DialogContext.d.ts +1 -0
- package/dist/components/Dialog/DialogFooter.d.ts +8 -0
- package/dist/components/Dialog/DialogHeader.d.ts +13 -0
- package/dist/components/Dialog/DialogTrigger.d.ts +44 -0
- package/dist/components/Dialog/index.d.ts +16 -0
- package/dist/components/Divider/Divider.d.ts +34 -0
- package/dist/components/Divider/index.d.ts +2 -0
- package/dist/components/Input/ErrorText.d.ts +13 -0
- package/dist/components/Input/Helper.d.ts +11 -0
- package/dist/components/Input/Input.d.ts +35 -0
- package/dist/components/Input/InputAddon.d.ts +8 -0
- package/dist/components/Input/InputGroup.d.ts +17 -0
- package/dist/components/Input/Label.d.ts +12 -0
- package/dist/components/Input/index.d.ts +12 -0
- package/dist/components/NativeSelect/NativeSelect.d.ts +41 -0
- package/dist/components/NativeSelect/index.d.ts +2 -0
- package/dist/components/Navbar/Navbar.d.ts +46 -0
- package/dist/components/Navbar/NavbarActions.d.ts +10 -0
- package/dist/components/Navbar/NavbarLink.d.ts +13 -0
- package/dist/components/Navbar/NavbarLogo.d.ts +22 -0
- package/dist/components/Navbar/NavbarMenuButton.d.ts +15 -0
- package/dist/components/Navbar/NavbarNav.d.ts +14 -0
- package/dist/components/Navbar/index.d.ts +12 -0
- package/dist/components/Pagination/Pagination.d.ts +77 -0
- package/dist/components/Pagination/index.d.ts +2 -0
- package/dist/components/Progress/Progress.d.ts +63 -0
- package/dist/components/Progress/index.d.ts +2 -0
- package/dist/components/Radio/Radio.d.ts +28 -0
- package/dist/components/Radio/index.d.ts +2 -0
- package/dist/components/Rating/Rating.d.ts +85 -0
- package/dist/components/Rating/index.d.ts +2 -0
- package/dist/components/Sidebar/Sidebar.d.ts +28 -0
- package/dist/components/Sidebar/SidebarContext.d.ts +32 -0
- package/dist/components/Sidebar/SidebarDivider.d.ts +8 -0
- package/dist/components/Sidebar/SidebarFooter.d.ts +8 -0
- package/dist/components/Sidebar/SidebarHeader.d.ts +13 -0
- package/dist/components/Sidebar/SidebarItem.d.ts +42 -0
- package/dist/components/Sidebar/SidebarNav.d.ts +13 -0
- package/dist/components/Sidebar/SidebarSection.d.ts +11 -0
- package/dist/components/Sidebar/SidebarToggle.d.ts +17 -0
- package/dist/components/Sidebar/index.d.ts +16 -0
- package/dist/components/Skeleton/Skeleton.d.ts +60 -0
- package/dist/components/Skeleton/index.d.ts +2 -0
- package/dist/components/Slider/Slider.d.ts +44 -0
- package/dist/components/Slider/index.d.ts +2 -0
- package/dist/components/Slot/Slot.d.ts +1 -0
- package/dist/components/Slot/composeEventHandlers.d.ts +1 -0
- package/dist/components/Slot/composeRefs.d.ts +1 -0
- package/dist/components/Slot/index.d.ts +3 -0
- package/dist/components/Spinner/Spinner.d.ts +31 -0
- package/dist/components/Spinner/index.d.ts +2 -0
- package/dist/components/Stepper/Step.d.ts +17 -0
- package/dist/components/Stepper/Stepper.d.ts +97 -0
- package/dist/components/Stepper/index.d.ts +4 -0
- package/dist/components/Switch/Switch.d.ts +53 -0
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Table/Table.d.ts +74 -0
- package/dist/components/Table/index.d.ts +2 -0
- package/dist/components/Tabs/Tabs.d.ts +41 -0
- package/dist/components/Tabs/TabsContent.d.ts +15 -0
- package/dist/components/Tabs/TabsContext.d.ts +61 -0
- package/dist/components/Tabs/TabsList.d.ts +10 -0
- package/dist/components/Tabs/TabsTrigger.d.ts +15 -0
- package/dist/components/Tabs/index.d.ts +8 -0
- package/dist/components/Textarea/Textarea.d.ts +51 -0
- package/dist/components/Textarea/index.d.ts +2 -0
- package/dist/components/ThemeToggle/ThemeToggle.d.ts +43 -0
- package/dist/components/ThemeToggle/index.d.ts +2 -0
- package/dist/components/Timeline/Timeline.d.ts +20 -0
- package/dist/components/Timeline/TimelineItem.d.ts +32 -0
- package/dist/components/Timeline/index.d.ts +4 -0
- package/dist/components/Toast/Toast.d.ts +47 -0
- package/dist/components/Toast/ToastContext.d.ts +93 -0
- package/dist/components/Toast/ToastProvider.d.ts +116 -0
- package/dist/components/Toast/index.d.ts +6 -0
- package/dist/components/floating/Menu/Menu.d.ts +67 -0
- package/dist/components/floating/Menu/MenuContent.d.ts +60 -0
- package/dist/components/floating/Menu/MenuContext.d.ts +78 -0
- package/dist/components/floating/Menu/MenuItem.d.ts +47 -0
- package/dist/components/floating/Menu/MenuLabel.d.ts +6 -0
- package/dist/components/floating/Menu/MenuSeparator.d.ts +6 -0
- package/dist/components/floating/Menu/MenuTrigger.d.ts +53 -0
- package/dist/components/floating/Menu/index.d.ts +12 -0
- package/dist/components/floating/Menu/menuSelectors.d.ts +15 -0
- package/dist/components/floating/Tooltip/Tooltip.d.ts +194 -0
- package/dist/components/floating/Tooltip/index.d.ts +2 -0
- package/dist/components/floating/index.d.ts +3 -0
- package/dist/components/floating/primitives/FloatingTreeRoot.d.ts +55 -0
- package/dist/components/floating/primitives/index.d.ts +2 -0
- package/dist/components/floating/primitives/useFloatingNode.d.ts +1 -0
- package/dist/components/index.d.ts +38 -0
- package/dist/hooks/useControllableState.d.ts +152 -0
- package/dist/hooks/useTheme.d.ts +44 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +1843 -0
- package/dist/server-safe.d.ts +69 -0
- package/dist/server-safe.js +47 -0
- package/dist/styles/igoded-base.css +1 -0
- package/dist/styles/igoded-components.css +1 -0
- package/dist/styles/igoded-design.css +1 -0
- package/dist/styles/igoded-fonts.css +1 -0
- package/dist/styles/igoded-reset.css +1 -0
- package/dist/styles/igoded-state-css.css +1 -0
- package/dist/styles/igoded-tokens.css +1 -0
- package/dist/styles/index.css +3 -0
- package/dist/styles/state/active.css +1 -0
- package/dist/styles/state/checked.css +1 -0
- package/dist/styles/state/default.css +1 -0
- package/dist/styles/state/disabled.css +1 -0
- package/dist/styles/state/empty.css +1 -0
- package/dist/styles/state/first-child.css +1 -0
- package/dist/styles/state/first-of-type.css +1 -0
- package/dist/styles/state/focus-visible.css +1 -0
- package/dist/styles/state/focus-within.css +1 -0
- package/dist/styles/state/focus.css +1 -0
- package/dist/styles/state/group-hover.css +1 -0
- package/dist/styles/state/hover.css +1 -0
- package/dist/styles/state/in-range.css +1 -0
- package/dist/styles/state/indeterminate.css +1 -0
- package/dist/styles/state/invalid.css +1 -0
- package/dist/styles/state/last-child.css +1 -0
- package/dist/styles/state/last-of-type.css +1 -0
- package/dist/styles/state/nth-child.css +1 -0
- package/dist/styles/state/only-child.css +1 -0
- package/dist/styles/state/optional.css +1 -0
- package/dist/styles/state/other.css +1 -0
- package/dist/styles/state/out-of-range.css +1 -0
- package/dist/styles/state/placeholder-shown.css +1 -0
- package/dist/styles/state/read-only.css +1 -0
- package/dist/styles/state/read-write.css +1 -0
- package/dist/styles/state/required.css +1 -0
- package/dist/styles/state/target.css +1 -0
- package/dist/styles/state/valid.css +1 -0
- package/dist/utils/cn.d.ts +11 -0
- package/package.json +282 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,3001 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Todos los cambios notables de este paquete se documentan aquí.
|
|
4
|
+
|
|
5
|
+
Formato basado en [Keep a Changelog](https://keepachangelog.com/),
|
|
6
|
+
versionado [SemVer](https://semver.org/lang/es/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Cambiado — BREAKING (última ventana antes de 1.0)
|
|
11
|
+
|
|
12
|
+
- Renombres de API pública para eliminar colisiones de nombre antes de congelar 1.0:
|
|
13
|
+
- Clases `.ig-tooltip-color-*` → `.ig-tooltip-*` (el infijo `-color-` era dialecto local; la doc ya prometía la forma sin él).
|
|
14
|
+
- Clase `.ig-navbar-brand` → `.ig-navbar-logo` y componente `NavbarBrand` → `NavbarLogo` (`brand` colisionaba con el eje de rol de color; el slot es el logo).
|
|
15
|
+
- Clases `.ig-input-error`/`.ig-input-success` → `.ig-input-invalid`/`.ig-input-valid` y valores del prop `state` (`"invalid"`/`"valid"`), alineados con `aria-invalid`.
|
|
16
|
+
|
|
17
|
+
### Añadido
|
|
18
|
+
|
|
19
|
+
- **Garantía `@server-safe` validada contra Vercel Edge REAL (#18)**: el catálogo de globals permitidos se medía contra `@edge-runtime/vm` (sandbox sobre Node que filtra globals Node-shared, ~95% fiel). Un probe de deploy real a Vercel Edge producción (`scripts/runtime-oracle/vercel/`, `typeof <bare>` — el único test fiel dado que el objeto-global de Edge es exótico) cerró ese ~5%: **3 globals que `@edge-runtime/vm` reportaba presentes por fuga de Node (`WeakRef`, `FinalizationRegistry`, `DOMException`) NO existen en Vercel Edge real** → restados de `SAFE_GLOBALS` (nuevo set `EDGE_MISSING_REAL`). Cierra un falso-negativo latente (0 módulos los usaban) que habría dejado pasar un `new WeakRef(...)` bare que crashea en producción Edge. Premisas del catálogo (createObjectURL/WASM/eval/elu) confirmadas 6/6 en el Edge real.
|
|
20
|
+
- **Freeze de API pública (§5.13)**: la superficie estable de 1.0 (clases de componente, data-attributes de estado, tokens Tier-2) se congela en `src/_audit/public-api-names.json`, protegida por el gate `scripts/check-public-api-names.mjs` (`⊆ dist`, encadenado en `verify:unit`). Editar el freeze exige bump MAJOR. La capa utility (`state.css`) y los tokens Tier-1/Tier-3 quedan fuera del freeze **por declaración explícita** (ver CSSAPI.mdx + DesignTokens.mdx).
|
|
21
|
+
|
|
22
|
+
## [1.0.0-beta.26] — 2026-05-29 (bloque claudegate5 / beta.27 cerrado)
|
|
23
|
+
|
|
24
|
+
Cierra el bloque **claudegate5 / beta.27** (25 PRs entre #105 y
|
|
25
|
+
#129) con 3 ejes principales:
|
|
26
|
+
|
|
27
|
+
- **Slot pattern DS-wide (D14)**: refactor `asChild` en las 4
|
|
28
|
+
familias con triggers (Dialog / AlertDialog / Tooltip / Menu).
|
|
29
|
+
Patrón canónico Radix/shadcn habilitado en todo el DS.
|
|
30
|
+
- **EOPT widening completo (#155)**: 307 props públicas opcionales
|
|
31
|
+
ensanchadas a `?: T | undefined` para consumers con
|
|
32
|
+
`exactOptionalPropertyTypes: true`. 2 fronteras documentadas
|
|
33
|
+
explícitamente (sección final del bloque).
|
|
34
|
+
- **Red de seguridad cross-platform (#151)**: matriz CI 4 combos
|
|
35
|
+
(ubuntu/windows × Node 22.12/24), test catálogo `CLIENT_GLOBALS`
|
|
36
|
+
vs Node runtime (#150), gates de payload reales (#160), peer
|
|
37
|
+
`@floating-ui/react ^0.27` pre-1.0 documentado (#156).
|
|
38
|
+
|
|
39
|
+
Tras este release queda el gate review claudegate6 (auditoría AI con
|
|
40
|
+
cruce A+B de dos auditores independientes) antes de tagear
|
|
41
|
+
`1.0.0-rc.1` (API congelada para 1.x). beta.26 puede aún recibir bug
|
|
42
|
+
fixes o ajustes pre-rc.1 si el gate identifica blockers.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### BREAKING (D14 Bloque B beta.27)
|
|
46
|
+
|
|
47
|
+
- **`DialogAction` ELIMINADO**. El patrón canónico para CTAs del footer
|
|
48
|
+
ahora es `<DialogClose asChild>` con un `<Button>` del consumer como
|
|
49
|
+
child. Migración:
|
|
50
|
+
|
|
51
|
+
```diff
|
|
52
|
+
<DialogFooter>
|
|
53
|
+
- <DialogAction className="ig-btn ig-btn-secondary">Cancelar</DialogAction>
|
|
54
|
+
- <DialogAction className="ig-btn ig-btn-brand">Aceptar</DialogAction>
|
|
55
|
+
+ <DialogClose asChild>
|
|
56
|
+
+ <Button variant="secondary">Cancelar</Button>
|
|
57
|
+
+ </DialogClose>
|
|
58
|
+
+ <DialogClose asChild>
|
|
59
|
+
+ <Button variant="brand">Aceptar</Button>
|
|
60
|
+
+ </DialogClose>
|
|
61
|
+
</DialogFooter>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Cierra la asimetría léxica `DialogClose styled / DialogAction unstyled /
|
|
65
|
+
AlertDialogClose clónico` identificada en el cruce reviews beta.25.
|
|
66
|
+
|
|
67
|
+
- **`AlertDialogClose` cambia default render** a icon-button "×" styled
|
|
68
|
+
(coherente con `DialogClose`). Antes era unstyled `<button>` para usar
|
|
69
|
+
con `className="ig-btn ig-btn-*"`. Migración para CTAs del footer:
|
|
70
|
+
|
|
71
|
+
```diff
|
|
72
|
+
<AlertDialogFooter>
|
|
73
|
+
- <AlertDialogClose className="ig-btn ig-btn-secondary">Cancelar</AlertDialogClose>
|
|
74
|
+
- <AlertDialogClose className="ig-btn ig-btn-danger">Sí, borrar</AlertDialogClose>
|
|
75
|
+
+ <AlertDialogClose asChild>
|
|
76
|
+
+ <Button variant="secondary">Cancelar</Button>
|
|
77
|
+
+ </AlertDialogClose>
|
|
78
|
+
+ <AlertDialogClose asChild>
|
|
79
|
+
+ <Button variant="danger">Sí, borrar</Button>
|
|
80
|
+
+ </AlertDialogClose>
|
|
81
|
+
</AlertDialogFooter>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Added (D14 Bloque B beta.27)
|
|
85
|
+
|
|
86
|
+
- **`asChild` prop en `DialogTrigger`, `DialogClose`, `AlertDialogClose`**.
|
|
87
|
+
Cuando `asChild={true}`, el componente clona el child del consumer y le
|
|
88
|
+
inyecta semantics (aria + onClick handler) sin renderizar wrapper
|
|
89
|
+
propio. Patrón canónico Radix/shadcn. Internamente usa el primitive
|
|
90
|
+
`<Slot>` de D14 Bloque A (PR #110).
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
<DialogTrigger asChild>
|
|
94
|
+
<Button variant="brand">Abrir modal</Button>
|
|
95
|
+
</DialogTrigger>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`AlertDialogTrigger` es alias de `DialogTrigger`, así que también
|
|
99
|
+
obtiene `asChild` automáticamente.
|
|
100
|
+
|
|
101
|
+
Sin `asChild`, comportamiento idéntico a 1.0.0-beta.25 (backwards-compat
|
|
102
|
+
para DialogTrigger y DialogClose; AlertDialogClose es BREAKING — ver arriba).
|
|
103
|
+
|
|
104
|
+
- **`<Slot>` primitive interno** en `src/components/Slot/` — no exportado
|
|
105
|
+
al consumer, solo usado por components del DS via `asChild` opt-in.
|
|
106
|
+
Ver D14 + PR #110 para diseño completo.
|
|
107
|
+
|
|
108
|
+
- **`asChild` prop en `MenuTrigger`** (D14 Bloque D beta.27).
|
|
109
|
+
Misma semántica que `DialogTrigger`/`DialogClose`/`AlertDialogClose`: si
|
|
110
|
+
`true`, clona el child del consumer y le aplica id + ARIA (haspopup,
|
|
111
|
+
expanded, controls) + handlers FUI (click + keyboard) sin renderizar
|
|
112
|
+
un `<button>` propio ni aplicar `ig-menu-trigger`. Permite usar
|
|
113
|
+
cualquier elemento como trigger:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
<Menu>
|
|
117
|
+
<MenuTrigger asChild>
|
|
118
|
+
<Button variant="brand">Abrir menú</Button>
|
|
119
|
+
</MenuTrigger>
|
|
120
|
+
<MenuContent>
|
|
121
|
+
<MenuItem>Editar</MenuItem>
|
|
122
|
+
</MenuContent>
|
|
123
|
+
</Menu>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Sin `asChild`, comportamiento idéntico a 1.0.0-beta.25 (backwards-compat).
|
|
127
|
+
|
|
128
|
+
**Cierra el refactor Slot DS-wide** (D14): 4 familias migradas (Dialog,
|
|
129
|
+
AlertDialog, Tooltip, Menu) — todas las que existen con triggers en
|
|
130
|
+
1.0. Las 13 familias futuras del roadmap `floating/README.md`
|
|
131
|
+
(Popover, HoverCard, ContextMenu, Combobox, etc.) se diseñarán ya
|
|
132
|
+
nativas con Slot en 1.1.0+.
|
|
133
|
+
|
|
134
|
+
- **`Tooltip` forwardea outer Slot props al child final** (D14 Bloque C beta.27).
|
|
135
|
+
`TooltipProps` admite ahora un index signature (cualquier prop adicional
|
|
136
|
+
no consumida por Tooltip — `text`, `placement`, `variant`, `children`,
|
|
137
|
+
delays, `container` — se forwardea via Slot interno al elemento child
|
|
138
|
+
clonado). Habilita el patrón nested asChild:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<DialogClose asChild>
|
|
142
|
+
<Tooltip text="Cancela y cierra">
|
|
143
|
+
<Button variant="danger">×</Button>
|
|
144
|
+
</Tooltip>
|
|
145
|
+
</DialogClose>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
El outer Slot de `DialogClose` clona Tooltip pasando close `onClick` +
|
|
149
|
+
ref; Tooltip los recibe en su nuevo `...rest` y los forwardea al
|
|
150
|
+
`<Button>` final via `<Slot>` interno. Pre-Bloque C, esos props se
|
|
151
|
+
dropeaban silenciosamente (codex P2 round 2 sobre PR #109 cazó el bug en el
|
|
152
|
+
doc-only plan).
|
|
153
|
+
|
|
154
|
+
API público de Tooltip preservado: `text`, `placement`, `variant`,
|
|
155
|
+
`children` (ReactElement), delays, `container` siguen igual.
|
|
156
|
+
|
|
157
|
+
### Added (resto del bloque beta.27)
|
|
158
|
+
|
|
159
|
+
- **EOPT widening — 307 props opcionales ensanchadas a `?: T | undefined`** (#155, PR #114). Habilita el patrón consumer `<Comp prop={cond ? val : undefined}>` bajo `exactOptionalPropertyTypes: true`. Reparto:
|
|
160
|
+
- 271 CLASE 1 (codemod AST mecánico).
|
|
161
|
+
- 36 CLASE 2 (widening + tests runtime sobre los 12 componentes con `useControllableState`).
|
|
162
|
+
- 11 OUT OF SCOPE (3 discriminadores `?: undefined` literal + 8 exclusiones `?: never`).
|
|
163
|
+
|
|
164
|
+
Inventario AST: `scripts/eopt-classify.mjs`. Verifier: `scripts/eopt-verify-class2.mjs`. Codemod: `scripts/eopt-codemod.mjs`. Tests runtime: `src/__tests__/eopt-undefined-uncontrolled.test.tsx`. Fronteras NO cubiertas documentadas más abajo.
|
|
165
|
+
|
|
166
|
+
- **Rating form-value bridge nativo (#153, PR #117)**. `Rating` añade `name?: string`. Cuando se proporciona, renderiza `<input type="hidden" name={name} value={value}>` adyacente al `role="radiogroup"`. El valor espeja el efectivo (controlled o uncontrolled). Mantiene el canal-de-forma WCAG (#102 — buttons en `radiogroup`).
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<form action="/rate" method="post">
|
|
170
|
+
<Rating name="score" defaultValue={3} max={5} />
|
|
171
|
+
<button type="submit">Enviar</button>
|
|
172
|
+
</form>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Cierra la asimetría con los otros 5 inputs del DS (`Checkbox`, `Switch`, `Radio`, `Slider`, `NativeSelect`) que serializan vía `<input>` nativo heredado.
|
|
176
|
+
|
|
177
|
+
- **CI matriz Windows + Node [22.12, 24]** (#151, PR #121). `.github/workflows/verify.yml` corre 4 combos: ubuntu/windows × Node 22.12/24. Cross-OS gates: `scripts/check-no-dev-warns.mjs` (reemplaza el step inline `grep -cF` POSIX-only), helpers `crossOsResolve/crossOsRelative/crossOsDirname` en `scripts/check-server-safe-markers.mjs` que preservan Windows drive letters (codex P1).
|
|
178
|
+
|
|
179
|
+
- **Test catálogo `CLIENT_GLOBALS` vs Node runtime cross-version** (#150, PR #122). `src/__tests__/server-safe-catalog-vs-node.test.ts` verifica en runtime real (Node 22.12 + 24, ubuntu + windows) que el catálogo del gate `@server-safe` no tiene falsos positivos sobre globals que Node provee. 4 overlaps documentados (`globalThis`/`process`/`Buffer`/`navigator`) con rationale multi-runtime.
|
|
180
|
+
|
|
181
|
+
- **Server-safe marker AST** (#158, PR #118). El gate `check-server-safe-markers.mjs` detecta el marker `@server-safe` vía TypeScript Compiler API (JSDoc tag), no via substring. Cierra 3 vectores de falsos positivos: string literals, line comments en prosa, block comments no-JSDoc. Tests: `src/__tests__/server-safe-marker-ast-detection.test.ts`.
|
|
182
|
+
|
|
183
|
+
- **Consumer-pack tarball-real-install gate** (PR #108). `scripts/test-consumer-pack.mjs` hace `npm pack` → instala en sandbox → corre `tsc --noEmit` con bundler + NodeNext. Caza issues que solo emergen con el flow end-to-end real: exports field interpretado por node/npm, peer deps resueltos, `.d.ts` paths bajo NodeNext.
|
|
184
|
+
|
|
185
|
+
- **Smuggling cross-módulo gate (HIGH-2)** (PR #106). El gate `@server-safe` ahora sigue imports transitivos (depth-first) para cazar bypasses que importan un util sucio desde un archivo `@server-safe`. Inventario completo en `scripts/check-server-safe-markers.mjs` (orquestador `checkFileWithImports`).
|
|
186
|
+
|
|
187
|
+
- **NodeNext + CLIENT_GLOBALS ampliado + JSDoc Menu (HIGH bloque)** (PR #105). El gate detecta acceso bare a 46 client globals (catálogo ampliado vs los 6 originales). `CLIENT_GLOBALS` exportado para uso en tests. Resolución alias en NodeNext robustecida con cascada de extensiones `.ts`/`.tsx`/`/index.ts`/`/index.tsx`.
|
|
188
|
+
|
|
189
|
+
- **D13 — name reservations pre-rc.1** (PR #107). Reservas léxicas para los componentes/props que el roadmap 1.1+ va a añadir (Popover/HoverCard/ContextMenu/Combobox y subsystem `Form`/`Field`/`FormError`). Documentadas en `docs/decisions/D13-name-reservations-pre-rc1.md`.
|
|
190
|
+
|
|
191
|
+
- **Fixture Playwright contraste pares componente** (#152, PR #129). `src/stories/ContrastPairs.stories.tsx` mide ΔE OKLab sobre DOM real (Chromium vía vitest-browser-playwright + Storybook) y asserta vs `deltaE_at_decision` con drift_tolerance 5%. Complementa el gate `check-component-contrast.mjs` (CSS resolver) con cobertura runtime DOM — caza regresiones de cascade/specificity.
|
|
192
|
+
|
|
193
|
+
- **Audit fixtures `navigator` wholesale denylist** (#164, PR #125). 10 tests en `src/_audit/server-safe-gate.test.ts` que prueban el wholesale denylist de `navigator.X` (sin allowlist por property name). Blindaje del invariante para que una regresión que saque `navigator` del catálogo salte por dos lados (catálogo + audit).
|
|
194
|
+
|
|
195
|
+
### Changed
|
|
196
|
+
|
|
197
|
+
- **ΔE error_threshold blindado 0.05 → 0.07 + axis-kobalium dark ratificado** (#154, PR #115). `scripts/perceptual-allowlist.json` v2: `error_threshold` subido a 0.07 sin recalibrar tokens. `axis-kobalium dark` (ΔE 0.0522) ratificado como excepción consciente vía allowlist (drift gate sigue activo). Cualquier par nuevo bajo 0.07 sin allowlist explícita rompe CI. SKILL.md alineado (PRs #115, #116).
|
|
198
|
+
|
|
199
|
+
- **DialogContext marcado `@internal`** (#162, PR #120). Los 4 exports del módulo (`DialogContextValue`, `DialogContext`, `useDialogContextOptional`, `useDialogContextRequired`) son `@internal`. `stripInternal: true` borra todo el `.d.ts` publicado (`export {}`). El bundle JS runtime sigue intacto. Verificado vía consumer-pack con `skipLibCheck: false`. El deep-import `AlertDialogClose → @/components/Dialog/DialogContext` es arquitectura D8 documentada (no deuda).
|
|
200
|
+
|
|
201
|
+
- **StepProps internal split completado** (#161, PR #119). Las 4 props que el `Stepper` inyecta vía `cloneElement` (`index`/`active`/`complete`/`labeled`) movidas de `StepProps` (público) a `StepInternalProps` (`@internal`). Justo con las 3 que ya estaban (`interactive`/`onActivate`/`onStepKeyDown`), `StepInternalProps` agrupa las 7 props internas. `StepProps` publicado queda con `label?: ReactNode` + `ref` + `extends HTMLAttributes`.
|
|
202
|
+
|
|
203
|
+
- **Size-budget mide payload real (no shim)** (#160, PR #126). `package.json` `size-limit` entries para JS bundles ahora incluyen el lazy chunk vía glob `dist/!(index|server-safe|cn).js` (extglob) — captura cualquier chunk transitivo que Rollup emita. Limits ajustados al payload real: `JS bundle ESM` 18.11 KB gz / 20 KB limit; `JS bundle server-safe ESM` 4.42 KB gz / 8 KB limit (vs 16 KB anterior cosmético).
|
|
204
|
+
|
|
205
|
+
- **Toast container JSDoc alineado con Tooltip/MenuContent** (#165, PR #127). `ToastProvider.container` documenta el patrón Dialog-nesting (top-layer del browser) + ejemplo Dialog-ref + referencia a `docs/decisions/C-02-modal-tooltip-portal.md`. Cierra la asimetría documental entre los 3 componentes que aceptan `container`.
|
|
206
|
+
|
|
207
|
+
### Fixed
|
|
208
|
+
|
|
209
|
+
- **`dist/cn.d.ts` huérfano eliminado del tarball** (#159, PR #123). El `vite-plugin-dts` emite un `.d.ts` por cada entry, pero `exports` field apunta a `./dist/utils/cn.d.ts` (tsc-emitted, con sourcemap). Helper `rmFile` añadido a `scripts/clean-internal-dist.mjs`.
|
|
210
|
+
|
|
211
|
+
- **Dev-vulns devDep cero** (#163, PR #124). `npm audit` reportaba 3 vulns (`brace-expansion`, `tmp`, `ws`) en devDeps. Verificado `npm audit --omit=dev` = 0 vulns pre/post-fix (runtime nunca afectado). Bumps: brace-expansion 5.0.5 → 5.0.6, tmp 0.2.5 → 0.2.7, ws 8.20.0 → 8.21.0. 0 cambios en `package.json` (solo lockfile).
|
|
212
|
+
|
|
213
|
+
### Documentation
|
|
214
|
+
|
|
215
|
+
- **Peer `@floating-ui/react ^0.27` pre-1.0 risk** (#156, PR #128). Sección nueva en README sobre la semántica especial del caret (^) en versiones 0.x: `^0.27.0` matchea SOLO `0.27.x` (NO `0.28.0`). Documenta la política del DS (mantener `^0.27.0` cerrado hasta verify FUI 0.28 compat) + recomendación al consumer (pinear `~0.27.0` si usa FUI directamente). Alineado con `docs/decisions/D10-fui-peer-dep-verify.md`.
|
|
216
|
+
|
|
217
|
+
### EOPT widening — fronteras (consumer-facing)
|
|
218
|
+
|
|
219
|
+
El widening `?: T | undefined` de #155 cubre **307 props del DS**. Hay 2 clases NO cubiertas que el consumer EOPT debe conocer.
|
|
220
|
+
|
|
221
|
+
#### 1. Discriminantes con literal `?: undefined`
|
|
222
|
+
|
|
223
|
+
`MenuItem.href`, `SidebarItem.href`, `NavbarBrand.AsDiv.href`. NO ensanchadas porque rompería el discriminated union — son los literal `undefined` que activan la rama `button`/`div`:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<MenuItem href={undefined}>Acción button</MenuItem>
|
|
227
|
+
<MenuItem href="/perfil">Acción anchor</MenuItem>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### 2. Exclusiones con `?: never`
|
|
231
|
+
|
|
232
|
+
8 props del inventory `scripts/eopt-classify.mjs --json` marcadas `OUT_OF_SCOPE_NEVER`. NO ensanchadas porque `never` es el mecanismo de exclusión de variantes en discriminated unions.
|
|
233
|
+
|
|
234
|
+
Inventario completo + clasificación AST en `scripts/eopt-classify.mjs`. Ver PR #114 / commit `9bdaf9a` del widening.
|
|
235
|
+
|
|
236
|
+
## [1.0.0-beta.25] — 2026-05-25 (gate review cruce cycle complete)
|
|
237
|
+
|
|
238
|
+
Plan **claudegate4** + cruce de gate reviews independientes (GPT Codex +
|
|
239
|
+
Claude Opus 4.7 v2 + Opus 4.7 genérico) cierra los 5 blockers + 2 HIGH del
|
|
240
|
+
cruce sobre `1.0.0-beta.24`. 8 PRs mergeados (#96–#101 + #103), 2 fallos
|
|
241
|
+
WCAG cerrados (1.4.11 AA + 1.4.1 A), 2 codex P1/P2 cazados sobre mis
|
|
242
|
+
propios fixes, 1 hallazgo nuevo (alias `@/` en `.d.ts`) descubierto y
|
|
243
|
+
resuelto durante el ciclo, 1 decision doc nuevo (D12 i18n).
|
|
244
|
+
|
|
245
|
+
### Added
|
|
246
|
+
|
|
247
|
+
- **`Rating.getStarLabel` prop**: override del aria-label por estrella
|
|
248
|
+
individual (`(n: number) => string`). Cierra el hueco i18n
|
|
249
|
+
detectado en el gate review beta.25 — el label del grupo ya era
|
|
250
|
+
overrideable vía `aria-label`, los hijos no lo eran. Default ES
|
|
251
|
+
permanece (`"1 estrella"` / `"N estrellas"`). Additive, no breaking.
|
|
252
|
+
|
|
253
|
+
### Changed
|
|
254
|
+
|
|
255
|
+
- **i18n formalización (D12)**: defaults ES quedan registrados como
|
|
256
|
+
decisión arquitectónica en `docs/decisions/D12-es-defaults-i18n.md`.
|
|
257
|
+
Tabla de overrides en `docs/CSSAPI.mdx` ampliada con Avatar,
|
|
258
|
+
SkeletonContainer y Rating (faltaban). Comments
|
|
259
|
+
`// i18n: ES default deliberado (D12). Override: {prop}.` añadidos
|
|
260
|
+
junto a cada string ES default en código (Alert, Chip, DialogClose,
|
|
261
|
+
Pagination, Progress, Stepper, Toast, Avatar, Skeleton, Rating).
|
|
262
|
+
No cambia comportamiento — solo descubribilidad para auditorías
|
|
263
|
+
futuras.
|
|
264
|
+
- **Rating contraste empty (gate review beta.25 HIGH)**: el estado
|
|
265
|
+
empty se comunicaba via `opacity: 0.32` sobre el color filled. Codex
|
|
266
|
+
midió en igoded.es 1.885:1 light / 2.165:1 dark — ambos `< 3:1`
|
|
267
|
+
(WCAG 1.4.11 para UI components). La opacity también atenuaba el
|
|
268
|
+
focus-ring del button focusable. Reemplazo: capa semántica de
|
|
269
|
+
foreground atenuado (`.ig-star { color: var(--ig-text-muted) }`),
|
|
270
|
+
que pasa ≥3:1 en ambos temas (light 6.67:1, dark 5.90:1) y deja el
|
|
271
|
+
focus-ring intacto al 100% contraste.
|
|
272
|
+
- **Rating canal de forma (issue #102, WCAG 1.4.1 nivel A)**: la
|
|
273
|
+
diferenciación filled/empty viajaba solo por hue (rutilus cálido vs
|
|
274
|
+
text-muted purple-gray, luminancia casi idéntica → 1.85:1 light /
|
|
275
|
+
1.88:1 dark). En daltonismo, escala de grises o `forced-colors:
|
|
276
|
+
active`, las 5 estrellas se ven iguales — el rating deja de
|
|
277
|
+
comunicar su valor. Reemplazo: glifo state-driven via `::before
|
|
278
|
+
{ content }` — filled `★` (U+2605 BLACK STAR), empty `☆` (U+2606
|
|
279
|
+
WHITE STAR). Dos canales independientes (forma + color),
|
|
280
|
+
ortogonales: sobrevive a forced-colors por code point distinto.
|
|
281
|
+
Cambio sintáctico de la API CSS-only: el consumer ya NO escribe el
|
|
282
|
+
carácter dentro del `<span class="ig-star">` (queda vacío + recomendado
|
|
283
|
+
`aria-hidden="true"`); el glifo lo aporta CSS. Markup React JSX no
|
|
284
|
+
cambia (Rating sigue rendering el button, ahora sin children).
|
|
285
|
+
|
|
286
|
+
### Removed
|
|
287
|
+
|
|
288
|
+
- **`--ig-rating-empty` (token público)**: muerto desde beta.24 (la
|
|
289
|
+
implementación lo abandonó por opacity-based). Sin consumers reales
|
|
290
|
+
pre-1.0.0, lo eliminamos para reducir surface API. La asimetría con
|
|
291
|
+
`--ig-rating-filled` (que sí queda público porque permite override
|
|
292
|
+
del cardinal `rutilus`) es deliberada: el empty consume la capa
|
|
293
|
+
semántica de foreground atenuado (`--ig-text-muted`) por ser un
|
|
294
|
+
color flat sin justificación de customización individual.
|
|
295
|
+
|
|
296
|
+
## [1.0.0-beta.24] — 2026-05-19 (D1 server-safe infrastructure complete)
|
|
297
|
+
|
|
298
|
+
Plan **claudegate3** (post-RC1 gate review) cierra el bloque D1 con
|
|
299
|
+
4 sub-tasks. Pre-tag de `1.0.0-rc.1` queda únicamente FREEZE-CHECK.
|
|
300
|
+
|
|
301
|
+
### Added
|
|
302
|
+
|
|
303
|
+
**`DialogAction` — botón unstyled para CTAs del footer del Dialog**:
|
|
304
|
+
|
|
305
|
+
Mirror exacto de `AlertDialogClose` para la familia Dialog. Cierra el
|
|
306
|
+
modal via contexto (`setOpen(false)`) pero NO aplica ninguna clase
|
|
307
|
+
base. Diseñado para botones de acción del `DialogFooter` (Cancelar,
|
|
308
|
+
Aceptar, Entendido, etc.) donde el consumer pasa `className="ig-btn
|
|
309
|
+
ig-btn-..."` y espera que esa sea la única clase visual.
|
|
310
|
+
|
|
311
|
+
**Por qué se añade ahora**: usar `<DialogClose className="ig-btn
|
|
312
|
+
ig-btn-brand">Aceptar</DialogClose>` mezclaba la clase base de
|
|
313
|
+
`DialogClose` (`ig-dialog-close`, que fuerza `2rem × 2rem`, `padding: 0`
|
|
314
|
+
para la "×" del header) con la clase del Button del DS → footer
|
|
315
|
+
descuadrado. `AlertDialogClose` ya tenía esta forma desde D8 (codex P1
|
|
316
|
+
sobre PR #87) pero el Dialog quedó sin el equivalente, así que el
|
|
317
|
+
patrón natural del consumer producía un visual roto.
|
|
318
|
+
|
|
319
|
+
Migración consumer (no breaking, paralelo a DialogClose):
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
// Antes (footer descuadrado por clases en conflicto):
|
|
323
|
+
<DialogFooter>
|
|
324
|
+
<DialogClose className="ig-btn ig-btn-secondary">Cancelar</DialogClose>
|
|
325
|
+
<DialogClose className="ig-btn ig-btn-brand">Aceptar</DialogClose>
|
|
326
|
+
</DialogFooter>
|
|
327
|
+
|
|
328
|
+
// Ahora (sizing del Button respetado):
|
|
329
|
+
<DialogFooter>
|
|
330
|
+
<DialogAction className="ig-btn ig-btn-secondary">Cancelar</DialogAction>
|
|
331
|
+
<DialogAction className="ig-btn ig-btn-brand">Aceptar</DialogAction>
|
|
332
|
+
</DialogFooter>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`<DialogClose />` queda como el botón "×" del header (sigue intocado).
|
|
336
|
+
Stories actualizadas (Default + Information). Tests añadidos: cierre
|
|
337
|
+
via context, no aplica `ig-dialog-close`, `onClick.preventDefault()`
|
|
338
|
+
bloquea el cierre.
|
|
339
|
+
|
|
340
|
+
### Fixed
|
|
341
|
+
|
|
342
|
+
**Rating — distinción visual filled/empty robusta + contraste light mode**:
|
|
343
|
+
|
|
344
|
+
Patrón canónico Material UI / Mantine / shadcn: ambos estados usan
|
|
345
|
+
el mismo glifo `★` solid; la diferenciación es por **opacity** sobre
|
|
346
|
+
el mismo color base (`--ig-rating-filled`):
|
|
347
|
+
|
|
348
|
+
- Vacía: ★ a opacity 0.32 sobre `--ig-rating-filled`.
|
|
349
|
+
- Llena: ★ a opacity 1.
|
|
350
|
+
|
|
351
|
+
Por qué este patrón:
|
|
352
|
+
|
|
353
|
+
- El glifo solid (`★`) tiene antialiasing más visible que el outline
|
|
354
|
+
(`☆`) sobre fondos near-white. En light mode el ☆ outline en
|
|
355
|
+
`--ig-rating-empty` (#685080 sobre #faf9fc) leía casi como bg.
|
|
356
|
+
- Mantener el mismo color base preserva el matiz coherente (cobre
|
|
357
|
+
rutilus) en ambos estados. La opacity baja conserva el hue sin
|
|
358
|
+
desaturarlo, evitando blend con el fondo.
|
|
359
|
+
- La diferencia visual filled vs empty es ahora ratio 3:1 (1.0 vs
|
|
360
|
+
0.32), independiente del tema activo.
|
|
361
|
+
|
|
362
|
+
Limpieza adicional: removidas reglas CSS-only que duplicaban el hover
|
|
363
|
+
preview (`.ig-rating:not(.ig-rating-readonly):hover .ig-star` y
|
|
364
|
+
`.ig-rating:not(.ig-rating-readonly) .ig-star:hover ~ .ig-star`).
|
|
365
|
+
Eran redundantes — `setHover` en JSX ya aplica `ig-star-filled` a las
|
|
366
|
+
estrellas en el preview. La regla con sibling combinator `~` tenía
|
|
367
|
+
specificity (0,5,0) que sobreescribía el estado, produciendo
|
|
368
|
+
visual inconsistente cuando el cursor permanecía sobre el rating
|
|
369
|
+
tras el click. Ahora el `ig-star-filled` class (estado JSX) es la
|
|
370
|
+
única fuente de verdad.
|
|
371
|
+
|
|
372
|
+
`--ig-rating-empty` token mantenido en `igoded-tokens.css` por
|
|
373
|
+
backward-compat con consumers que lo extendieran, pero el CSS del
|
|
374
|
+
componente ya no lo referencia.
|
|
375
|
+
|
|
376
|
+
### Added (beta.24)
|
|
377
|
+
|
|
378
|
+
**D1-P3 — `reactigoded/server-safe` entry + `react-server` conditional export**:
|
|
379
|
+
|
|
380
|
+
Nuevo subpath export `reactigoded/server-safe` + condicional automático
|
|
381
|
+
`"react-server"` en `package.json#exports["."]` que re-exporta solo los
|
|
382
|
+
36 componentes marcados `@server-safe` (verificados por el gate
|
|
383
|
+
`test:server-safe-markers`).
|
|
384
|
+
|
|
385
|
+
Consumer pattern (Next.js App Router Server Component, Astro server
|
|
386
|
+
island, Remix loader, etc.):
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
// app/page.tsx (server, sin "use client")
|
|
390
|
+
import { Button, CardBody, CardHeader } from "reactigoded";
|
|
391
|
+
// → resuelve a dist/server-safe.js via react-server condition
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Componentes interactivos (Dialog/Menu/Toast Provider/Tooltip/Switch/
|
|
395
|
+
ThemeToggle) siguen importables desde el barrel root cuando el
|
|
396
|
+
consumer está en Client Component (`"use client"`).
|
|
397
|
+
|
|
398
|
+
CI gates añadidos:
|
|
399
|
+
|
|
400
|
+
- `test:publint`: validador de exports config (sustituye attw temporalmente
|
|
401
|
+
por bug fflate en ARM64 — ver `docs/known-issues.md`).
|
|
402
|
+
- `test:rsc-fixture`: `tsc -p fixtures/rsc/tsconfig.json --noEmit` con
|
|
403
|
+
`customConditions: ["react-server"]` + paths a `dist/server-safe.d.ts`.
|
|
404
|
+
Cubre regresiones del subset server-safe.
|
|
405
|
+
- `test:server-safe-markers` (retroactivo de D1-P1): añadido al
|
|
406
|
+
workflow `verify.yml` que antes solo lo corría localmente.
|
|
407
|
+
|
|
408
|
+
Sin cambios breaking — el barrel root sigue exportando todos los componentes.
|
|
409
|
+
|
|
410
|
+
### Changed (BREAKING — pre-RC1, beta.24)
|
|
411
|
+
|
|
412
|
+
**D1-P4 — ESM-only package**:
|
|
413
|
+
|
|
414
|
+
reactigoded ahora es **ESM-only**. La rama CJS de `package.json#exports`
|
|
415
|
+
queda eliminada, junto con los emits `dist/*.cjs` y los campos `main`
|
|
416
|
+
+ `module` del manifest. El paquete sigue declarando `"type": "module"`
|
|
417
|
+
(introducido en beta.22).
|
|
418
|
+
|
|
419
|
+
Migración consumer:
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
// Antes (CJS, ya no soportado):
|
|
423
|
+
const { Button } = require("reactigoded");
|
|
424
|
+
|
|
425
|
+
// Ahora (ESM):
|
|
426
|
+
import { Button } from "reactigoded";
|
|
427
|
+
|
|
428
|
+
// Si necesitas usarlo desde un CJS file legacy, Node ≥22 soporta
|
|
429
|
+
// require() sincrónico de ESM nativamente:
|
|
430
|
+
const { Button } = require("reactigoded");
|
|
431
|
+
|
|
432
|
+
// O dynamic import:
|
|
433
|
+
const { Button } = await import("reactigoded");
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
`package.json#exports` simplificado:
|
|
437
|
+
|
|
438
|
+
```json
|
|
439
|
+
{
|
|
440
|
+
".": {
|
|
441
|
+
"react-server": {
|
|
442
|
+
"types": "./dist/server-safe.d.ts",
|
|
443
|
+
"default": "./dist/server-safe.js"
|
|
444
|
+
},
|
|
445
|
+
"default": {
|
|
446
|
+
"types": "./dist/index.d.ts",
|
|
447
|
+
"default": "./dist/index.js"
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
"./server-safe": { "types": "...", "default": "./dist/server-safe.js" },
|
|
451
|
+
"./cn": { "types": "...", "default": "./dist/cn.js" }
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Razones técnicas:
|
|
456
|
+
|
|
457
|
+
- **Coherencia**: el DS ya era ESM-first (`"type": "module"` desde beta.22),
|
|
458
|
+
el output CJS era un fallback que pocos consumers usaban.
|
|
459
|
+
- **publint clean**: el warning `pkg.exports["."].require.types` sobre
|
|
460
|
+
types ESM interpretadas en condición require queda eliminado (no más
|
|
461
|
+
rama `require`).
|
|
462
|
+
- **Bundle output**: menos artefactos en `dist/` (no `*.cjs`), tamaño
|
|
463
|
+
total del paquete publicado más ligero.
|
|
464
|
+
- **Tooling moderno**: Vite, Next.js > 13, Astro, Remix, Bun, Deno
|
|
465
|
+
soportan ESM nativamente. Node 22 LTS (mínimo del DS desde
|
|
466
|
+
`"engines": { "node": ">=22.12.0" }`) soporta `require()` sincrónico
|
|
467
|
+
de ESM.
|
|
468
|
+
|
|
469
|
+
CI workflow actualizado: `dist/index.cjs` removido del check de
|
|
470
|
+
"Bundle has no dev warns".
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
**D1-P2 — clsx promoted from dependency to peerDependency**:
|
|
475
|
+
|
|
476
|
+
`clsx` (^2.1.0) ahora es **peer-dependency obligatoria**. Antes era una
|
|
477
|
+
dep regular bundleada — el consumer tenía `clsx` instalada de forma
|
|
478
|
+
implícita. A partir de beta.24, el consumer debe instalarla
|
|
479
|
+
explícitamente en su árbol de deps.
|
|
480
|
+
|
|
481
|
+
Migración:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
npm install clsx@^2.1.0
|
|
485
|
+
# o
|
|
486
|
+
pnpm add clsx@^2.1.0
|
|
487
|
+
# o
|
|
488
|
+
yarn add clsx@^2.1.0
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Razones técnicas:
|
|
492
|
+
|
|
493
|
+
- **Deduplicación**: la mayoría de consumers (apps con Tailwind, shadcn/ui,
|
|
494
|
+
proyectos existentes que ya combinan classNames) ya tienen `clsx` en su
|
|
495
|
+
árbol. Bundle-arla en reactigoded duplica el módulo en runtime sin valor
|
|
496
|
+
funcional.
|
|
497
|
+
- **Bundle size**: el ESM principal pierde ~500B gz, el CJS un poco más.
|
|
498
|
+
- **Coherencia D1 server-safe**: con `@server-safe` enforced en beta.24,
|
|
499
|
+
los chunks publicados están auditados — externalizar peer-deps refuerza
|
|
500
|
+
que el árbol de bundling sea predecible para SSR/RSC consumers.
|
|
501
|
+
|
|
502
|
+
Decisión documentada inline en `package.json#peerDependencies` y
|
|
503
|
+
`vite.lib.config.ts#rollupOptions.external`. La decisión previa
|
|
504
|
+
(beta.20 "clsx bundleado permanente") queda invalidada por el plan D1.
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
**D2 + D7 — Menu Full FUI portal + namespace reorg** (B1-PR3):
|
|
509
|
+
|
|
510
|
+
- **Menu file move**: `src/components/Menu/` → `src/components/floating/Menu/`.
|
|
511
|
+
Public API root barrel `reactigoded` sin cambio (símbolo `Menu` se
|
|
512
|
+
exporta idéntico). Internal cross-component imports actualizados.
|
|
513
|
+
- **Menu Full FUI portal**: `<FloatingPortal>` + `floatingStyles` inline +
|
|
514
|
+
`data-side` / `data-align` / `data-state` attributes (split Radix-style).
|
|
515
|
+
Unmount-on-close (no más CSS-hidden).
|
|
516
|
+
- **Modifier classes eliminadas**: `.ig-menu-right`, `.ig-menu-up`,
|
|
517
|
+
`.ig-menu-open`, `:focus-within` JS-less fallback. Consumers que las
|
|
518
|
+
stylizaban en su CSS deben migrar a `[data-side]` / `[data-align]`
|
|
519
|
+
attribute selectors en MenuContent.
|
|
520
|
+
- **Menu requires JS**: alineado con Tooltip/Dialog/Accordion. No más
|
|
521
|
+
CSS-only fallback `:focus-within`.
|
|
522
|
+
- **floating/ namespace agrupado**: `src/components/floating/index.ts`
|
|
523
|
+
nuevo barrel re-exporta `./primitives + ./Tooltip + ./Menu`. En
|
|
524
|
+
`src/components/index.ts` los 3 wildcards floating sueltos se colapsan
|
|
525
|
+
a uno (`export * from "./floating"`). Sin cambio para consumer.
|
|
526
|
+
- **useFloatingNode internal definitive**: retirado del wildcard
|
|
527
|
+
re-export de `floating/primitives/index.ts`. JSDoc declaraba @example
|
|
528
|
+
interno pero export * lo exponía. Internal consumers ya importan via
|
|
529
|
+
path directo `@/components/floating/primitives/useFloatingNode`.
|
|
530
|
+
Consumers que (accidentalmente) lo importaban desde `"reactigoded"`
|
|
531
|
+
deben removerlo — uso público era unintended.
|
|
532
|
+
|
|
533
|
+
Decision docs: `docs/decisions/D2-menu-portal.md`,
|
|
534
|
+
`docs/decisions/D7-floating-namespace.md`,
|
|
535
|
+
`src/components/floating/README.md`. C-03 doc status actualizado
|
|
536
|
+
"diferida a 1.1.0" → "DONE en beta.24".
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
C.2 DS-wide rename pattern: callbacks aligned al prop name local cuando
|
|
541
|
+
el prop tiene nombre de dominio (no "value" arbitrario). Sub-patrón DS
|
|
542
|
+
articulado en `docs/decisions/D3-callback-rename-cw.md` y
|
|
543
|
+
`docs/decisions/D4-sidebar-api.md`.
|
|
544
|
+
|
|
545
|
+
**Pagination** — prop rename + callback rename:
|
|
546
|
+
- `currentPage?: number` → `page?: number`.
|
|
547
|
+
- `defaultPage` queda.
|
|
548
|
+
- `onValueChange?: (page: number) => void` →
|
|
549
|
+
`onPageChange?: (page: number) => void`.
|
|
550
|
+
- Param signature `(page: number)` queda.
|
|
551
|
+
|
|
552
|
+
Migration consumer:
|
|
553
|
+
```diff
|
|
554
|
+
- <Pagination currentPage={p} onValueChange={setP} totalPages={N} />
|
|
555
|
+
+ <Pagination page={p} onPageChange={setP} totalPages={N} />
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Stepper** — callback rename solo:
|
|
559
|
+
- Prop `active` queda. Callback `onValueChange` → `onActiveChange`.
|
|
560
|
+
- Param signature `(next: number)` queda.
|
|
561
|
+
|
|
562
|
+
```diff
|
|
563
|
+
- <Stepper active={s} onValueChange={setS}>...</Stepper>
|
|
564
|
+
+ <Stepper active={s} onActiveChange={setS}>...</Stepper>
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**ThemeToggle** — callback rename solo:
|
|
568
|
+
- Props `theme` + `defaultTheme` quedan. Callback `onValueChange` →
|
|
569
|
+
`onThemeChange`.
|
|
570
|
+
|
|
571
|
+
```diff
|
|
572
|
+
- <ThemeToggle theme={t} onValueChange={setT} />
|
|
573
|
+
+ <ThemeToggle theme={t} onThemeChange={setT} />
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**Sidebar** — callback rename + SidebarItem a11y refactor:
|
|
577
|
+
- Props `collapsed` + `defaultCollapsed` quedan. Callback `onValueChange`
|
|
578
|
+
→ `onCollapsedChange`.
|
|
579
|
+
- CSS class `.ig-sidebar-collapsed` queda. ARIA `aria-expanded={!collapsed}`
|
|
580
|
+
queda (encapsulado en SidebarToggle).
|
|
581
|
+
- **SidebarItem** ahora consume `useSidebar()` context para aplicar
|
|
582
|
+
`aria-label` condicionalmente: aplicado solo en estado collapsed
|
|
583
|
+
(ARIA APG fix — eliminado anti-pattern de nombre accesible duplicado
|
|
584
|
+
en estado expanded).
|
|
585
|
+
- **SidebarItem pasa a `"use client"`** (consume context). Sale de
|
|
586
|
+
candidatos `@server-safe` D1 P1.
|
|
587
|
+
- **SidebarItem REQUIERE estar dentro de `<Sidebar>`** (D11.4 regla
|
|
588
|
+
DS-wide: hooks que requieren ancestor lanzan).
|
|
589
|
+
|
|
590
|
+
`useSidebar` JSDoc reescrito eliminando `@example PersistSidebar`
|
|
591
|
+
(anti-pattern declaraba uso público mientras bundle no expone el hook
|
|
592
|
+
desde B-04 RC1). Pointer a README sección "Persisting Sidebar state"
|
|
593
|
+
(a añadir en B1-PR1 sweep) para patrón consumer-facing con controlled
|
|
594
|
+
mode external + guard SSR-safe en localStorage.
|
|
595
|
+
|
|
596
|
+
**Componentes aligned (sin cambios)**: Accordion, Rating, Slider, Tabs
|
|
597
|
+
— sus props se llaman `value`, callbacks `onValueChange` son aligned
|
|
598
|
+
localmente.
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
**D6 — Dialog Full compound refactor** (B2-PR2):
|
|
603
|
+
|
|
604
|
+
**BREAKING**. `Dialog` pasa de ser un componente monolítico (era el
|
|
605
|
+
`<dialog>` HTML nativo con children directos) a Provider compound
|
|
606
|
+
Radix-style. La lógica del modal real vive en el nuevo `<DialogContent>`;
|
|
607
|
+
`<DialogTrigger>` (nuevo) abre el modal con ARIA disclosure correcto.
|
|
608
|
+
|
|
609
|
+
API nueva:
|
|
610
|
+
|
|
611
|
+
```tsx
|
|
612
|
+
<Dialog
|
|
613
|
+
open?={x} // controlled (opcional, era obligatorio)
|
|
614
|
+
defaultOpen?={false} // uncontrolled (nuevo)
|
|
615
|
+
onOpenChange?={fn}
|
|
616
|
+
>
|
|
617
|
+
<DialogTrigger>Abrir</DialogTrigger> // nuevo, aria-haspopup/-controls/-expanded
|
|
618
|
+
<DialogContent // nuevo, el <dialog> real
|
|
619
|
+
size? backdrop? closeOnBackdrop?
|
|
620
|
+
closeOnEsc? loading? ref? className?
|
|
621
|
+
>
|
|
622
|
+
<DialogHeader />
|
|
623
|
+
<DialogBody />
|
|
624
|
+
<DialogClose /> // ahora auto-cierra via contexto
|
|
625
|
+
</DialogContent>
|
|
626
|
+
</Dialog>
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Migration mecánica** consumer:
|
|
630
|
+
|
|
631
|
+
```diff
|
|
632
|
+
- <Dialog open={x} onOpenChange={fn} size="md">
|
|
633
|
+
- <DialogHeader />
|
|
634
|
+
- <DialogBody>texto</DialogBody>
|
|
635
|
+
- <DialogClose onClick={() => setOpen(false)} />
|
|
636
|
+
- </Dialog>
|
|
637
|
+
+ <Dialog open={x} onOpenChange={fn}>
|
|
638
|
+
+ <DialogContent size="md">
|
|
639
|
+
+ <DialogHeader />
|
|
640
|
+
+ <DialogBody>texto</DialogBody>
|
|
641
|
+
+ <DialogClose />
|
|
642
|
+
+ </DialogContent>
|
|
643
|
+
+ </Dialog>
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Opcional: para casos sencillos, eliminar `useState` externo + `Button`
|
|
647
|
+
+ `onClick` y usar `<DialogTrigger>` + `defaultOpen`:
|
|
648
|
+
|
|
649
|
+
```diff
|
|
650
|
+
- const [open, setOpen] = useState(false);
|
|
651
|
+
- <Button onClick={() => setOpen(true)}>Abrir</Button>
|
|
652
|
+
- <Dialog open={open} onOpenChange={setOpen}>
|
|
653
|
+
- <DialogHeader />
|
|
654
|
+
- </Dialog>
|
|
655
|
+
+ <Dialog defaultOpen={false}>
|
|
656
|
+
+ <DialogTrigger className="ig-btn ig-btn-brand">Abrir</DialogTrigger>
|
|
657
|
+
+ <DialogContent>
|
|
658
|
+
+ <DialogHeader />
|
|
659
|
+
+ </DialogContent>
|
|
660
|
+
+ </Dialog>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Cambios técnicos:
|
|
664
|
+
- `Dialog` ahora es Provider puro (sin DOM). `useControllableState`
|
|
665
|
+
interno con `SUPPRESS_NO_HANDLER_WARN` en modo controlled-presentational
|
|
666
|
+
(`active` sin callback, decisión consciente del consumer).
|
|
667
|
+
- `DialogContent` hereda TODAS las props visuales antiguas de `Dialog`:
|
|
668
|
+
`size`, `backdrop`, `closeOnBackdrop`, `closeOnEsc`, `loading`, `ref`,
|
|
669
|
+
`className`, `onPointerDown`/`onClick` chained. H-02 drag-out parity
|
|
670
|
+
preservado.
|
|
671
|
+
- `DialogTrigger` es `<button>` plano con `aria-haspopup="dialog"` +
|
|
672
|
+
`aria-controls={contentId}` + `aria-expanded={open}` automáticos.
|
|
673
|
+
Chainea `onClick` consumer; `preventDefault` bloquea apertura.
|
|
674
|
+
- `DialogClose` consume el contexto y llama `setOpen(false)` automático.
|
|
675
|
+
Sigue siendo tolerante fuera de `<Dialog>` (no rompe, simplemente no
|
|
676
|
+
cierra nada — responsabilidad del consumer via `onClick`).
|
|
677
|
+
- Alias backward-compat de tipos: `DialogSize` y `DialogBackdrop` ahora
|
|
678
|
+
son alias de `DialogContentSize` / `DialogContentBackdrop`. Consumers
|
|
679
|
+
que importaban los tipos del barrel siguen funcionando.
|
|
680
|
+
- Prop `onClose` (deprecated en B-02 RC1) sigue disparando en cierres
|
|
681
|
+
via wrapper en setOpen; eliminada en 2.0.
|
|
682
|
+
|
|
683
|
+
Decision doc: `docs/decisions/D6-dialog-compound.md`.
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
**D1-P1 — `@server-safe` JSDoc marker + suite SSR ejecutable** (D1 server-safe, parte 1/4):
|
|
688
|
+
|
|
689
|
+
Establece la infraestructura declarativa + ejecutable para SSR / React
|
|
690
|
+
Server Components compatibility. Sin breaking — solo añade marker
|
|
691
|
+
JSDoc + gate script + double-render test.
|
|
692
|
+
|
|
693
|
+
- **JSDoc `@server-safe`** en 36 componentes — todos los archivos sin
|
|
694
|
+
`"use client"` directive en `src/components/`. Includes:
|
|
695
|
+
`Avatar/AvatarGroup`, `Badge`, `Breadcrumb/BreadcrumbItem`,
|
|
696
|
+
`Button`/`IconButton`, `Card`+subcomponentes, `Chip`, `Divider`,
|
|
697
|
+
`Dialog/DialogBody/DialogFooter`, `Input`+`Label`/`Helper`/etc.,
|
|
698
|
+
`Navbar`+subcomponentes, `Progress`, `Radio`, `Sidebar` (subs
|
|
699
|
+
presentacionales), `Skeleton`, `Spinner`, `Stepper`/`Step`, `Table`,
|
|
700
|
+
`Timeline`/`TimelineItem`, `Toast` (item; el Provider es client).
|
|
701
|
+
- **Gate** `scripts/check-server-safe-markers.mjs` +
|
|
702
|
+
`npm run test:server-safe-markers` (encadenado en `verify:unit`):
|
|
703
|
+
enforza que archivos `@server-safe` NO declaren `"use client"` y NO
|
|
704
|
+
accedan a `document.X`/`window.X`/`navigator.X`/`process.X`/`Buffer.X`
|
|
705
|
+
sin guard `typeof X !== "undefined"`.
|
|
706
|
+
- **Test double-render idempotence** en `__ssr__.test.tsx`: dos
|
|
707
|
+
invocaciones de `renderToString(jsx)` con el mismo JSX producen el
|
|
708
|
+
mismo HTML. Skip components con `useId` (Accordion, Dialog, Menu,
|
|
709
|
+
Sidebar, Tabs, Tooltip, Toast) por semántica de `useId` entre
|
|
710
|
+
invocaciones independientes.
|
|
711
|
+
- Decision doc: `docs/decisions/D1-P1-server-safe-marker.md`.
|
|
712
|
+
|
|
713
|
+
Componentes adicionales se marcarán incrementalmente post-RC1 conforme
|
|
714
|
+
se audite cada uno.
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
**MEDIUMs consolidados beta.24** (B3-PR1):
|
|
719
|
+
|
|
720
|
+
Batch de fixes individuales identificados en el gate review claudegate3
|
|
721
|
+
sección IV (EXC-A1/A2). Atómicos para bisect-friendliness — un commit
|
|
722
|
+
por finding:
|
|
723
|
+
|
|
724
|
+
- **M-04 (Tooltip.extractText)**: investigado y descartado tras
|
|
725
|
+
análisis (codex P2 round 2 sobre PR #89). El finding original asumía
|
|
726
|
+
que `join("")` rompía spacing en `<>Hello <b>World</b>!</>`, pero
|
|
727
|
+
JSX YA preserva los espacios en los strings literales adyacentes
|
|
728
|
+
(`children = ["Hello ", <b>"World"</b>, "!"]`). Inyectar `" "` rompía
|
|
729
|
+
casos legítimos de tokens contiguos (`<>v<code>1</code>.<code>2</code></>`
|
|
730
|
+
→ "v 1 . 2" en lugar de "v1.2"). El behavior actual `join("")` es
|
|
731
|
+
correcto — el consumer es dueño de los espacios. Añadido test
|
|
732
|
+
regression guard "preserva adjacencia de tokens".
|
|
733
|
+
- **M-05 (Label required)**: el asterisco usaba `style={{ marginLeft: 4 }}`
|
|
734
|
+
inline (CSP issue). Migrado a CSS class `ig-label-required-mark` con
|
|
735
|
+
margin + color del token `--ig-malum`. **Bonus fix**: el CSS
|
|
736
|
+
`.ig-label-required::after` también renderizaba un asterisco — junto
|
|
737
|
+
al `<span>*</span>` del JSX producía DOBLE asterisco visible. Fix
|
|
738
|
+
inicial (eliminar `::after`) rompía el caso CSS-only API (HTML
|
|
739
|
+
estático sin React perdía el marker — codex P2 round 2). Fix final:
|
|
740
|
+
`:has(.ig-label-required-mark)` condiciona el `::after` a aplicar
|
|
741
|
+
SOLO cuando el span React no está, preservando ambas APIs (React +
|
|
742
|
+
CSS-only) sin doble asterisco.
|
|
743
|
+
- **M-07 (landmark registries)**: `useLandmarkRegistry` +
|
|
744
|
+
`useTopLevelLandmarkCheck` mantienen estado module-level (Map y
|
|
745
|
+
contador). Sin reset entre tests acumulaban entries. Los helpers
|
|
746
|
+
`__reset*ForTests` existían pero solo se invocaban manualmente en
|
|
747
|
+
sus tests dedicados. Centralizados en `src/test/setup.ts` `afterEach`
|
|
748
|
+
para que todos los tests hereden el cleanup.
|
|
749
|
+
- **M-10 (D-02 doc grep)**: `docs/decisions/D-02-forwardref-migration-done.md`
|
|
750
|
+
afirmaba `grep -rn "forwardRef" → vacío esperado` pero realidad
|
|
751
|
+
muestra 11 matches (dev-warn references + test fixtures, no DS-side
|
|
752
|
+
usage). Refinado al call-form `grep -rn "forwardRef("` que sí
|
|
753
|
+
retorna 0 y captura el invariante real ("ningún componente del DS
|
|
754
|
+
INVOCA forwardRef").
|
|
755
|
+
|
|
756
|
+
Findings descartados sin cambio (audit confirmó OK):
|
|
757
|
+
- **M-06 (useA11yWarnInput)**: hook ya robusto.
|
|
758
|
+
- **M-08 (Toast timers)**: cleanup ya correcto via `afterEach`.
|
|
759
|
+
- **M-09 (Twin act() warning)**: no encontrado en logs.
|
|
760
|
+
- **M-11 (C-03 doc DONE)**: ya marcado tras D2/D7 beta.24.
|
|
761
|
+
- **M-13 (Switch indeterminate)**: ya conforme H-15 WAI-ARIA 1.2.
|
|
762
|
+
- **M-14 (Stepper test consolidation)**: refactor cosmético; deferido
|
|
763
|
+
a iteración futura — los tests cubren regresiones reales (codex P1+P2
|
|
764
|
+
históricos) y consolidar pierde trazabilidad por marginal LOC saving.
|
|
765
|
+
|
|
766
|
+
Verify cold: 836/836 sin regresión.
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
**README sweep masivo post-beta.24** (B1-PR1):
|
|
771
|
+
|
|
772
|
+
`README.md` y `docs/CSSAPI.mdx` tenían drift acumulado de los renames
|
|
773
|
+
y refactors de beta.24. Sweep sistemático:
|
|
774
|
+
|
|
775
|
+
- **Pagination API** (D3): `currentPage`/`onValueChange` → `page`/
|
|
776
|
+
`onPageChange` en README sección "Controlled vs uncontrolled" y
|
|
777
|
+
CSSAPI.mdx § Pagination.
|
|
778
|
+
- **Dialog compound** (D6): tabla de componentes, ejemplos de uso,
|
|
779
|
+
sección SSR/hydration, CSS API. `Modal*` → `Dialog*` / `DialogContent`
|
|
780
|
+
/ `DialogTrigger` / etc. La línea "Modal es controlled-only" eliminada
|
|
781
|
+
(ya no aplica: Dialog soporta uncontrolled con DialogTrigger).
|
|
782
|
+
- **AlertDialog family** (D8): añadido a tabla de componentes + tabla
|
|
783
|
+
CSS API + sección SSR (`role="alertdialog"` + `closeOnBackdrop=false`
|
|
784
|
+
default mencionado).
|
|
785
|
+
- **Menu rename** (D2/D7): `OptionsMenu*` → `Menu*` (refactor Full FUI
|
|
786
|
+
beta.24 + namespace reorg).
|
|
787
|
+
- **Tabs subcomponentes**: `TabList`/`Tab`/`TabPanel` → `TabsList`/
|
|
788
|
+
`TabsTrigger`/`TabsContent` (alineado con exports reales del barrel).
|
|
789
|
+
- **D9 budget claim**: "~14 KB gz" → "~16 KB gz (budget 20 KB ESM con
|
|
790
|
+
headroom)".
|
|
791
|
+
- **Tabla controlled/uncontrolled DS-wide** añadida bajo § Patrones
|
|
792
|
+
recurrentes, listando los 10 componentes con la convención
|
|
793
|
+
`{prop}?` + `default{Prop}?` + `on{Prop}Change` cerrada en beta.24.
|
|
794
|
+
- **D11 hooks disposition**: sección "Hooks públicos del DS" ya
|
|
795
|
+
existía; el bloque B-04 sobre context hooks privados se renombró a
|
|
796
|
+
D11 para reflejar la disposición canónica beta.24.
|
|
797
|
+
|
|
798
|
+
Solo cambios docs. Cero runtime. Verify cold 836/836 sin regresión.
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
**D8 — AlertDialog family** (B2-PR3):
|
|
803
|
+
|
|
804
|
+
Nueva familia para confirmaciones destructivas / acciones que demandan
|
|
805
|
+
atención consciente. Hereda toda la infraestructura compound de Dialog
|
|
806
|
+
(D6); el único componente con comportamiento propio es
|
|
807
|
+
`<AlertDialogContent>`. Los demás (`AlertDialog`, `AlertDialogTrigger`,
|
|
808
|
+
`AlertDialogHeader/Body/Footer/Close`) son aliases directos de los
|
|
809
|
+
equivalentes Dialog — cero overhead, cero divergence.
|
|
810
|
+
|
|
811
|
+
```tsx
|
|
812
|
+
<AlertDialog defaultOpen={false}>
|
|
813
|
+
<AlertDialogTrigger className="ig-btn ig-btn-danger">
|
|
814
|
+
Borrar permanentemente
|
|
815
|
+
</AlertDialogTrigger>
|
|
816
|
+
<AlertDialogContent>
|
|
817
|
+
<AlertDialogHeader>
|
|
818
|
+
<h2>Confirmar borrado</h2>
|
|
819
|
+
</AlertDialogHeader>
|
|
820
|
+
<AlertDialogBody>Esta acción es irreversible.</AlertDialogBody>
|
|
821
|
+
<AlertDialogFooter>
|
|
822
|
+
<AlertDialogClose className="ig-btn ig-btn-secondary">
|
|
823
|
+
Cancelar
|
|
824
|
+
</AlertDialogClose>
|
|
825
|
+
<AlertDialogClose className="ig-btn ig-btn-danger">
|
|
826
|
+
Sí, borrar
|
|
827
|
+
</AlertDialogClose>
|
|
828
|
+
</AlertDialogFooter>
|
|
829
|
+
</AlertDialogContent>
|
|
830
|
+
</AlertDialog>
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
Diferencias de `AlertDialogContent` vs `DialogContent`:
|
|
834
|
+
|
|
835
|
+
1. **`role="alertdialog"`** (override del `role="dialog"` automático del
|
|
836
|
+
`<dialog>` HTML). WAI-ARIA APG: indica que el modal demanda atención
|
|
837
|
+
consciente del usuario.
|
|
838
|
+
2. **`closeOnBackdrop={false}` por defecto** (vs `true` en
|
|
839
|
+
`DialogContent`). Click outside NO cierra — el usuario debe pulsar
|
|
840
|
+
Cancel o Confirm explícitamente. Override por consumer si su caso
|
|
841
|
+
es más suave: `<AlertDialogContent closeOnBackdrop />`.
|
|
842
|
+
|
|
843
|
+
`closeOnEsc` sigue `true` (atajo ergonómico universal).
|
|
844
|
+
|
|
845
|
+
7 tests cubren las diferencias D8 + composición end-to-end. Decision
|
|
846
|
+
doc: `docs/decisions/D8-alert-dialog.md`. No breaking — D8 es API
|
|
847
|
+
nueva pura.
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
### Fixed (non-breaking, beta.24)
|
|
852
|
+
|
|
853
|
+
**H-03 — Progress CSP-friendly via CSS custom property** (B2-PR5):
|
|
854
|
+
|
|
855
|
+
Pre-fix, `Progress.tsx` emitía el porcentaje runtime como propiedad
|
|
856
|
+
CSS arbitraria en el style attribute:
|
|
857
|
+
|
|
858
|
+
```html
|
|
859
|
+
<div class="ig-progress-bar" style="width: 42%"></div>
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
Esto rompía CSP estricto sin `'unsafe-inline'` en `style-src`. M-08
|
|
863
|
+
(RC1) lo había documentado como excepción legítima; la auditoría
|
|
864
|
+
beta.24 lo re-clasificó como bug con mitigación canónica disponible.
|
|
865
|
+
|
|
866
|
+
Fix: sustituir style attribute con propiedad arbitraria por un único
|
|
867
|
+
**CSS custom property**:
|
|
868
|
+
|
|
869
|
+
```html
|
|
870
|
+
<div class="ig-progress-bar" style="--ig-progress-percent: 42%"></div>
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Stylesheet ahora consume la variable: `.ig-progress-bar { width:
|
|
874
|
+
var(--ig-progress-percent, 0%); }`. La lógica visual vive en
|
|
875
|
+
stylesheet; el componente solo pasa datos. Auditores CSP modernos
|
|
876
|
+
y políticas con `'unsafe-hashes'` tratan `style="--var: value"`
|
|
877
|
+
distinto a inline rules visuales — `'unsafe-hashes'` sobre el
|
|
878
|
+
conjunto finito de valores `--ig-progress-percent` (0%..100%) es
|
|
879
|
+
ahora viable. Patrón canónico Radix/Mantine/MUI Joy.
|
|
880
|
+
|
|
881
|
+
**API React sin cambio**. Consumers que pasaban `value`/`max`/etc.
|
|
882
|
+
no notan nada. CSS-only consumers que copiaban el snippet con
|
|
883
|
+
`style="width: X%"` ven el snippet en docs actualizado a
|
|
884
|
+
`style="--ig-progress-percent: X%"`; el viejo formato sigue
|
|
885
|
+
funcionando (specificity del inline style gana al stylesheet) pero
|
|
886
|
+
NO es CSP-friendly.
|
|
887
|
+
|
|
888
|
+
Decision doc: `docs/decisions/H-03-progress-csp-css-var.md`.
|
|
889
|
+
|
|
890
|
+
---
|
|
891
|
+
|
|
892
|
+
**D11 — Disposición pública de hooks del DS** (B2-PR8):
|
|
893
|
+
|
|
894
|
+
Cierra la disposición canónica de los símbolos hook/factory expuestos
|
|
895
|
+
por `"reactigoded"`. Sin breaking — solo formaliza intención con JSDoc
|
|
896
|
+
tags + un alias adicional + documentación.
|
|
897
|
+
|
|
898
|
+
- **Nuevo alias `UseToastReturn`** en `src/components/Toast/ToastContext.ts`,
|
|
899
|
+
re-exportado desde `Toast/index.ts`. Sigue la convención DS-wide
|
|
900
|
+
`Use{Name}Return` (cf. `UseThemeReturn`, `UseControllableStateReturn`).
|
|
901
|
+
El interfaz underlying `ToastContextValue` queda exportado para mocks
|
|
902
|
+
/ adapters; el nombre canónico que el consumer tipa al destructurar
|
|
903
|
+
`useToast()` es `UseToastReturn`.
|
|
904
|
+
- **JSDoc `@public`** en `useTheme`, `useControllableState`, `useToast`,
|
|
905
|
+
`FloatingTreeRoot`. Declaración de intención: estos cuatro símbolos
|
|
906
|
+
son API pública estable 1.0.
|
|
907
|
+
- **JSDoc `@internal`** en `useFloatingNode` con pointer explícito a
|
|
908
|
+
`FloatingTreeRoot` como entrypoint público para anidación de floats.
|
|
909
|
+
El hook YA estaba retirado del wildcard re-export (D7.4 beta.24); el
|
|
910
|
+
tag explicita la intención.
|
|
911
|
+
- **README sección "Hooks públicos del DS"** (bajo § Patrones recurrentes)
|
|
912
|
+
con tabla de los cuatro símbolos públicos + nota explícita de qué
|
|
913
|
+
hooks NO son públicos (`useDropdown` / `useTabs` / `useFloatingNode`
|
|
914
|
+
/ `useSidebar` / etc.).
|
|
915
|
+
|
|
916
|
+
Patrón organizacional: hooks generic (`useTheme`, `useControllableState`)
|
|
917
|
+
viven en `src/hooks/` y se exportan explícitamente desde `src/index.ts`.
|
|
918
|
+
Hooks family-specific (`useToast`, `FloatingTreeRoot`) viven en
|
|
919
|
+
`src/components/<Family>/` y se exportan via la barrel chain de la
|
|
920
|
+
familia. Razonamiento detallado: `docs/decisions/D11-hooks-disposition.md`.
|
|
921
|
+
|
|
922
|
+
---
|
|
923
|
+
|
|
924
|
+
**D9 — size-limit re-baseline + budget creep policy + H-07 gate ejecutable** (B2-PR7):
|
|
925
|
+
|
|
926
|
+
Re-baseline de los budgets `size-limit` del JS bundle tras los cambios
|
|
927
|
+
de beta.24 (Menu Full FUI portal, callback renames, Avatar fallback,
|
|
928
|
+
Progress CSP, hooks disposition). El margen ESM había caído a 0.18 KB
|
|
929
|
+
(1.1%) — funcionalmente cero, cualquier PR mediano rompía CI por scope
|
|
930
|
+
creep accidental. CSS budgets se mantienen en su zona 23-30% headroom
|
|
931
|
+
post-H-13 (sin drift en beta.24).
|
|
932
|
+
|
|
933
|
+
**Nuevos budgets JS** (25-30% headroom paralelo, política H-13):
|
|
934
|
+
|
|
935
|
+
| Entry | Real beta.24 | Anterior | **Nuevo** | Headroom |
|
|
936
|
+
|---|---|---|---|---|
|
|
937
|
+
| JS ESM (gzip) | 15.82 KB | 16 KB | **20 KB** | 26.4% |
|
|
938
|
+
| JS CJS (gzip) | 14.06 KB | 15 KB | **18 KB** | 28.0% |
|
|
939
|
+
|
|
940
|
+
CSS budgets sin cambio.
|
|
941
|
+
|
|
942
|
+
**Budget creep policy** documentada (`D9-size-limit-baseline.md`):
|
|
943
|
+
- Headroom target 25-30%.
|
|
944
|
+
- Subir budget solo por feature documentada o dep upstream con
|
|
945
|
+
justificación + cálculo de nuevo budget en el mismo PR.
|
|
946
|
+
- NO subir budget por refactor, dep bumps cosméticos, comodidad de
|
|
947
|
+
iteración o "CI falla, fix rápido".
|
|
948
|
+
|
|
949
|
+
**H-07 gate ejecutable**: `scripts/check-state-css-exclusion.mjs` +
|
|
950
|
+
`npm run test:state-css-exclusion`, encadenado en `verify:unit`
|
|
951
|
+
post-build. Verifica que ninguna utility de `state.css` (clases con
|
|
952
|
+
prefijo `hover:ig-`, `focus:ig-`, `active:ig-`, `disabled:ig-`,
|
|
953
|
+
`checked:ig-`, `default:ig-`, `empty:ig-`, `first-child:ig-`,
|
|
954
|
+
`last-child:ig-`) aparezca en `dist/index.js` ni `dist/index.cjs`. Si
|
|
955
|
+
un componente del DS empezara accidentalmente a referenciarlas (713 KB
|
|
956
|
+
gz standalone), el gate explota pre-publish. Cierra el vector de
|
|
957
|
+
escape que H-07 había dejado como deuda (decisión "conservar" sin
|
|
958
|
+
gate ejecutable de invariante).
|
|
959
|
+
|
|
960
|
+
Decision docs: `docs/decisions/D9-size-limit-baseline.md` (nuevo),
|
|
961
|
+
`docs/decisions/H-07-state-css-and-future.md` (actualizado con
|
|
962
|
+
pointer al gate).
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
**H-08 — hydrateRoot SSR tests** (B2-PR6):
|
|
967
|
+
|
|
968
|
+
Extensión de la suite SSR (`src/__ssr__.test.tsx`) con 4 nuevos casos
|
|
969
|
+
que ejercitan el **ciclo completo** server → cliente: `renderToString`
|
|
970
|
+
→ mount en `<div>` DOM → `hydrateRoot` con `onRecoverableError` →
|
|
971
|
+
assert no mismatch + DOM intacto post-hidratación. Cubre la categoría
|
|
972
|
+
de bugs que `renderToString` solo no caza:
|
|
973
|
+
|
|
974
|
+
1. **Hydration mismatch**: HTML server difiere del primer paint
|
|
975
|
+
cliente. React reconstruye el subtree y pierde estado/handlers.
|
|
976
|
+
2. **useId divergente**: si la posición de tree es no-determinística
|
|
977
|
+
entre server y cliente, los IDs no matchean. React 19 garantiza
|
|
978
|
+
determinismo si el tree es idéntico — este test confirma que los
|
|
979
|
+
componentes del DS lo son.
|
|
980
|
+
3. **DOM mutation post-hydrate**: effect síncrono mal diseñado
|
|
981
|
+
sobreescribe el HTML del server.
|
|
982
|
+
|
|
983
|
+
**Selección de 4 componentes representativos** (no toda la suite — los
|
|
984
|
+
38 `renderToString` cases ya cubren server-safety; hidratación es spot
|
|
985
|
+
check del ciclo completo):
|
|
986
|
+
|
|
987
|
+
- **Button** — golden path simple, sin context ni useId.
|
|
988
|
+
- **Card (compound)** — contenedor con children compound. Caso layout.
|
|
989
|
+
- **Accordion (controlled state)** — estado `open/closed` interno +
|
|
990
|
+
`useId()` para aria-controls. Stress test snapshot ↔ hydrate sync.
|
|
991
|
+
- **Tabs (compound + useId)** — `useId()` para trigger/panel pairing.
|
|
992
|
+
Caso histórico de mismatch en DSs que no aíslan ID counter.
|
|
993
|
+
|
|
994
|
+
**Componentes excluidos** (decisión consciente, documentada en el
|
|
995
|
+
test): Dialog/Menu/Tooltip/Toast (client-only por design, "use client"
|
|
996
|
+
granular + portales — el consumer NO los server-renderiza),
|
|
997
|
+
ThemeToggle/Switch indeterminate (`useSyncExternalStore` con server
|
|
998
|
+
snapshot fijo, ya cubierto trivialmente por renderToString).
|
|
999
|
+
|
|
1000
|
+
Sin `act()` en el test: `hydrateRoot` commitea sincrónicamente y
|
|
1001
|
+
`onRecoverableError` se llama durante el commit; los assertions son
|
|
1002
|
+
sincronos (array push + DOM snapshot), no dependen de effects
|
|
1003
|
+
post-commit.
|
|
1004
|
+
|
|
1005
|
+
---
|
|
1006
|
+
|
|
1007
|
+
**D5 — Stepper `defaultActive` + modo uncontrolled** (B2-PR1):
|
|
1008
|
+
|
|
1009
|
+
Cierra el último componente con estado público del DS que rompía el
|
|
1010
|
+
patrón controlled/uncontrolled DS-wide. Pre-D5, `active` era
|
|
1011
|
+
obligatorio; consumers debían crear `useState` externo incluso para
|
|
1012
|
+
casos sencillos. Ahora alineado con Accordion/Pagination/Sidebar/
|
|
1013
|
+
Tabs/Switch/Slider/Rating/ThemeToggle.
|
|
1014
|
+
|
|
1015
|
+
API (no breaking — `active` deja de ser obligatorio):
|
|
1016
|
+
|
|
1017
|
+
```ts
|
|
1018
|
+
export interface StepperProps {
|
|
1019
|
+
active?: number; // ← era obligatorio
|
|
1020
|
+
defaultActive?: number; // ← nuevo (default 0)
|
|
1021
|
+
onActiveChange?: (next: number) => void; // ← sin cambio
|
|
1022
|
+
}
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
Semántica de modos:
|
|
1026
|
+
| Props pasados | Modo | Interactive |
|
|
1027
|
+
|---|---|---|
|
|
1028
|
+
| `active=N` | Controlled | NO (sin callback no hay vector de cambio) |
|
|
1029
|
+
| `active=N, onActiveChange=fn` | Controlled | SÍ |
|
|
1030
|
+
| `defaultActive=N` (o nada) | Uncontrolled | SÍ (estado interno) |
|
|
1031
|
+
| `defaultActive=N, onActiveChange=fn` | Uncontrolled + observer | SÍ |
|
|
1032
|
+
|
|
1033
|
+
Implementación: `useControllableState<number>` interno. Keyboard
|
|
1034
|
+
handlers + click invocan `setActive(nextIdx)` que resuelve a:
|
|
1035
|
+
- Controlled: forward a `onActiveChange`.
|
|
1036
|
+
- Uncontrolled: actualizar state interno + dispatch a `onActiveChange`
|
|
1037
|
+
como observer.
|
|
1038
|
+
|
|
1039
|
+
7 tests nuevos en `Stepper.test.tsx` cubren todos los modos +
|
|
1040
|
+
regression guard "controlled sin callback queda presentational".
|
|
1041
|
+
Story `Uncontrolled` añadida con play test que valida estado
|
|
1042
|
+
interno via click sin useState externo.
|
|
1043
|
+
|
|
1044
|
+
Decision doc: `docs/decisions/D5-stepper-defaultactive.md`. Cierra
|
|
1045
|
+
la simetría del DS — todos los componentes con estado interno ahora
|
|
1046
|
+
siguen el mismo patrón `{prop}?` + `default{Prop}?` + `on{Prop}Change`.
|
|
1047
|
+
|
|
1048
|
+
## [1.0.0-beta.23] — 2026-05-16 (RC1 gate review + post-audit codex)
|
|
1049
|
+
|
|
1050
|
+
Cierra el playbook RC1 completo: **17 PRs técnicos** ejecutados sobre la
|
|
1051
|
+
branch `rc1-gate-fixes` (Menu/FUI, Modal→Dialog, Tabs Radix alignment,
|
|
1052
|
+
ThemeSwitch→ThemeToggle, "use client" granular, Tooltip 12 placements,
|
|
1053
|
+
3 stories cruzadas, useControllableState updater + breaking semantic,
|
|
1054
|
+
Tooltip dev-warn 4-layer + extractText, Dialog drag-out parity,
|
|
1055
|
+
`reactigoded/cn` subpath, Skeleton ARIA soak) + **audit post-RC1
|
|
1056
|
+
con codex sobre 16 PRs antiguos** que descubrió 5 findings adicionales
|
|
1057
|
+
(todos resueltos antes de tagear).
|
|
1058
|
+
|
|
1059
|
+
**19 PRs auditados por codex / 5 findings reales / 0 issues abiertos
|
|
1060
|
+
en main al cerrar la beta.**
|
|
1061
|
+
|
|
1062
|
+
Última pre-release antes de FREEZE-CHECK final + `1.0.0-rc.1`.
|
|
1063
|
+
|
|
1064
|
+
### Internal — bump de deps a latest
|
|
1065
|
+
|
|
1066
|
+
22 paquetes actualizados a su última versión (minors + patches, sin
|
|
1067
|
+
breaking detectado en verify cold 794/794):
|
|
1068
|
+
|
|
1069
|
+
| Paquete | Antes | Después |
|
|
1070
|
+
|---|---|---|
|
|
1071
|
+
| Storybook ecosystem (`storybook`, `addon-a11y`, `addon-docs`, `addon-themes`, `addon-vitest`, `react-vite`, `eslint-plugin-storybook`) | 10.3.6 | **10.4.0** |
|
|
1072
|
+
| `vitest`, `@vitest/browser`, `@vitest/browser-playwright`, `@vitest/coverage-v8` | 4.1.5 | 4.1.6 |
|
|
1073
|
+
| `vite` | 8.0.10 | 8.0.13 |
|
|
1074
|
+
| `eslint` | 10.3.0 | 10.4.0 |
|
|
1075
|
+
| `react`, `react-dom` | 19.2.5 | 19.2.6 |
|
|
1076
|
+
| `chromatic` | 16.6.3 | 16.10.1 |
|
|
1077
|
+
| `playwright` | 1.59.1 | 1.60.0 |
|
|
1078
|
+
| `globals` | 17.5.0 | 17.6.0 |
|
|
1079
|
+
| `@types/node` | 25.6.0 | 25.8.0 |
|
|
1080
|
+
| `@vitejs/plugin-react` | 6.0.1 | 6.0.2 |
|
|
1081
|
+
| `postcss` | 8.5.13 | 8.5.14 |
|
|
1082
|
+
| `typescript-eslint` | 8.59.1 | 8.59.3 |
|
|
1083
|
+
|
|
1084
|
+
**Storybook 10.4 highlights aprovechables**:
|
|
1085
|
+
- Sidebar review tools: status filtering, URL-persisted filters,
|
|
1086
|
+
clearer review signals (útil para gate humano FREEZE-CHECK).
|
|
1087
|
+
- A11y: `aria-live` announcements en el propio Storybook, contrast en
|
|
1088
|
+
forced colors mode, ArgsTable borders Windows HCM.
|
|
1089
|
+
- Addon-Docs: reset story button, avoid re-render de Source blocks (perf).
|
|
1090
|
+
- Addon-Vitest: `provide-API` para injectar values en stories.
|
|
1091
|
+
|
|
1092
|
+
**Patch local mantenido**: `patches/eslint-plugin-jest-dom+5.5.0.patch`
|
|
1093
|
+
sigue necesario — upstream v5.5.0 sigue inconsistente entre uso del
|
|
1094
|
+
helper `getSourceCode(context)` y llamadas directas `context.getSourceCode()`
|
|
1095
|
+
(broken en ESLint v9). El patch sustituye las directas por
|
|
1096
|
+
`context.sourceCode` (sin breaking aún en ESLint v8 porque el wrapper
|
|
1097
|
+
upstream ya hace el fallback). Re-evaluar en upgrades futuros del
|
|
1098
|
+
plugin.
|
|
1099
|
+
|
|
1100
|
+
**`engines.node` raised a `>=22.12.0`** (codex P1 sobre PR #76):
|
|
1101
|
+
Vite 8.0.13 exige `^20.19.0 || >=22.12.0`. Nuestro `>=22` previo
|
|
1102
|
+
incluía Node 22.0–22.11 en el declared support pero fallaría en
|
|
1103
|
+
install/runtime. Floor alineado al peor caso del ecosystem.
|
|
1104
|
+
|
|
1105
|
+
### Changed (BREAKING — pre-RC1)
|
|
1106
|
+
|
|
1107
|
+
- **`useControllableState.setValue` en modo controlled ya NO advance el
|
|
1108
|
+
pending entre chained calls [codex P1 post-audit sobre PR #70]**:
|
|
1109
|
+
pre-fix, dos `setValue(p => p + 1)` chained en modo controlled desde
|
|
1110
|
+
`value=10` emitían `onChange(11)` y `onChange(22)` — el segundo
|
|
1111
|
+
partía del pending advanced.
|
|
1112
|
+
|
|
1113
|
+
Post-fix: emiten `onChange(11)` y `onChange(20)` — ambos parten del
|
|
1114
|
+
último committed value externo. Esto es semánticamente correcto: en
|
|
1115
|
+
controlled, el padre es la fuente de verdad. Si rechaza el commit
|
|
1116
|
+
(no actualiza `value` prop), no hay re-render y el pending no debe
|
|
1117
|
+
avanzar — si avanzara, el segundo `onChange` daría payload
|
|
1118
|
+
inconsistente con el state real.
|
|
1119
|
+
|
|
1120
|
+
Consumers afectados: uso de chained updater functions con `setValue`
|
|
1121
|
+
en modo controlled (prop `value` definido). Verificado: cero
|
|
1122
|
+
consumers internos del DS dependen de este patrón. Mitigación
|
|
1123
|
+
external consumer: pasar valor calculado externamente en lugar de
|
|
1124
|
+
chaining, o usar modo uncontrolled si el chaining era intencional.
|
|
1125
|
+
|
|
1126
|
+
### Fixed
|
|
1127
|
+
|
|
1128
|
+
- **`Tooltip`: sr-only span renderiza texto plano via `extractText`
|
|
1129
|
+
[codex P1 post-audit sobre PR #52]**: pre-fix, `text={ReactNode}`
|
|
1130
|
+
con contenido interactivo (`<button>`/`<a>`) se renderizaba completo
|
|
1131
|
+
en el sr-only span. Solución previa (`inert` en el span) rompía
|
|
1132
|
+
`aria-describedby` al excluir el subárbol del a11y tree.
|
|
1133
|
+
|
|
1134
|
+
Fix: `extractText()` extrae texto plano del ReactNode recursivamente.
|
|
1135
|
+
El sr-only renderiza solo `string` — sin focusables posibles → no se
|
|
1136
|
+
necesita `inert`. Resuelve simultáneamente:
|
|
1137
|
+
- Bug original (focus trap invisible con ReactNode interactivo).
|
|
1138
|
+
- Bug post-audit (`inert` rompe accessible name del trigger).
|
|
1139
|
+
|
|
1140
|
+
El portal flotante mantiene el `ReactNode` completo con `inert`
|
|
1141
|
+
(decoración visual aislada).
|
|
1142
|
+
|
|
1143
|
+
- **`Stepper`: guard idx-mismatch en focus management
|
|
1144
|
+
[codex P1 post-audit sobre PR #19]**: si el padre rechaza una
|
|
1145
|
+
transición de `active` (no commitea el cambio), `focusTargetIdxRef`
|
|
1146
|
+
quedaba stale apuntando al idx rechazado. Una transición posterior
|
|
1147
|
+
distinta podía mover focus al idx antiguo.
|
|
1148
|
+
|
|
1149
|
+
Fix: guard `if (idx !== clampedActive) return` antes de aplicar
|
|
1150
|
+
focus — solo movemos focus si el commit del padre coincide con la
|
|
1151
|
+
intent del keydown.
|
|
1152
|
+
|
|
1153
|
+
- **`Pagination`: `defaultPage` se clampa al inicializar state, no
|
|
1154
|
+
solo en render [codex P2 post-audit sobre PR #19]**: pre-fix,
|
|
1155
|
+
`defaultPage` fuera de rango se almacenaba raw en state interno y
|
|
1156
|
+
solo se clampaba en render. Si `totalPages` aumentaba después, la
|
|
1157
|
+
UI saltaba al `defaultPage` stale.
|
|
1158
|
+
|
|
1159
|
+
Fix: pre-clamp de `defaultPage` contra `totalPages` antes de pasar
|
|
1160
|
+
al hook.
|
|
1161
|
+
|
|
1162
|
+
- **`Avatar`: fallback de img preserva accessible name via `alt`
|
|
1163
|
+
[codex P2 post-audit sobre PR #36]**: pre-fix, cuando la imagen
|
|
1164
|
+
fallaba a cargar y caía a `initials`, el span de initials estaba
|
|
1165
|
+
`aria-hidden="true"` y el wrapper sin role/aria-label — el SR
|
|
1166
|
+
quedaba mudo si el consumer no había pasado `aria-label` explícito.
|
|
1167
|
+
|
|
1168
|
+
Fix: en fallback mode, el wrapper recibe `role="img"` +
|
|
1169
|
+
`aria-label={alt ?? ariaLabel}`. Avatares solo decorativos (sin
|
|
1170
|
+
`alt` ni `ariaLabel`) siguen siendo silenciosos por decisión
|
|
1171
|
+
consciente del consumer.
|
|
1172
|
+
|
|
1173
|
+
### Added
|
|
1174
|
+
|
|
1175
|
+
- **Soak del cambio ARIA de `Skeleton` (beta.22) [M-02]**: cobertura de
|
|
1176
|
+
tests + Storybook ampliada para validar el patrón ARIA del breaking
|
|
1177
|
+
change introducido en beta.22 (cada `Skeleton` standalone es
|
|
1178
|
+
decorativo; el announcement al SR vive en `SkeletonContainer`).
|
|
1179
|
+
|
|
1180
|
+
- Story `ConContainer` recibe play test real verificando contrato
|
|
1181
|
+
completo (único `role=status` + `aria-busy` + `aria-live=polite` +
|
|
1182
|
+
`aria-label` + Skeletons internos con `role=presentation`).
|
|
1183
|
+
- 2 stories nuevas: `SkeletonsStandaloneNoAnuncian` (consciente:
|
|
1184
|
+
sin container, cero announcements) y `MultiplesContainersParalelos`
|
|
1185
|
+
(cada container con status independiente).
|
|
1186
|
+
- 5 tests nuevos en `describe("Skeleton — M-02 ARIA soak")`:
|
|
1187
|
+
container vacío sigue anunciando, múltiples paralelos independientes,
|
|
1188
|
+
anidación preserva ambos status, standalone fuera del a11y tree,
|
|
1189
|
+
role inmutable (regresión guard).
|
|
1190
|
+
- Test extra en `AllStates regression` verifica invariante "sin
|
|
1191
|
+
container, cero announcements" — convierte el placebo legacy en
|
|
1192
|
+
aserción significativa.
|
|
1193
|
+
|
|
1194
|
+
axe-core ahora ejecuta sobre 3 stories del patrón en CI.
|
|
1195
|
+
|
|
1196
|
+
- **Subpath export `reactigoded/cn` para tree-shaking [H-12]**: pre-fix,
|
|
1197
|
+
`import { cn } from "reactigoded"` arrastraba el bundle entero
|
|
1198
|
+
(~64 KB ungz / 16 KB gz) incluyendo `createPortal` de `react-dom`
|
|
1199
|
+
vía Toast — los bundlers consumers no tree-shaken bundles
|
|
1200
|
+
monolíticos. Build multi-entry de Vite separa `cn` como entry
|
|
1201
|
+
independiente.
|
|
1202
|
+
|
|
1203
|
+
```ts
|
|
1204
|
+
// Antes: arrastra todo el DS
|
|
1205
|
+
import { cn } from "reactigoded"; // 16 KB gz
|
|
1206
|
+
|
|
1207
|
+
// Ahora: ~0.4 KB gz (wrapper + clsx)
|
|
1208
|
+
import { cn } from "reactigoded/cn";
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
El import desde el root (`reactigoded`) sigue funcionando — no es
|
|
1212
|
+
breaking. La subpath solo añade una opción más eficiente para
|
|
1213
|
+
consumers que solo necesitan `cn` (utility libraries, build
|
|
1214
|
+
helpers, etc).
|
|
1215
|
+
|
|
1216
|
+
Output del build:
|
|
1217
|
+
- `dist/cn.js` 70 B (re-export)
|
|
1218
|
+
- `dist/cn-<hash>.js` 0.55 KB (chunk clsx compartido con el root bundle)
|
|
1219
|
+
|
|
1220
|
+
### Fixed
|
|
1221
|
+
|
|
1222
|
+
- **`Dialog`: drag-out parity tracking — selección de texto ya no cierra
|
|
1223
|
+
el modal [H-02]**: cuando el usuario hacía `mousedown` sobre el contenido
|
|
1224
|
+
del Dialog (para seleccionar texto) y arrastraba hasta soltar sobre el
|
|
1225
|
+
backdrop, el browser disparaba un `click` event con `target === currentTarget`
|
|
1226
|
+
(el `<dialog>` propio) — el handler interpretaba ese click como "click
|
|
1227
|
+
en backdrop" y cerraba el modal, abandonando la selección.
|
|
1228
|
+
|
|
1229
|
+
Fix con `pointerdownTargetRef`: registra el target del `pointerdown` y
|
|
1230
|
+
solo considera "click en backdrop" cuando AMBOS el pointerdown Y el
|
|
1231
|
+
click final tienen target = dialog (no si pointerdown empezó en
|
|
1232
|
+
contenido). El fallback `pointerdownTarget === null` preserva el
|
|
1233
|
+
comportamiento para clicks programáticos sin pointerdown previo
|
|
1234
|
+
(tests, consumer-side `.click()` calls).
|
|
1235
|
+
|
|
1236
|
+
Sin cambio de API — fix interno transparente.
|
|
1237
|
+
|
|
1238
|
+
### Added
|
|
1239
|
+
|
|
1240
|
+
- **`Tooltip` dev-warn cuando custom child no forwardea ref [M-07.2]**:
|
|
1241
|
+
cuando el `children` del Tooltip es un componente custom que ignora
|
|
1242
|
+
el ref (sin `React.forwardRef` o sin aceptar `ref` como prop normal
|
|
1243
|
+
en React 19), Floating UI no puede medir el trigger ni montar el
|
|
1244
|
+
portal — el tooltip queda inerte al hover/focus pero el sr-only
|
|
1245
|
+
span con `aria-describedby` sigue funcionando. Pre-RC1 el síntoma
|
|
1246
|
+
era silencioso (consumer no sabía por qué el tooltip "no abre").
|
|
1247
|
+
|
|
1248
|
+
Implementación con **approach de 4 capas** (cero magic numbers en
|
|
1249
|
+
camino crítico):
|
|
1250
|
+
|
|
1251
|
+
1. **Static analysis O(1)** sobre `children.type`:
|
|
1252
|
+
- `string` (DOM intrinsic): `guaranteed_ok` → cero runtime.
|
|
1253
|
+
- `$$typeof === REACT_FORWARD_REF_TYPE`: `guaranteed_ok`.
|
|
1254
|
+
- `memo(...)`: recurse en el inner type.
|
|
1255
|
+
- `function`: `ambiguous` (React 19 ref-as-prop posible).
|
|
1256
|
+
2. **Probe sticky** en el ref: `useRef(false)` que se setea a `true`
|
|
1257
|
+
la primera vez que recibe un Element no-null. Una vez true, no se
|
|
1258
|
+
resetea — cero false positives en lazy mounting.
|
|
1259
|
+
3. **Sentinel dev-only** con `display: contents` envolviendo el child
|
|
1260
|
+
(solo si `verdict === 'ambiguous'`). Listeners `mouseenter`/`focus`
|
|
1261
|
+
en capture-phase via `addEventListener` nativo (React no expone
|
|
1262
|
+
`onMouseEnterCapture` porque mouseenter no bubblea). Captura el
|
|
1263
|
+
intent del usuario incluso si el child no propaga handlers — sin
|
|
1264
|
+
depender de tiempo arbitrario.
|
|
1265
|
+
4. **Safety net 2000ms** como fallback solo para el caso edge "dev
|
|
1266
|
+
observa pero no interactúa". Honestamente generoso (cubre
|
|
1267
|
+
`React.lazy`, Suspense, fetch lentos, `requestIdleCallback`).
|
|
1268
|
+
|
|
1269
|
+
Detecta los **3 modos de fallo** del Tooltip (no solo el del ref):
|
|
1270
|
+
child que no forwardea ref, child que no propaga handlers (`...rest`
|
|
1271
|
+
ignorado), child que renderiza null permanentemente.
|
|
1272
|
+
|
|
1273
|
+
Mensaje del warn (incluye nombre del componente):
|
|
1274
|
+
|
|
1275
|
+
```
|
|
1276
|
+
[reactigoded] <Tooltip>: el child <MyCustom> no expone su nodo DOM
|
|
1277
|
+
via ref. El tooltip no puede medir el trigger ni abrirse al hover/
|
|
1278
|
+
focus. Usa React.forwardRef (React <19) o acepta `ref` como prop
|
|
1279
|
+
normal (React 19+) y pásalo al elemento DOM root del componente.
|
|
1280
|
+
aria-describedby sigue funcionando — el SR anuncia el texto del
|
|
1281
|
+
tooltip pero el portal visual no aparece.
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
No-op en producción (gated por `import.meta.env.DEV`).
|
|
1285
|
+
|
|
1286
|
+
- **`useControllableState.setValue` acepta updater function [M-06]**: el
|
|
1287
|
+
setter del hook ahora acepta valor directo `T` **o** una función
|
|
1288
|
+
updater `(prev: T) => T` — mismo patrón que `useState` de React.
|
|
1289
|
+
|
|
1290
|
+
```ts
|
|
1291
|
+
const { value, setValue } = useControllableState<boolean>({
|
|
1292
|
+
defaultValue: false,
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
// Antes: solo valor directo (depende del closure de `value`).
|
|
1296
|
+
setValue(!value);
|
|
1297
|
+
|
|
1298
|
+
// Ahora también: updater (no depende del closure).
|
|
1299
|
+
setValue((prev) => !prev);
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
Útil para updates encadenados en handlers que no quieren capturar
|
|
1303
|
+
`value` en el closure (`onClick`/`onKeyDown` con múltiples acciones).
|
|
1304
|
+
|
|
1305
|
+
Funciona en los 3 modos:
|
|
1306
|
+
- **uncontrolled**: la updater recibe el `internalValue` actual.
|
|
1307
|
+
- **controlled**: la updater recibe el `value` controlado externo y
|
|
1308
|
+
el resuelto se pasa a `onChange` (sin tocar state interno).
|
|
1309
|
+
- **derive**: la updater recibe el valor derivado y el resuelto se
|
|
1310
|
+
pasa a `setDerivedValue`.
|
|
1311
|
+
|
|
1312
|
+
Sin breaking — `setValue(directValue)` sigue funcionando.
|
|
1313
|
+
|
|
1314
|
+
- **`Tooltip` stories cruzadas + play tests reales [H-08]**: cobertura
|
|
1315
|
+
Storybook ampliada con 1 story cruzada nueva (`TooltipDentroDeTabs`)
|
|
1316
|
+
y play tests reescritos en stories existentes para sustituir las
|
|
1317
|
+
asserts placebo (counting spans, leyendo `aria-describedby` sin abrir
|
|
1318
|
+
el portal) por **interacciones reales**:
|
|
1319
|
+
|
|
1320
|
+
- `A11yInteraction`: play ahora hace `userEvent.hover`, verifica que
|
|
1321
|
+
el portal monta en `document.body` con `.ig-tooltip` y el texto
|
|
1322
|
+
correcto, dispara `{Escape}` y verifica que el portal desmonta —
|
|
1323
|
+
y que el sr-only persiste como referente estable de `aria-describedby`.
|
|
1324
|
+
- `AllStates`: el `expect(spans).toHaveLength` legacy se mantiene
|
|
1325
|
+
como sanity check pero se añade hover real sobre `top` →
|
|
1326
|
+
verify `.ig-tooltip.ig-tooltip-place-top` en `document.body` con
|
|
1327
|
+
texto → unhover → verify desmonte.
|
|
1328
|
+
- `TooltipDentroDeModal` (story cruzada existente): nuevo play que
|
|
1329
|
+
abre el Dialog, hace hover en el botón danger, verifica que el
|
|
1330
|
+
portal del tooltip monta dentro del top-layer del `<dialog>` (no
|
|
1331
|
+
detrás del backdrop) y dispara Escape para cerrar solo el tooltip.
|
|
1332
|
+
- `TooltipDentroDeTabs` (story cruzada nueva): cambia entre paneles
|
|
1333
|
+
de `Tabs`, verifica que el tooltip del panel inactivo desaparece
|
|
1334
|
+
del DOM al cambiar, y que el del panel activo aparece al hover.
|
|
1335
|
+
|
|
1336
|
+
Coverage cruzada total: 3 stories (`WithCustomContainer` con `<dialog>`
|
|
1337
|
+
nativo + `TooltipDentroDeModal` con `<Dialog>` del DS + `TooltipDentroDeTabs`
|
|
1338
|
+
con `<Tabs>` del DS). axe-core ahora ejecutará a11y checks sobre estas
|
|
1339
|
+
combinaciones en CI.
|
|
1340
|
+
|
|
1341
|
+
- **`Tooltip` ampliado a los 12 placements de Floating UI [M-04]**: el
|
|
1342
|
+
type `TooltipPlacement` pasa de 4 (`top` / `bottom` / `left` / `right`)
|
|
1343
|
+
a los **12 valores nativos** de Floating UI — 4 sides × 3 alignments
|
|
1344
|
+
(base, `-start`, `-end`):
|
|
1345
|
+
|
|
1346
|
+
```ts
|
|
1347
|
+
type TooltipPlacement =
|
|
1348
|
+
| "top" | "top-start" | "top-end"
|
|
1349
|
+
| "right" | "right-start" | "right-end"
|
|
1350
|
+
| "bottom" | "bottom-start" | "bottom-end"
|
|
1351
|
+
| "left" | "left-start" | "left-end";
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
Sin cambios CSS — las clases `.ig-tooltip-place-*` siguen siendo
|
|
1355
|
+
hooks informativos vacíos (Floating UI posiciona vía inline styles).
|
|
1356
|
+
La nueva clase `.ig-tooltip-place-top-start` y resto se emiten
|
|
1357
|
+
automáticamente.
|
|
1358
|
+
|
|
1359
|
+
Sin breaking — los 4 placements antiguos siguen siendo válidos. Solo
|
|
1360
|
+
amplía el conjunto de opciones permitidas.
|
|
1361
|
+
|
|
1362
|
+
Tests: `describe.each` ampliado a los 12 placements.
|
|
1363
|
+
Stories: nuevo layout 3x3 + alas mostrando los 12 placements simultáneamente.
|
|
1364
|
+
|
|
1365
|
+
### Changed
|
|
1366
|
+
|
|
1367
|
+
- **`"use client"` granular por archivo en lugar de global [H-09]**: el
|
|
1368
|
+
barrel `src/index.ts` ya no lleva `"use client";` global. En su lugar,
|
|
1369
|
+
los **48 archivos** que dependen de hooks React, browser APIs o context
|
|
1370
|
+
interno llevan la directive en su propio archivo. Los **44 archivos
|
|
1371
|
+
restantes** (Card subcomponents, Dialog body/footer/close, Sidebar
|
|
1372
|
+
items, Navbar pieces, Skeleton, Spinner, Badge, Progress, Divider,
|
|
1373
|
+
Timeline, etc. — ~49% del DS) son server-safe y pueden renderizarse
|
|
1374
|
+
desde React Server Components sin forzar boundary client en el árbol
|
|
1375
|
+
consumer.
|
|
1376
|
+
|
|
1377
|
+
Impacto consumers RSC: árboles puramente presentacionales (p.ej.
|
|
1378
|
+
`<Card><CardBody>...</CardBody></Card>`) ya no escalan el "client
|
|
1379
|
+
boundary" innecesariamente. Para componentes interactivos (`Switch`,
|
|
1380
|
+
`Tabs`, `Dialog`, `Toast`, etc.) el comportamiento es idéntico — la
|
|
1381
|
+
directive sigue presente, ahora a nivel archivo.
|
|
1382
|
+
|
|
1383
|
+
Sin impacto en SPA / Vite dev / Storybook — la directive es no-op
|
|
1384
|
+
fuera de bundlers RSC-aware (Next.js App Router, Remix, etc.).
|
|
1385
|
+
|
|
1386
|
+
### Breaking
|
|
1387
|
+
|
|
1388
|
+
- **`ThemeSwitch` → `ThemeToggle` (rename JS, sin cambios CSS)**: el componente
|
|
1389
|
+
se renombra a `ThemeToggle` para reflejar fielmente su comportamiento (toggle
|
|
1390
|
+
binario light/dark) y desacoplar el nombre público de la implementación
|
|
1391
|
+
interna (que sigue usando `<Switch>` por dentro). Alinea con la industria
|
|
1392
|
+
(Radix, ShadCN, MUI usan `ThemeToggle`/`ThemeButton`).
|
|
1393
|
+
|
|
1394
|
+
Migración consumers:
|
|
1395
|
+
```diff
|
|
1396
|
+
- import { ThemeSwitch } from "reactigoded";
|
|
1397
|
+
- <ThemeSwitch defaultTheme="dark" />
|
|
1398
|
+
+ import { ThemeToggle } from "reactigoded";
|
|
1399
|
+
+ <ThemeToggle defaultTheme="dark" />
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
Tipo de props renombrado en paralelo: `ThemeSwitchProps` → `ThemeToggleProps`.
|
|
1403
|
+
|
|
1404
|
+
**CSS sin cambios**: `ThemeToggle` sigue emitiendo `.ig-switch` (delega
|
|
1405
|
+
en `<Switch>` internamente). Consumers que customicen vía CSS de
|
|
1406
|
+
`.ig-switch` no se ven afectados.
|
|
1407
|
+
|
|
1408
|
+
- **Tabs API alineada a Radix puro (rename + wrapper eliminado)**: la API
|
|
1409
|
+
pública se simplifica a `<Tabs>`, `<TabsList>`, `<TabsTrigger>`, `<TabsContent>`
|
|
1410
|
+
con composición 100% Radix-style. Cierra inconsistencia interna del DS y
|
|
1411
|
+
alinea con la industria (Radix, ShadCN, Ark UI).
|
|
1412
|
+
|
|
1413
|
+
| Antes | Después | Notas |
|
|
1414
|
+
|---|---|---|
|
|
1415
|
+
| `Tab` | `TabsTrigger` | trigger del tab |
|
|
1416
|
+
| `TabList` | `TabsList` | coherencia plural con `TabsContent`/`TabsContext` |
|
|
1417
|
+
| `TabPanel` | `TabsContent` | panel asociado al `value` |
|
|
1418
|
+
| `<TabsContent>` (wrapper sin lógica) | ❌ eliminado | era Caso 1 (`<div ig-tabs-content>` puro). Padding-top movido a `.ig-tabs-content` (el panel renombrado). |
|
|
1419
|
+
|
|
1420
|
+
API final 100% Radix-style:
|
|
1421
|
+
```tsx
|
|
1422
|
+
<Tabs>
|
|
1423
|
+
<TabsList>
|
|
1424
|
+
<TabsTrigger value="a">A</TabsTrigger>
|
|
1425
|
+
</TabsList>
|
|
1426
|
+
<TabsContent value="a">…</TabsContent>
|
|
1427
|
+
</Tabs>
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
CSS coordinado:
|
|
1431
|
+
- `.ig-tab` → `.ig-tabs-trigger` (con sufijo `-active`)
|
|
1432
|
+
- `.ig-tab-panel` → `.ig-tabs-content` (con sufijo `-active`)
|
|
1433
|
+
- `.ig-tabs-list` SIN cambio (ya plural pre-RC1)
|
|
1434
|
+
- `.ig-tabs-content` (wrapper original) eliminada — el panel renombrado
|
|
1435
|
+
hereda el nombre.
|
|
1436
|
+
- Utilities `.ig-tab-1/2/4/8` (tab-size CSS) **SIN cambios** — son
|
|
1437
|
+
typography utilities, no componente Tabs.
|
|
1438
|
+
|
|
1439
|
+
Tokens CSS-variable `--ig-tab-*` **mantenidos** (no renombrados) por
|
|
1440
|
+
compatibilidad con custom CSS de consumers (mismo criterio que
|
|
1441
|
+
`--ig-z-modal` en PR 3).
|
|
1442
|
+
|
|
1443
|
+
Razón última: paralelismo con `Menu`/`MenuTrigger`/`MenuContent`
|
|
1444
|
+
post-PR Menu — los compounds del DS siguen el mismo patrón
|
|
1445
|
+
estructural `<X>` / `<XTrigger>` / `<XContent>`.
|
|
1446
|
+
|
|
1447
|
+
Migración:
|
|
1448
|
+
```diff
|
|
1449
|
+
- import { Tabs, TabList, Tab, TabPanel, TabsContent } from "reactigoded";
|
|
1450
|
+
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "reactigoded";
|
|
1451
|
+
|
|
1452
|
+
- <Tabs>
|
|
1453
|
+
- <TabList>
|
|
1454
|
+
- <Tab value="a">A</Tab>
|
|
1455
|
+
- </TabList>
|
|
1456
|
+
- <TabsContent>
|
|
1457
|
+
- <TabPanel value="a">...</TabPanel>
|
|
1458
|
+
- </TabsContent>
|
|
1459
|
+
- </Tabs>
|
|
1460
|
+
+ <Tabs>
|
|
1461
|
+
+ <TabsList>
|
|
1462
|
+
+ <TabsTrigger value="a">A</TabsTrigger>
|
|
1463
|
+
+ </TabsList>
|
|
1464
|
+
+ <TabsContent value="a">...</TabsContent>
|
|
1465
|
+
+ </Tabs>
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
- **`Modal` → `Dialog` (rename + regularización CSS-JS)**: el componente JS
|
|
1469
|
+
se renombra a `Dialog` para alinear con el HTML element nativo `<dialog>`
|
|
1470
|
+
(que ya usaba internamente) y con la industria (Radix, Ark UI, HeadlessUI,
|
|
1471
|
+
MUI usan `Dialog`). Las clases CSS ya estaban nombradas `.ig-dialog-*`
|
|
1472
|
+
(alineación previa), por lo que NO cambian. Este rename regulariza la
|
|
1473
|
+
inconsistencia legacy: componente `Modal` (JS) emitiendo clases
|
|
1474
|
+
`.ig-dialog-*` (CSS).
|
|
1475
|
+
|
|
1476
|
+
Renames JS:
|
|
1477
|
+
|
|
1478
|
+
| Antes | Después |
|
|
1479
|
+
|---|---|
|
|
1480
|
+
| `Modal` | `Dialog` |
|
|
1481
|
+
| `ModalBody` | `DialogBody` |
|
|
1482
|
+
| `ModalHeader` | `DialogHeader` |
|
|
1483
|
+
| `ModalFooter` | `DialogFooter` |
|
|
1484
|
+
| `ModalClose` | `DialogClose` |
|
|
1485
|
+
| `ModalProps` (y `Modal*Props` subcomponentes) | `DialogProps` (y `Dialog*Props`) |
|
|
1486
|
+
| `ModalContext` (interno) | `DialogContext` (interno) |
|
|
1487
|
+
| `ModalSize` / `ModalBackdrop` (types) | `DialogSize` / `DialogBackdrop` |
|
|
1488
|
+
|
|
1489
|
+
Las **clases CSS `.ig-dialog-*` SIN cambios** (ya alineadas pre-RC1).
|
|
1490
|
+
Tokens `--ig-z-modal` / `--ig-z-modal-backdrop` se mantienen como
|
|
1491
|
+
utilities z-index genéricas (no renombradas para preservar
|
|
1492
|
+
compatibilidad con custom CSS de consumers).
|
|
1493
|
+
|
|
1494
|
+
Migración:
|
|
1495
|
+
```diff
|
|
1496
|
+
- import { Modal, ModalBody, ModalHeader, ModalFooter, ModalClose } from "reactigoded";
|
|
1497
|
+
+ import { Dialog, DialogBody, DialogHeader, DialogFooter, DialogClose } from "reactigoded";
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
- **`Menu` internals migrados a Floating UI (C-03)**: `Menu` reescrito sobre la
|
|
1501
|
+
capa `floating/primitives/` (PR #62). Reemplaza ~600 LOC hand-rolled de
|
|
1502
|
+
navegación por hooks composables:
|
|
1503
|
+
|
|
1504
|
+
- `useListNavigation` con `focusItemOnOpen: 'auto'` (APG menu pattern: arrows
|
|
1505
|
+
abren menu + focan primer/último item; click abre sin focar).
|
|
1506
|
+
- `useTypeahead` (focus por primera letra, APG).
|
|
1507
|
+
- `useDismiss({ bubbles: { escapeKey: true } })` (cascade dismiss via
|
|
1508
|
+
`<FloatingTreeRoot>`).
|
|
1509
|
+
- `FloatingFocusManager` con `initialFocus={-1}` + `returnFocus` (defiere
|
|
1510
|
+
focus management a useListNavigation, devuelve foco al trigger al cerrar).
|
|
1511
|
+
- `useFloatingNode` para registrar el Menu en el FloatingTree (cascade dismiss
|
|
1512
|
+
cuando hay descendientes flotantes como Tooltip dentro de MenuItem).
|
|
1513
|
+
|
|
1514
|
+
API pública (`open`/`defaultOpen`/`onOpenChange`/`placement`/`direction`/
|
|
1515
|
+
`closeOnSelect`) y compound shape (`Menu`/`MenuTrigger`/`MenuContent`/
|
|
1516
|
+
`MenuItem`/`MenuSeparator`/`MenuLabel`) sin cambio. Las clases CSS
|
|
1517
|
+
`.ig-menu-*` mantienen mismo contrato.
|
|
1518
|
+
|
|
1519
|
+
Breaking de tipos en callbacks de `MenuItem`:
|
|
1520
|
+
- `onClick` y `onKeyDown` cambian de `MouseEvent<HTMLAnchorElement>` /
|
|
1521
|
+
`MouseEvent<HTMLButtonElement>` (según rama) a `MouseEvent<HTMLElement>`
|
|
1522
|
+
(genérico). Mismo para `KeyboardEvent`. Audit pre-RC1 verificó cero usage
|
|
1523
|
+
de API anchor/button-specific (`.href`, `.disabled`, etc.) en callbacks
|
|
1524
|
+
de MenuItem en repo + cero consumers reales.
|
|
1525
|
+
|
|
1526
|
+
Migración consumers futuros que necesiten API element-specific:
|
|
1527
|
+
```tsx
|
|
1528
|
+
onClick={(e) => {
|
|
1529
|
+
if (e.currentTarget instanceof HTMLAnchorElement) {
|
|
1530
|
+
e.currentTarget.href; // narrow type-safe
|
|
1531
|
+
}
|
|
1532
|
+
}}
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
- **`OptionsMenu` → `Menu` + 5 subcomponentes renombrados (B-01 redefinido)**:
|
|
1536
|
+
alineación con la industria. 5 de 7 librerías top (MUI, Mantine, Ark UI,
|
|
1537
|
+
HeadlessUI, Chakra) usan el nombre canónico `Menu`. B-01 original (rename
|
|
1538
|
+
`Dropdown` → `OptionsMenu` pre-RC1) liberaba el nombre `Dropdown` para una
|
|
1539
|
+
migración FUI futura; la decisión 2026-05-10 unifica directamente a `Menu`.
|
|
1540
|
+
|
|
1541
|
+
| Antes | Después | Notas |
|
|
1542
|
+
|---|---|---|
|
|
1543
|
+
| `OptionsMenu` | `Menu` | core |
|
|
1544
|
+
| `OptionsMenuTrigger` | `MenuTrigger` | core |
|
|
1545
|
+
| `OptionsMenuContent` | `MenuContent` | core |
|
|
1546
|
+
| `OptionsMenuItem` | `MenuItem` | core |
|
|
1547
|
+
| `OptionsMenuDivider` | `MenuSeparator` | rol ARIA correcto `separator` |
|
|
1548
|
+
| `OptionsMenuHeader` | `MenuLabel` | label de sección dentro del menu |
|
|
1549
|
+
|
|
1550
|
+
CSS coordinado:
|
|
1551
|
+
- `.ig-options-menu-*` → `.ig-menu-*`
|
|
1552
|
+
- `.ig-options-menu-divider-*` → `.ig-menu-separator-*`
|
|
1553
|
+
- `.ig-options-menu-header-*` → `.ig-menu-label-*`
|
|
1554
|
+
|
|
1555
|
+
Migración:
|
|
1556
|
+
```diff
|
|
1557
|
+
- import { OptionsMenu, OptionsMenuTrigger, OptionsMenuItem } from "reactigoded";
|
|
1558
|
+
+ import { Menu, MenuTrigger, MenuItem } from "reactigoded";
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
- **`Tooltip` Slot pattern (D-01 / M-05 / B-03 / H-01)**: el componente
|
|
1562
|
+
ya **no envuelve al child en `<span class="ig-tooltip-wrapper">`**.
|
|
1563
|
+
El render emite el child clonado + un `<span class="ig-sr-only"
|
|
1564
|
+
role="tooltip" inert>` sibling + el portal flotante. La API pública
|
|
1565
|
+
de props (`text`, `placement`, `variant`, `openDelay`, `closeDelay`,
|
|
1566
|
+
`container`) no cambia, **pero**:
|
|
1567
|
+
|
|
1568
|
+
- **CSS público**: la clase `.ig-tooltip-wrapper` ya no se emite. Si
|
|
1569
|
+
tenías reglas CSS dirigidas al wrapper (layout, espaciado), aplica
|
|
1570
|
+
los estilos al child directamente o envuélvelo manualmente en tu
|
|
1571
|
+
propio span/div.
|
|
1572
|
+
- **Tipos de `TooltipProps`**: dejan de extender
|
|
1573
|
+
`HTMLAttributes<HTMLSpanElement>`. `className`, `ref` y
|
|
1574
|
+
`...rest` HTML props del wrapper desaparecen del tipo. Si pasabas
|
|
1575
|
+
`<Tooltip className="..." ref={spanRef}>`, el TS error es
|
|
1576
|
+
intencional — esos props no tenían destino fiable post-Slot.
|
|
1577
|
+
- **Layout**: si el child es block-level (e.g. `<div>`, `<table>`),
|
|
1578
|
+
ahora respeta el flujo natural (antes, el wrapper `display:
|
|
1579
|
+
contents` lo neutralizaba pero introducía un nodo silencioso).
|
|
1580
|
+
|
|
1581
|
+
Razón: el wrapper rompía block-level layouts del consumer y obligaba
|
|
1582
|
+
a CSS extra para corregirlo. M-05 y D-01 lo señalaban; en RC1 se
|
|
1583
|
+
cierran.
|
|
1584
|
+
|
|
1585
|
+
### Añadido
|
|
1586
|
+
|
|
1587
|
+
- **`FloatingTreeRoot` y `useFloatingNode`** (B-03 / H-01): nueva capa
|
|
1588
|
+
`floating/primitives/` con un wrapper opt-in (`<FloatingTreeRoot>`)
|
|
1589
|
+
que habilita cascade dismiss entre `Tooltip` y futuros floats
|
|
1590
|
+
(`Popover`, `HoverCard`, `Dropdown` FUI 1.x+) cuando se anidan. Sin
|
|
1591
|
+
el root, los floats funcionan independientes (sin regresión).
|
|
1592
|
+
|
|
1593
|
+
```tsx
|
|
1594
|
+
import { FloatingTreeRoot } from "reactigoded";
|
|
1595
|
+
|
|
1596
|
+
function App() {
|
|
1597
|
+
return (
|
|
1598
|
+
<FloatingTreeRoot>
|
|
1599
|
+
<RouterProvider router={router} />
|
|
1600
|
+
</FloatingTreeRoot>
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
```
|
|
1604
|
+
|
|
1605
|
+
El hook `useFloatingNode()` devuelve `{ nodeId, parentId }` para que
|
|
1606
|
+
componentes flotantes del DS se registren en el árbol activo.
|
|
1607
|
+
`Tooltip` ya lo consume internamente.
|
|
1608
|
+
|
|
1609
|
+
### Notas operativas
|
|
1610
|
+
|
|
1611
|
+
- **Sin publicación a npm pre-RC1** (M-03 / cf. `docs/RC1_DECISIONS.md`
|
|
1612
|
+
§ "Actualización 2026-05-06: pausa operativa"). Pre-RC1 el paquete
|
|
1613
|
+
se consume vía `git clone` + `npm link`. Iván decidió pausar
|
|
1614
|
+
`npm publish --tag beta` de `1.0.0-beta.22` hasta tener capacidad
|
|
1615
|
+
operativa para sostener el mantenimiento (issues, PRs de consumers,
|
|
1616
|
+
semver discipline). El branch `rc1-gate-fixes` ya cerró los 18
|
|
1617
|
+
Blockers + 8 Highs del gate review; la calidad del código no
|
|
1618
|
+
requiere publicación inmediata para ser real.
|
|
1619
|
+
- **Tag git `v1.0.0-rc.1`** se creará tras la sesión de FREEZE-CHECK
|
|
1620
|
+
para trazabilidad histórica del repo, **sin acompañar de
|
|
1621
|
+
`npm publish`** mientras la pausa siga vigente.
|
|
1622
|
+
- **Reactivación de la publicación** seguirá el procedimiento
|
|
1623
|
+
documentado en `docs/RC1_DECISIONS.md`: bumpear versión, revertir
|
|
1624
|
+
commit B-01-followup (banner del README), `npm publish --tag rc`.
|
|
1625
|
+
|
|
1626
|
+
## [1.0.0-beta.22] — 2026-05-06 (saneamiento RC1)
|
|
1627
|
+
|
|
1628
|
+
Cierra los 18 Blockers + 8 Highs del audit RC1
|
|
1629
|
+
(`rc1-gate-review-reactigoded.md`) más limpieza adicional descubierta
|
|
1630
|
+
durante el ciclo. **Última pre-release antes de `1.0.0-rc.1`**.
|
|
1631
|
+
|
|
1632
|
+
### Breaking
|
|
1633
|
+
|
|
1634
|
+
- **Sidebar**: prop `ariaLabel` eliminada por consistencia con el resto
|
|
1635
|
+
del DS (Pagination, Spinner, Stepper, TabList, Rating, Timeline ya
|
|
1636
|
+
habían eliminado la prop separada en beta.4 y posteriores). Sidebar
|
|
1637
|
+
era el último outlier. Migration:
|
|
1638
|
+
```diff
|
|
1639
|
+
- <Sidebar ariaLabel="Navegación principal">…</Sidebar>
|
|
1640
|
+
+ <Sidebar aria-label="Navegación principal">…</Sidebar>
|
|
1641
|
+
```
|
|
1642
|
+
Sin override sigue cayendo al default ES `"Navegación lateral"`.
|
|
1643
|
+
- **Skeleton**: cambio del patrón ARIA. `Skeleton` ahora es decorativo
|
|
1644
|
+
(`role="presentation"` + `aria-hidden="true"`). Para anunciar carga
|
|
1645
|
+
al lector de pantalla, envuelve un grupo en
|
|
1646
|
+
`<SkeletonContainer label="...">`, que dispara UN solo aviso
|
|
1647
|
+
(`role="status"` + `aria-busy` + `aria-live="polite"`). El patrón
|
|
1648
|
+
anterior generaba spam de "status busy" en SR cuando había varios
|
|
1649
|
+
`Skeleton`. Migration:
|
|
1650
|
+
```diff
|
|
1651
|
+
- <Skeleton variant="text" />
|
|
1652
|
+
- <Skeleton variant="text" />
|
|
1653
|
+
+ <SkeletonContainer label="Cargando lista">
|
|
1654
|
+
+ <Skeleton variant="text" />
|
|
1655
|
+
+ <Skeleton variant="text" />
|
|
1656
|
+
+ </SkeletonContainer>
|
|
1657
|
+
```
|
|
1658
|
+
El layout del wrapper es neutro (`display: contents`): no añade caja
|
|
1659
|
+
al flujo, los hijos se posicionan como si el container no existiese.
|
|
1660
|
+
|
|
1661
|
+
### Added
|
|
1662
|
+
|
|
1663
|
+
- **`SkeletonContainer`** [B-12]: nuevo wrapper a11y para grupos de
|
|
1664
|
+
`Skeleton`. Props: `label?: string` (default ES
|
|
1665
|
+
`"Cargando contenido…"`), `children`. Acepta `aria-label` directo
|
|
1666
|
+
vía rest (gana sobre `label`).
|
|
1667
|
+
- **`Pagination` uncontrolled state sync** [B-18]: cuando `totalPages`
|
|
1668
|
+
baja por debajo del page interno, el componente sincroniza `page`
|
|
1669
|
+
al clamped current con `silent: true` (no dispara `onValueChange`).
|
|
1670
|
+
Cuando `totalPages` vuelve a subir, NO "salta" al page viejo.
|
|
1671
|
+
- **`Tabs` controlled inválido — fallback tab stop** [H-26]: cuando
|
|
1672
|
+
`value` no matchea ningún Tab montado, el primer Tab registrado
|
|
1673
|
+
recibe `tabIndex=0` para mantener el tablist navegable por
|
|
1674
|
+
teclado. `aria-selected` permanece `false` en todos. Mensaje de
|
|
1675
|
+
warn dev actualizado.
|
|
1676
|
+
- **`useControllableState` warn re-aplicado** [B-08-followup en
|
|
1677
|
+
beta.21]: ahora con escape hatch `__suppressNoHandlerWarn` que se
|
|
1678
|
+
elimina del `.d.ts` publicado vía `stripInternal`.
|
|
1679
|
+
- **`Card.ref` polimórfica tipada** [B-03]: `ref` infiere desde
|
|
1680
|
+
`as`. `<Card as="a" ref={r}>` → `r` es `RefObject<HTMLAnchorElement>`.
|
|
1681
|
+
- **Skeleton + Stepper data-step-index** [H-25]: el dot interactivo
|
|
1682
|
+
del Stepper expone `data-step-index` (implementation detail, NO
|
|
1683
|
+
API pública) para que el effect post-commit de focus management
|
|
1684
|
+
resuelva por índice lógico, no por orden DOM.
|
|
1685
|
+
|
|
1686
|
+
### Fixed
|
|
1687
|
+
|
|
1688
|
+
- **Bundle prod sin `console.*` dev warns** [B-07]: 9 componentes
|
|
1689
|
+
migrados de `if (!isDev())` a `if (!import.meta.env.DEV)` para
|
|
1690
|
+
que esbuild/Vite hagan DCE de los warn dev en build de producción.
|
|
1691
|
+
Verificado: `grep -c console.* dist/index.js` = 0; `grep -c
|
|
1692
|
+
console.* dist/index.cjs` = 0. CI ahora gated por step explícito.
|
|
1693
|
+
- **`ThemeSwitch` respeta `<html data-theme>` pre-puesto** [B-08]:
|
|
1694
|
+
el derive lee `<html data-theme>` antes de caer a `defaultTheme`,
|
|
1695
|
+
así que un script anti-flash del consumer
|
|
1696
|
+
(`<html data-theme="light">` antes de hidratar) ya no se sobreescribe.
|
|
1697
|
+
6 tests añadidos (incluido SSR `renderToString` smoke + SSR test
|
|
1698
|
+
versión A que stuba `globalThis.document` y valida el branch
|
|
1699
|
+
`typeof document === "undefined"` del derive).
|
|
1700
|
+
- **`Stepper` focus management sin `setTimeout`** [H-25]: el focus
|
|
1701
|
+
al nuevo step ya no usa `setTimeout(0)`; ahora usa un
|
|
1702
|
+
`useEffect([active])` post-commit y `data-step-index` para resolver
|
|
1703
|
+
el target de forma robusta contra conditional rendering, Steps
|
|
1704
|
+
decorativos sin role=button, o CSS reordering.
|
|
1705
|
+
- **`Slider` warn dev cuando `value=NaN`** [H-27]: el path controlled
|
|
1706
|
+
no-finito ya no es silencioso; emite warn dev-only similar al que
|
|
1707
|
+
existía para `defaultValue` no-finito.
|
|
1708
|
+
- **`Sidebar` blindaje a11y** [B-09]: `role`/`aria-busy`/`aria-live`
|
|
1709
|
+
en `<aside>` no pueden ser overrideados por consumer (extracción
|
|
1710
|
+
explícita de `aria-label` desde rest). Mismo patrón canónico del DS.
|
|
1711
|
+
- **`Card.ref` con `Ref<unknown>`** [B-03]: pasa a
|
|
1712
|
+
`ComponentPropsWithRef<C>["ref"]`. Consumers en strict TS configs
|
|
1713
|
+
ya no reciben tipo laxo.
|
|
1714
|
+
- **Sitio (igoded.es / Storybook)**:
|
|
1715
|
+
- `<html lang="es">` forzado runtime + estático [B-04 + B-04-followup].
|
|
1716
|
+
- Dedupe de `<title>` y `<meta name="description">` [B-05]. Causa
|
|
1717
|
+
raíz arreglada: metas estáticas consolidadas en
|
|
1718
|
+
`.storybook/manager-head.html` (eran duplicadas con
|
|
1719
|
+
`main.ts:managerHead()`); el script runtime queda como red de
|
|
1720
|
+
seguridad defensiva.
|
|
1721
|
+
|
|
1722
|
+
### Internal
|
|
1723
|
+
|
|
1724
|
+
- **`stripInternal: true`** en `tsconfig.build.json` [B-02]:
|
|
1725
|
+
`__suppressNoHandlerWarn` y cualquier miembro `@internal` no
|
|
1726
|
+
aparecen en el `.d.ts` publicado.
|
|
1727
|
+
- **Excludes del dts publicado** [B-06]: `src/test-utils/`,
|
|
1728
|
+
`src/stories/`, helpers internos (`env.ts` borrado, `useIsoLayoutEffect`,
|
|
1729
|
+
`mergeDescribedBy`) ya no viajan al tarball. Script
|
|
1730
|
+
`scripts/clean-internal-dist.mjs` post-build defensivo.
|
|
1731
|
+
- **`vite-env.d.ts` aislado** [B-07-followup]: nuevo
|
|
1732
|
+
`src/_internal-env.d.ts` con SOLO `ImportMetaEnv` para que tsc.build
|
|
1733
|
+
resuelva `import.meta.env.DEV` sin contaminar el dts publicado con
|
|
1734
|
+
`declare module "*.css"`.
|
|
1735
|
+
- **MDX foundations canónicamente en `docs/`** [M-06]: 6 páginas
|
|
1736
|
+
movidas de `src/stories/` a `docs/`. Storybook `main.ts` añade
|
|
1737
|
+
`../docs/**/*.mdx` a `stories`.
|
|
1738
|
+
- **`"use client";` en bundle** [B-17]: directiva añadida vía
|
|
1739
|
+
`output.banner` en `vite.lib.config.ts` (además del source) para
|
|
1740
|
+
garantizar que llega al bundle final tras minify. Soporta consumers
|
|
1741
|
+
Next.js App Router en Server Components.
|
|
1742
|
+
- **CI verify pipeline reforzado**:
|
|
1743
|
+
- `test:scope-leaks --strict` [B-15] añadido como step.
|
|
1744
|
+
- `verify:unit` ahora encadena `test:unit:ci` (isolate + forks),
|
|
1745
|
+
no `test:unit` [B-16].
|
|
1746
|
+
- Bundle dev-warn guard con greps explícitos [B-07-followup].
|
|
1747
|
+
- Chromatic sin `--auto-accept-changes=main` ni
|
|
1748
|
+
`--exit-zero-on-changes` [H-02]: revisión visual humana
|
|
1749
|
+
obligatoria pre-RC1.
|
|
1750
|
+
- **Build optimizado** [H-04]: `vite.lib.config.ts`
|
|
1751
|
+
`build.minify: "esbuild"`. Shave ~15% del bundle gzipped.
|
|
1752
|
+
- **Perceptual allowlist** [B-13]: reversión consciente del tripwire
|
|
1753
|
+
`dark axis-kobalium` (ΔE 0.0522) introducido en c8a5202 (beta.18).
|
|
1754
|
+
El tripwire nunca fue operativo (`error_threshold=0.05 < 0.0522`).
|
|
1755
|
+
NO se tocan tokens. Anti-regresión:
|
|
1756
|
+
`src/_audit/perceptual-allowlist.test.ts` falla CI si la entrada
|
|
1757
|
+
desaparece sin razón documentada.
|
|
1758
|
+
- **`merge-refs` consistente**: Stepper, Checkbox y Switch usan
|
|
1759
|
+
ahora el mismo patrón `useCallback(setRefs, [ref])`.
|
|
1760
|
+
- **Higiene del repo**: `.claude/`, `.notes-*`, `.release-*`,
|
|
1761
|
+
`BLOQUEOS.md`, `SESION-RESUMEN*.md` añadidos a `.gitignore`.
|
|
1762
|
+
`src/utils/env.ts` (huérfano post-B-07) borrado.
|
|
1763
|
+
|
|
1764
|
+
### Docs
|
|
1765
|
+
|
|
1766
|
+
- **Pagination** uncontrolled documentado en README [B-14].
|
|
1767
|
+
- **`Introduction.mdx`**: banner pre-publicación recomendando
|
|
1768
|
+
`npm install reactigoded@beta` [B-11].
|
|
1769
|
+
- **`state.css` size** corregida en 4 sitios:
|
|
1770
|
+
`~6.5 MB sin gzip / ~700 KB gzipped` (antes "7.1 MB") [B-11].
|
|
1771
|
+
- **JSDoc** de `Rating`, `Tabs`, `TabsContext`: ejemplos migrados
|
|
1772
|
+
a `aria-label` HTML estándar (antes mostraban API obsoleta
|
|
1773
|
+
`ariaLabel`) [B-10].
|
|
1774
|
+
- **`docs/DS_AUTOSUFFICIENCY_DEBT.md`** capa 1.4 marcada cerrada
|
|
1775
|
+
[H-06].
|
|
1776
|
+
- **CHANGELOG** referencia stale a `BLOQUEOS.md` reemplazada por
|
|
1777
|
+
puntero a `docs/DS_AUTOSUFFICIENCY_DEBT.md` [H-07].
|
|
1778
|
+
- **`docs/RC1_DECISIONS.md`**: registro de las 4 decisiones humanas
|
|
1779
|
+
B1-B4 confirmadas explícitamente.
|
|
1780
|
+
- **`docs/POST_RC1_BACKLOG.md`** + **`docs/RC1_FOUND_DURING_FIX.md`**:
|
|
1781
|
+
trackers de la deuda diferida y los hallazgos descubiertos durante
|
|
1782
|
+
el ciclo.
|
|
1783
|
+
- **README**: sección "Desarrollo" documenta el requisito de
|
|
1784
|
+
`npm ci --legacy-peer-deps` [H-08].
|
|
1785
|
+
|
|
1786
|
+
### Bundle stats (size-limit, gzip)
|
|
1787
|
+
|
|
1788
|
+
| Bundle | Tamaño | Límite | Uso |
|
|
1789
|
+
|---|---|---|---|
|
|
1790
|
+
| JS ESM | 13.96 KB | 16 KB | 87% |
|
|
1791
|
+
| JS CJS | 12.39 KB | 15 KB | 83% |
|
|
1792
|
+
| state.css | 713.54 KB | 800 KB | 89% |
|
|
1793
|
+
| components.css | 28.05 KB | 75 KB | 37% |
|
|
1794
|
+
| tokens.css | 6.53 KB | 30 KB | 22% |
|
|
1795
|
+
|
|
1796
|
+
JS ESM bajó de 14.91 KB (beta.21) a 13.96 KB tras añadir
|
|
1797
|
+
`build.minify: "esbuild"` (H-04), absorbiendo además el coste del
|
|
1798
|
+
`SkeletonContainer` nuevo y los 5 tests de hook adicionales.
|
|
1799
|
+
|
|
1800
|
+
## [1.0.0-beta.21] — 2026-05-05
|
|
1801
|
+
|
|
1802
|
+
### Added
|
|
1803
|
+
- **`useControllableState`** dev warn re-aplicado vía Option E
|
|
1804
|
+
(escape hatch interno `__suppressNoHandlerWarn`). Avisa cuando
|
|
1805
|
+
un componente está en modo controlled (`value` definido) sin
|
|
1806
|
+
`onChange` y sin el flag de suppress. Una vez por instancia.
|
|
1807
|
+
Tras el revert en beta.20 por falsos positivos en
|
|
1808
|
+
`Rating.SoloLectura`, ahora cada componente con modo
|
|
1809
|
+
display-only legítimo (Rating con `readOnly`) suprime el warn
|
|
1810
|
+
vía el flag interno.
|
|
1811
|
+
- **Audit consistente del hook** en los 9 componentes que lo
|
|
1812
|
+
usan: Rating y Slider ahora pasan `onChange: onValueChange` al
|
|
1813
|
+
hook (eliminando llamadas duplicadas locales). Switch usa
|
|
1814
|
+
`__suppressNoHandlerWarn: true` permanente porque su `onChange`
|
|
1815
|
+
recibe `ChangeEvent` (no boolean) y tiene un warn local más
|
|
1816
|
+
específico.
|
|
1817
|
+
- **Script `scripts/check-css-scope-leaks.mjs`** + allowlist
|
|
1818
|
+
`scope-leak-allowlist.json`. Cierra deuda capa 3.1 (PRIORIDAD
|
|
1819
|
+
ALTA). Detecta riesgo de scope-leak: clases globales
|
|
1820
|
+
modificadoras (`.ig-X-active`, `.ig-X-danger`…) emitidas en >1
|
|
1821
|
+
elemento del mismo componente. Modo `--strict` integrado en
|
|
1822
|
+
`verify:unit` pipeline. Run inicial: 420 candidatas / 6
|
|
1823
|
+
allowlisted (audit case-by-case con razón documentada) / 0
|
|
1824
|
+
riesgos nuevos.
|
|
1825
|
+
|
|
1826
|
+
### Tests
|
|
1827
|
+
- Hook: 5 tests del warn (dispara/no dispara/escape hatch/una
|
|
1828
|
+
vez por instancia).
|
|
1829
|
+
- Rating: 2 tests anti-regresión (`readOnly` no genera warn;
|
|
1830
|
+
controlled sin readOnly y sin onValueChange sí lo genera).
|
|
1831
|
+
|
|
1832
|
+
### Internal
|
|
1833
|
+
- `Rating.stories.tsx AllStates`: instancias `<Rating value={N} />`
|
|
1834
|
+
marcadas `readOnly` (eran galería visual, no interactivas).
|
|
1835
|
+
- README.md: actualizado a "última publicación: 1.0.0-beta.20".
|
|
1836
|
+
|
|
1837
|
+
## [1.0.0-beta.20] — 2026-05-05
|
|
1838
|
+
|
|
1839
|
+
### Added
|
|
1840
|
+
- **`Card`**: prop polimórfica `as` con genéricos TS
|
|
1841
|
+
(`<C extends ElementType = "div">`). Acepta strings HTML
|
|
1842
|
+
(`as="article"`, `as="a"` con `href` tipado) y componentes
|
|
1843
|
+
(`as={Link}` para react-router/next). Las props específicas del
|
|
1844
|
+
elemento subyacente se tipan automáticamente. Story `Polimorfica`
|
|
1845
|
+
añadida.
|
|
1846
|
+
- **`Pagination`**: modo **uncontrolled** vía `useControllableState`.
|
|
1847
|
+
`currentPage` y `onValueChange` ahora opcionales; `defaultPage`
|
|
1848
|
+
(default 1) inicializa el state interno. Patrón consistente con
|
|
1849
|
+
Tabs / Accordion / OptionsMenu / etc. Story `Uncontrolled` añadida.
|
|
1850
|
+
- **`Stepper`**: modo **interactive** opt-in con `onValueChange`.
|
|
1851
|
+
Cuando se pasa el callback, cada step se vuelve focuseable
|
|
1852
|
+
(`role="button"` + roving tabIndex) y soporta keyboard nav
|
|
1853
|
+
completo: ArrowLeft/Right/Up/Down, Home/End, Enter/Space. Sin
|
|
1854
|
+
`onValueChange` permanece presentational (backwards compatible).
|
|
1855
|
+
Nueva clase `.ig-step-interactive`. `aria-label="Paso N"` en
|
|
1856
|
+
cada dot interactivo.
|
|
1857
|
+
- **`Progress`**: props `loadingLabel` (default `"Cargando"`) y
|
|
1858
|
+
`formatLabel?: (percent: number) => string` para i18n del
|
|
1859
|
+
`aria-label`. Cierra el inventario de strings hardcoded
|
|
1860
|
+
user-facing del DS.
|
|
1861
|
+
- **`AllStates` matrix Ola 2 (16 componentes)**: stories
|
|
1862
|
+
`chromatic.modes light+dark` para Alert, Radio, Rating, Slider,
|
|
1863
|
+
Stepper, Pagination, Tooltip, Timeline, Accordion, Sidebar,
|
|
1864
|
+
Navbar, ThemeSwitch, Toast, OptionsMenu, Modal, Input compound,
|
|
1865
|
+
Table, Textarea, NativeSelect. Cobertura visual completa en Chromatic
|
|
1866
|
+
bajo dual-mode.
|
|
1867
|
+
- **`useControllableState` derive mode**: `ThemeSwitch` migrado
|
|
1868
|
+
a `useControllableState({ derive, setDerivedValue, ... })`.
|
|
1869
|
+
Consolida los 9 componentes con state controlled/uncontrolled
|
|
1870
|
+
bajo el mismo hook + soporte explícito para fuente de verdad
|
|
1871
|
+
externa.
|
|
1872
|
+
- **`docs/CSSAPI.mdx`**: nueva sección "i18n y a11y strings" con
|
|
1873
|
+
tabla de overrides + patrón de uso con `react-i18next`. Notas
|
|
1874
|
+
sobre `Card.as` polimórfica y `Pagination` controlled+uncontrolled.
|
|
1875
|
+
- **`docs/DS_AUTOSUFFICIENCY_DEBT.md`**: capa 6 nueva
|
|
1876
|
+
"Convenciones de tests unit". 6.1 ✅ regla anti-`console.error`
|
|
1877
|
+
mock para warnings React (causa: vitest `isolate: false` +
|
|
1878
|
+
React dedupe). 6.2 ✅ nota DOM Switch (`ig-switch` en `<label>`).
|
|
1879
|
+
- **`src/test-utils/`**: utilities `queryAllByRoleSafe()` y
|
|
1880
|
+
`expectAtLeast()` exportadas (capa 1.5/1.6 debt doc).
|
|
1881
|
+
- **Tests del CONTRATO** de `useControllableState` (4 tests
|
|
1882
|
+
hook-first cubriendo ambos modos × ambas direcciones de
|
|
1883
|
+
transición controlled↔uncontrolled). Smokes en componentes
|
|
1884
|
+
con input nativo (Rating, Slider, Switch).
|
|
1885
|
+
|
|
1886
|
+
### Changed
|
|
1887
|
+
- **`Pagination`**: `currentPage` y `onValueChange` pasaron de
|
|
1888
|
+
required a opcionales. Cambio API público pre-1.0.0 (sin
|
|
1889
|
+
breaking productivo). Consumers que ya pasaban ambos siguen
|
|
1890
|
+
funcionando idénticamente.
|
|
1891
|
+
- **`Stepper`**: clase global `.ig-step-active` migrada a selector
|
|
1892
|
+
compound `.ig-step.ig-step-active` para evitar scope-leak al
|
|
1893
|
+
wrapper `.ig-step-item.ig-step-active` (bug latente desde beta.5
|
|
1894
|
+
detectado en sub-Bloque A: dejaba label en contraste 1.02).
|
|
1895
|
+
Anti-regresión test añadido.
|
|
1896
|
+
- Defaults ES de strings user-facing (`Alert.closeLabel`,
|
|
1897
|
+
`Chip.removeLabel`, `ModalClose aria-label`, `Pagination.prevLabel`/
|
|
1898
|
+
`nextLabel`, `Progress.loadingLabel`, `Stepper aria-label`,
|
|
1899
|
+
`Toast.closeLabel`) declarados intencionales en `CSSAPI.mdx`.
|
|
1900
|
+
Audience inicial hispanohablante; cambio a EN se reevaluará en
|
|
1901
|
+
1.1.0 si demanda real lo justifica.
|
|
1902
|
+
- **`Slider` / `Switch`** tests viejos de transición controlled↔
|
|
1903
|
+
uncontrolled que afirmaban sobre warning de React vía
|
|
1904
|
+
`console.error` mock reemplazados por assertions sobre
|
|
1905
|
+
comportamiento observable. Los anteriores eran flaky por orden:
|
|
1906
|
+
vitest `isolate: false` + React dedupe de warnings dev por
|
|
1907
|
+
proceso. Documentado como regla en debt doc capa 6.1.
|
|
1908
|
+
|
|
1909
|
+
### Fixed
|
|
1910
|
+
- **`Stepper`**: scope-leak CSS de `.ig-step-active` (ver Changed).
|
|
1911
|
+
- Documentación JSDoc de `Switch` clarifica estructura DOM
|
|
1912
|
+
(`ig-switch` en `<label>` wrapper, NO en `<input>`).
|
|
1913
|
+
|
|
1914
|
+
### Internal
|
|
1915
|
+
- Stories storybook con `chromatic.modes light+dark` ya
|
|
1916
|
+
proporcionan dual-theme matrix sin duplicar archivos test:storybook.
|
|
1917
|
+
- Tracking de decisiones diferidas y deuda RC1 vive en
|
|
1918
|
+
`docs/DS_AUTOSUFFICIENCY_DEBT.md`.
|
|
1919
|
+
- Decisión arquitectónica registrada: warn dev de
|
|
1920
|
+
`useControllableState` para `value=` sin `onChange=` diferido a
|
|
1921
|
+
rc.1 con escape hatch interno `__suppressNoHandlerWarn`.
|
|
1922
|
+
Intento beta.20 (commit 2975e19) revertido por falsos positivos
|
|
1923
|
+
en `Rating.SoloLectura` (readOnly legítimo). Diseño Option E
|
|
1924
|
+
documentado en debt doc sección 1.4.
|
|
1925
|
+
|
|
1926
|
+
### Bundle stats (size-limit, gzip)
|
|
1927
|
+
|
|
1928
|
+
| Bundle | Tamaño | Límite | Uso |
|
|
1929
|
+
|---|---|---|---|
|
|
1930
|
+
| JS ESM | 14.91 KB | 15 KB | 99.4% |
|
|
1931
|
+
| JS CJS | 12.85 KB | 15 KB | 85.7% |
|
|
1932
|
+
| `tokens.css` | 6.53 KB | 30 KB | 21.8% |
|
|
1933
|
+
| `components.css` | 28.05 KB | 75 KB | 37.4% |
|
|
1934
|
+
| `base.css` | 453 B | 2 KB | 22.7% |
|
|
1935
|
+
| `reset.css` | 924 B | 2 KB | 46.2% |
|
|
1936
|
+
| `fonts.css` | 142 B | 1 KB | 14.2% |
|
|
1937
|
+
| `state.css` | 713.54 KB | 800 KB | 89.2% |
|
|
1938
|
+
| `design.css` (meta) | 70 B | 2 KB | 3.5% |
|
|
1939
|
+
|
|
1940
|
+
JS ESM al 99% del budget — beta.21/rc.1 debería revisar headroom o
|
|
1941
|
+
subir el límite ante crecimiento de features (Card.as polimórfica
|
|
1942
|
+
+ Stepper keyboard nav son los principales nuevos). state.css cerca
|
|
1943
|
+
del límite (89%) — es natural por el growth de utilities pseudo-class.
|
|
1944
|
+
|
|
1945
|
+
## [1.0.0-beta.19] — 2026-05-04
|
|
1946
|
+
|
|
1947
|
+
### Breaking
|
|
1948
|
+
- **Tooltip**: clases CSS migradas a prefijos únicos para evitar
|
|
1949
|
+
colisión semántica.
|
|
1950
|
+
- `ig-tooltip-{top,right,bottom,left}` → `ig-tooltip-place-{...}`
|
|
1951
|
+
- `ig-tooltip-{brand,secondary,success,warning,danger,info}` →
|
|
1952
|
+
`ig-tooltip-color-{...}`
|
|
1953
|
+
Solo afecta a consumers vanilla con clases hardcoded; React API
|
|
1954
|
+
intacta. Migración automática vía `scripts/migrate-tooltip-prefixes.mjs`.
|
|
1955
|
+
- **OptionsMenu**: clase de estado abierto migrada de `.open` a
|
|
1956
|
+
`.ig-options-menu-open` para respetar la convención de namespace `ig-*`.
|
|
1957
|
+
- **Stepper**: prop `defaultActive` eliminada (era engañosa — el
|
|
1958
|
+
componente nunca fue uncontrolled). Migración:
|
|
1959
|
+
`<Stepper defaultActive={X}>` → `<Stepper active={X}>`. Si necesitas
|
|
1960
|
+
uncontrolled, mantén `[step, setStep]` en el consumer.
|
|
1961
|
+
- **Navbar**: dos booleans `sticky`/`fixed` mutuamente excluyentes
|
|
1962
|
+
reemplazados por un único `position?: 'sticky' | 'fixed'`.
|
|
1963
|
+
Migración:
|
|
1964
|
+
|
|
1965
|
+
| Antes | Después |
|
|
1966
|
+
|---|---|
|
|
1967
|
+
| `<Navbar sticky>` | `<Navbar position="sticky">` |
|
|
1968
|
+
| `<Navbar fixed>` | `<Navbar position="fixed">` |
|
|
1969
|
+
| `<Navbar sticky fixed>` | (TS error, antes silencioso) |
|
|
1970
|
+
|
|
1971
|
+
### Fixed
|
|
1972
|
+
- **OptionsMenu**: keyboard open salta items con `aria-disabled="true"`
|
|
1973
|
+
desde el trigger (fix incompleto del bug original que solo cubría
|
|
1974
|
+
`OptionsMenuItem`). Selector compartido en `optionsMenuSelectors.ts`.
|
|
1975
|
+
- **Tabs**: sin `value`/`defaultValue` selecciona el PRIMER tab
|
|
1976
|
+
registrado, no el último. El auto-select usa `setSelectedRaw` con
|
|
1977
|
+
`{ silent: true }` — ya no dispara `onValueChange`.
|
|
1978
|
+
- **Pagination**: clamp completo de `currentPage`, `totalPages` y
|
|
1979
|
+
`siblingCount` fuera de rango (NaN, Infinity, negativos). Separación
|
|
1980
|
+
`prevLabel`/`nextLabel` (children visible) de
|
|
1981
|
+
`prevAriaLabel`/`nextAriaLabel` (a11y).
|
|
1982
|
+
- **Progress**: guards para `max ≤ 0` o no-finite.
|
|
1983
|
+
- **Slider**: guard NaN.
|
|
1984
|
+
- **Card**: `tabIndex={0}` automático cuando actsAsButton (con override
|
|
1985
|
+
explícito respetado).
|
|
1986
|
+
- **Rating**: `readOnly` aplica `aria-readonly` al radiogroup, no
|
|
1987
|
+
`disabled` a los radios (mejora a11y para SR).
|
|
1988
|
+
- **Checkbox**/**Switch**/**Tab**: `useLayoutEffect` con fallback
|
|
1989
|
+
`useEffect` en SSR (no `() => {}`). Util compartida
|
|
1990
|
+
`src/utils/useIsoLayoutEffect.ts`.
|
|
1991
|
+
- **Tab**: keyboard nav usa `closest('[role="tablist"]')` en lugar de
|
|
1992
|
+
`parentElement` (robustez ante wrappers).
|
|
1993
|
+
- **Tooltip**: warn dev-only si `children` no es elemento React válido.
|
|
1994
|
+
- **README**: clases para migración de `<button>` corregidas
|
|
1995
|
+
(`.ig-btn*`, no `.ig-button*`).
|
|
1996
|
+
- **igoded-design.css**: cabecera `info → axis` corregida a
|
|
1997
|
+
`info → kobalium`.
|
|
1998
|
+
- **manager.ts** y stories Card/Avatar: hex de paleta actualizados a
|
|
1999
|
+
la vigente desde beta.16 (`#5eded5` → `#3ae2f7`,
|
|
2000
|
+
`#d4c2f9` → `#d2bff7`).
|
|
2001
|
+
- **SKILL.md**: tabla ΔE OKLab regenerada con cifras reales (eran de
|
|
2002
|
+
paleta pre-beta.16).
|
|
2003
|
+
- **styles.test.ts**: test "7 cardinales" verificaba 6, faltaba
|
|
2004
|
+
kobalium.
|
|
2005
|
+
- **mixColors** en `check-component-contrast.mjs`: shortest-arc para
|
|
2006
|
+
hue circular (bug latente que no se disparaba con tokens actuales
|
|
2007
|
+
pero podía con futuros).
|
|
2008
|
+
|
|
2009
|
+
### Added
|
|
2010
|
+
- **`useControllableState<T>`** hook centralizado para patrón
|
|
2011
|
+
controlled/uncontrolled, con setter `silent` opt-in. **9
|
|
2012
|
+
componentes migrados**: Switch, Sidebar, Slider, Rating, Accordion
|
|
2013
|
+
(single + multiple), Alert, OptionsMenu, Tabs. 9 tests del hook
|
|
2014
|
+
(incluyendo `setValue` con `{ silent: true }` para auto-selects y
|
|
2015
|
+
rehidratación de fuentes externas no-de-usuario).
|
|
2016
|
+
- **AllStates Ola 1** — 14 stories matrix con `chromatic.modes`
|
|
2017
|
+
light + dark: Button, Input, Checkbox, Switch, Tabs, Progress,
|
|
2018
|
+
Card, Badge, Spinner, Skeleton, Divider, Breadcrumb, Avatar, Chip.
|
|
2019
|
+
Util compartida `src/stories/_matrix.tsx` con `MatrixGrid`. 28
|
|
2020
|
+
snapshots Chromatic. Ola 2 (16 componentes interactivos +
|
|
2021
|
+
compounds) en beta.20.
|
|
2022
|
+
- **Tests regresión visual AllStates** — 14 tests vía `composeStory`,
|
|
2023
|
+
uno por componente Ola 1, anti-regresión de "alguien borra una
|
|
2024
|
+
variant".
|
|
2025
|
+
- **`Fundamentos/CSS API pública`** — MDX exhaustiva con tabla
|
|
2026
|
+
detallada y ejemplo HTML por los 32 componentes para consumers
|
|
2027
|
+
vanilla. Linkada desde el README.
|
|
2028
|
+
- **`Fundamentos/Catálogo AllStates`** — índice MDX que lista los
|
|
2029
|
+
matrices visuales por ola.
|
|
2030
|
+
- **`scripts/perceptual-allowlist.json`** con 2 excepciones
|
|
2031
|
+
documentadas (`laurus-vitreus` LIGHT 0.0847,
|
|
2032
|
+
`malum-rutilus` DARK 0.0706).
|
|
2033
|
+
- **`check-component-contrast.mjs --print-perceptual-table`** modo
|
|
2034
|
+
debug para regenerar tabla del SKILL.
|
|
2035
|
+
- **Tests añadidos** (no exhaustivo): OptionsMenu aria-disabled (×2),
|
|
2036
|
+
Tabs auto-select silent (×4), Pagination clamps (×6),
|
|
2037
|
+
Pagination prev/nextAriaLabel (×2), Progress guards (×6),
|
|
2038
|
+
Card tabIndex (×3), Rating aria-readonly (×2),
|
|
2039
|
+
useControllableState (×9), Tab keyboard wrapped, Spinner.label i18n
|
|
2040
|
+
(×3), Sidebar.ariaLabel i18n (×3).
|
|
2041
|
+
- **`.github/workflows/verify.yml`** — gate CI completo (lint,
|
|
2042
|
+
typecheck, test:unit, test:contrast, build, verify:size) + job
|
|
2043
|
+
separado `storybook` con playwright.
|
|
2044
|
+
- **`size-limit`** budget para `igoded-state-css.css` (techo 800 KB
|
|
2045
|
+
gzip).
|
|
2046
|
+
- **`src/utils/env.ts`** — `isDev()` helper sin global augmentation.
|
|
2047
|
+
- **`src/utils/useIsoLayoutEffect.ts`** — patrón canónico SSR-safe.
|
|
2048
|
+
- **i18n explícitas**: `Spinner.label`, `Sidebar.ariaLabel`,
|
|
2049
|
+
`Avatar.statusLabel`, `Pagination.prevAriaLabel`/`nextAriaLabel`.
|
|
2050
|
+
- **`scripts/migrate-tooltip-prefixes.mjs`** — pase con PostCSS AST
|
|
2051
|
+
para la migración de Tooltip.
|
|
2052
|
+
|
|
2053
|
+
### Changed
|
|
2054
|
+
- **`scripts/check-component-contrast.mjs`**: Check 3 ΔE OKLab
|
|
2055
|
+
promovido a ERROR con umbral 0.05 + WARN umbral 0.10. Allowlist
|
|
2056
|
+
explícita con drift detection (95% del valor de decisión). Cómputo
|
|
2057
|
+
automático de los pares de cardinales por tema (30 totales).
|
|
2058
|
+
- **`dist/env.d.ts`** ya no se publica al consumer
|
|
2059
|
+
(`tsconfig.build.json` lo excluye explícitamente). Componentes
|
|
2060
|
+
ahora usan `isDev()` util en lugar de `import.meta.env.DEV` directo.
|
|
2061
|
+
- **`Modal`**: `ModalContextValue` removido del barrel export (era
|
|
2062
|
+
huérfano sin `ModalContext` ni `useModal()` exportados).
|
|
2063
|
+
- **CSS publicados minificados con esbuild** (state.css 6.3 MB →
|
|
2064
|
+
713 KB gzipped, components.css 28 KB gzipped).
|
|
2065
|
+
- **`design.css`** size-limit budget bajado de 5 KB a 2 KB (real
|
|
2066
|
+
~70 B gzipped).
|
|
2067
|
+
- **`tokens.css`** size-limit budget subido de 25 KB a 30 KB para
|
|
2068
|
+
margen.
|
|
2069
|
+
|
|
2070
|
+
### Removed
|
|
2071
|
+
- CSS huérfano: `.ig-modal`, `.ig-modal-backdrop` en
|
|
2072
|
+
`igoded-components.css` (3 ocurrencias residuales del cleanup
|
|
2073
|
+
beta.0; el componente Modal usa `<dialog>`/`.ig-dialog`).
|
|
2074
|
+
- Referencias prematuras a `1.0.0-rc.{1,2,3}` en código y docs
|
|
2075
|
+
(sustituidas por `1.0.0-beta.8` o `pre-1.0.0-rc.1` según contexto).
|
|
2076
|
+
- `Stepper.defaultActive` (era prop muerta).
|
|
2077
|
+
- `src/env.d.ts` (sustituido por `src/utils/env.ts` con `isDev()`).
|
|
2078
|
+
|
|
2079
|
+
### Notes
|
|
2080
|
+
- **2 componentes saltados en migración a `useControllableState`**
|
|
2081
|
+
por razones técnicas documentadas:
|
|
2082
|
+
- **`ThemeSwitch`**: triple fuente de truth (controlled prop +
|
|
2083
|
+
override interno + storage cross-tab vía `useSyncExternalStore`).
|
|
2084
|
+
El componente original deriva el valor en render directamente
|
|
2085
|
+
de las fuentes; el hook genérico introduciría un `useState`
|
|
2086
|
+
interno que en happy-dom provoca loop infinito por la cadena
|
|
2087
|
+
`setItem → StorageEvent → snapshot revaluation → setState →
|
|
2088
|
+
setItem`. El patrón actual es idiomático para este caso, no es
|
|
2089
|
+
deuda técnica. Para post-RC1: extender el hook con modo
|
|
2090
|
+
`derive: () => T` opt-in o crear
|
|
2091
|
+
`useControllableStateWithStorage` dedicado.
|
|
2092
|
+
- **`Modal`**: controlled-only puro (`open: boolean` requerido,
|
|
2093
|
+
sin `defaultOpen`). El hook no aporta valor; siempre sería
|
|
2094
|
+
`isControlled=true`.
|
|
2095
|
+
- **`Checkbox`**: NO tiene `useState` interno — delega checked
|
|
2096
|
+
al `<input type="checkbox">` nativo. La migración añadiría
|
|
2097
|
+
complejidad sin valor.
|
|
2098
|
+
- **Migration guide útil — `useControllableState` API**:
|
|
2099
|
+
|
|
2100
|
+
```ts
|
|
2101
|
+
const { value, setValue } = useControllableState({ value, defaultValue, onChange });
|
|
2102
|
+
|
|
2103
|
+
// Acción del usuario (dispara onChange):
|
|
2104
|
+
setValue(newValue);
|
|
2105
|
+
|
|
2106
|
+
// Auto-select interno o rehidratación post-mount (NO dispara onChange):
|
|
2107
|
+
setValue(newValue, { silent: true });
|
|
2108
|
+
```
|
|
2109
|
+
|
|
2110
|
+
- **Tag retroactivo `v1.0.0-beta.0`** apuntado al commit
|
|
2111
|
+
`8a1c7ef5f030ba7bf227b53a0b8469fa442149e3` (`feat: rewrite as
|
|
2112
|
+
TypeScript + React 19 design system`, 2026-05-01) para
|
|
2113
|
+
trazabilidad histórica completa de la rama 1.0.x.
|
|
2114
|
+
|
|
2115
|
+
### Bundle stats (gzip)
|
|
2116
|
+
|
|
2117
|
+
| Archivo | Real | Budget | Margen |
|
|
2118
|
+
|--------------------------------------------|----------|---------|--------|
|
|
2119
|
+
| `dist/index.js` (ESM) | 14.37 KB | 15 KB | 4% |
|
|
2120
|
+
| `dist/index.cjs` | 12.36 KB | 15 KB | 18% |
|
|
2121
|
+
| `dist/styles/igoded-design.css` | 70 B | 2 KB | 97% |
|
|
2122
|
+
| `dist/styles/igoded-tokens.css` | 6.53 KB | 30 KB | 78% |
|
|
2123
|
+
| `dist/styles/igoded-components.css` | 28.05 KB | 75 KB | 63% |
|
|
2124
|
+
| `dist/styles/igoded-base.css` | 453 B | 2 KB | 78% |
|
|
2125
|
+
| `dist/styles/igoded-reset.css` | 924 B | 2 KB | 55% |
|
|
2126
|
+
| `dist/styles/igoded-fonts.css` | 142 B | 1 KB | 86% |
|
|
2127
|
+
| `dist/styles/igoded-state-css.css` | 713.5 KB | 800 KB | 11% |
|
|
2128
|
+
|
|
2129
|
+
### Test stats
|
|
2130
|
+
- Suite unit final: **515** tests (`vitest run --project unit`).
|
|
2131
|
+
- 36 archivos de test, 100% verde.
|
|
2132
|
+
- 14 nuevos AllStates regression tests (composeStory + selectores
|
|
2133
|
+
CSS).
|
|
2134
|
+
|
|
2135
|
+
## [1.0.0-beta.18] — 2026-05-03
|
|
2136
|
+
|
|
2137
|
+
### Changed
|
|
2138
|
+
- **Test cleanup pre-RC1**: refactor masivo de tests unit.
|
|
2139
|
+
- **Variantes/sizes/forma cosmética**: colapsadas en `describe.each`
|
|
2140
|
+
parametrizado. Antes había 10 asserts sueltos por componente
|
|
2141
|
+
(uno por valor del eje); ahora un solo bloque parametrizado
|
|
2142
|
+
cubre todos los valores con una assert densa. Misma señal de
|
|
2143
|
+
contrato CSS, código 5× más compacto. Aplicado en Badge, Button,
|
|
2144
|
+
Card, Checkbox, Chip, Divider, Input, Modal, Navbar, Progress,
|
|
2145
|
+
Radio, Spinner, Switch, Tabs, Tooltip, Avatar, Rating.
|
|
2146
|
+
- **Estados funcionales con efecto observable**: `loading` del
|
|
2147
|
+
Button ahora expone `aria-busy` además de la clase, y el test
|
|
2148
|
+
valida ARIA + behavior (no solo clase). Combo `loading + disabled`
|
|
2149
|
+
cubierto explícitamente.
|
|
2150
|
+
- **Negative asserts** (`not.toHaveClass(default)`, `not.toHaveClass(success)`
|
|
2151
|
+
cuando outline) eliminados — paranoia frágil que rompía con
|
|
2152
|
+
cualquier rename.
|
|
2153
|
+
- **`className` merge desde consumer**: añadido test por componente
|
|
2154
|
+
(Badge, Button, Switch, Checkbox, Radio) verificando que las
|
|
2155
|
+
clases del consumer se mergean sin pisar las del componente. Bug
|
|
2156
|
+
recurrente que Chromatic no detecta.
|
|
2157
|
+
- **Stories `play()` duplicadas**: eliminadas `Checkbox.ToggleInteraction`
|
|
2158
|
+
y `Radio.SelectInteraction` (mismo aserto que el unit test, 10×
|
|
2159
|
+
más lentas).
|
|
2160
|
+
|
|
2161
|
+
### Added
|
|
2162
|
+
- `Button` ahora expone `aria-busy={loading}` (antes solo clase).
|
|
2163
|
+
Mejora a11y para SR.
|
|
2164
|
+
|
|
2165
|
+
### Test stats
|
|
2166
|
+
- Suite unit final tras refactor: **453** (`vitest run --project unit`).
|
|
2167
|
+
Con `describe.each` el conteo bruto sube por la expansión de cada
|
|
2168
|
+
parametrización; lo relevante es que el contrato CSS/ARIA queda
|
|
2169
|
+
cubierto con menos archivos y asserts más densos.
|
|
2170
|
+
- Asserts cosméticos sueltos eliminados: ~80.
|
|
2171
|
+
- Tests transversales nuevos: 5 (`className` merge), 1 combo de
|
|
2172
|
+
estado peligroso (`loading + disabled`).
|
|
2173
|
+
- Nota: a partir de esta versión NO publicamos delta numérico
|
|
2174
|
+
contra la versión anterior; el conteo puede oscilar libremente
|
|
2175
|
+
con cada refactor de parametrización y no es una métrica
|
|
2176
|
+
contractual del paquete.
|
|
2177
|
+
|
|
2178
|
+
## [1.0.0-beta.17] — 2026-05-03
|
|
2179
|
+
|
|
2180
|
+
### Fixed
|
|
2181
|
+
- **Textarea / NativeSelect estados `error` y `success` invisibles**: las
|
|
2182
|
+
reglas `.ig-input-error` / `.ig-input-success` (línea 6227) estaban
|
|
2183
|
+
declaradas ANTES de `.ig-textarea`, `.ig-textarea-auto` y `.ig-native-select`
|
|
2184
|
+
(líneas 6306, 6343, 6376), todas con la misma especificidad 0,1,0.
|
|
2185
|
+
Las reglas posteriores definían `border-color` y pisaban al estado de
|
|
2186
|
+
validación. Resultado: pasar `state="error"` o `state="success"` no
|
|
2187
|
+
cambiaba el borde del componente. Fix: reglas con doble clase (0,2,0)
|
|
2188
|
+
para `.ig-textarea.ig-input-error`, `.ig-native-select.ig-input-error`, etc.
|
|
2189
|
+
Input no estaba afectado porque `.ig-input` se declara antes de las
|
|
2190
|
+
reglas de estado.
|
|
2191
|
+
|
|
2192
|
+
## [1.0.0-beta.16] — 2026-05-03
|
|
2193
|
+
|
|
2194
|
+
### Changed
|
|
2195
|
+
- **Vitreus reposicionado** de H≈194.83° (cyan puro sRGB, artefacto
|
|
2196
|
+
histórico de la paleta) a H=207.50° (centro perceptual matemático
|
|
2197
|
+
verde↔azul en OKLCH, punto medio entre verde Hering ~145° y azul
|
|
2198
|
+
Hering ~270°). Hex: `#053a40` (lux) / `#3ae2f7` (nox). Decisión
|
|
2199
|
+
fundacional — vitreus es el color brand del DS y su identidad
|
|
2200
|
+
declarada es "el azul-verde matemáticamente medio en OKLCH"; el hex
|
|
2201
|
+
anterior no cumplía esa intención. Contraste AAA preservado en los
|
|
2202
|
+
5 fondos del tema en ambos modos (LIGHT 11.78, DARK 11.91 sobre
|
|
2203
|
+
fundus opuesto). Las alphas (`--ig-vitreus-alpha-{10,20,30,50,70}`)
|
|
2204
|
+
y `--ig-text-heading` heredan automáticamente porque están definidas
|
|
2205
|
+
con `color-mix` sobre `--ig-vitreus`. Re-baseline de Chromatic
|
|
2206
|
+
requerida para snapshots con brand, text-heading o sus alphas.
|
|
2207
|
+
|
|
2208
|
+
### Fixed
|
|
2209
|
+
- **Badge default vs Badge pill**: ambos usaban `--ig-rounded-full` y
|
|
2210
|
+
eran visualmente idénticos. Ahora el default es `--ig-rounded-md`
|
|
2211
|
+
(chip estilo etiqueta, esquinas suavemente redondeadas) y `pill`
|
|
2212
|
+
mantiene `--ig-rounded-full` (cápsula).
|
|
2213
|
+
|
|
2214
|
+
## [1.0.0-beta.15] — 2026-05-03
|
|
2215
|
+
|
|
2216
|
+
### Fixed
|
|
2217
|
+
- **Elevación en LIGHT** (`--ig-bg-surface`, `--ig-bg-elevated`): los
|
|
2218
|
+
tokens mezclaban con `white` para "elevarse", pero como el body en
|
|
2219
|
+
LIGHT es `--ig-fundus-lux` = `#faf9fc` (casi blanco) el resultado era
|
|
2220
|
+
prácticamente igual al body. Cards, accordions, dropdowns, modals,
|
|
2221
|
+
toasts, sidebars y tabs se solapaban con el fondo. Ahora la elevación
|
|
2222
|
+
en LIGHT se consigue OSCURECIENDO ligeramente con tinte azulado
|
|
2223
|
+
coherente (`--ig-cinis-lux` 4% para surface, 8% para elevated). DARK
|
|
2224
|
+
intacto.
|
|
2225
|
+
|
|
2226
|
+
## [1.0.0-beta.14] — 2026-05-03
|
|
2227
|
+
|
|
2228
|
+
### Fixed
|
|
2229
|
+
- **Divider con texto + variante** pintaba el `<div>` padre entero
|
|
2230
|
+
con el color de variante. La regla `.ig-divider-brand
|
|
2231
|
+
{ background-color: var(--ig-vitreus); }` (del divider básico
|
|
2232
|
+
horizontal `<hr>`) matcheaba también al
|
|
2233
|
+
`<div class="ig-divider-with-text ig-divider-brand">` por compartir
|
|
2234
|
+
la clase modifier. Resultado: caja cyan brillante con el texto cyan
|
|
2235
|
+
encima — invisible. Fix: scope a `.ig-divider.ig-divider-brand` para
|
|
2236
|
+
que solo aplique al divider básico (que sí lleva la clase base
|
|
2237
|
+
`.ig-divider`).
|
|
2238
|
+
|
|
2239
|
+
## [1.0.0-beta.13] — 2026-05-03
|
|
2240
|
+
|
|
2241
|
+
### Changed
|
|
2242
|
+
- **Divider con texto variante** invierte la jerarquía de color:
|
|
2243
|
+
ahora el TEXTO recibe el accent (`vitreus`, `axis`, ...) y las
|
|
2244
|
+
líneas se quedan en `--ig-border-subtle`. Antes (beta.10–12) las
|
|
2245
|
+
líneas iban con accent y el texto en `cinis`; las dos líneas
|
|
2246
|
+
alineadas creaban una banda continua perceptual y el texto quedaba
|
|
2247
|
+
apagado. Patrón Material UI: las líneas son sutiles, el color
|
|
2248
|
+
semántico vive en el label.
|
|
2249
|
+
|
|
2250
|
+
## [1.0.0-beta.12] — 2026-05-03
|
|
2251
|
+
|
|
2252
|
+
### Removed
|
|
2253
|
+
- Stories aisladas `Indeterminate` de `Checkbox` y `Switch`.
|
|
2254
|
+
Confundían — el click no parecía hacer nada porque el sticky
|
|
2255
|
+
behavior re-aplicaba `indeterminate=true` sin un parent que
|
|
2256
|
+
gestione state. La nueva story `MasterSelectAll` (beta.11) ya
|
|
2257
|
+
muestra el indeterminate en su estado inicial y demuestra el
|
|
2258
|
+
patrón canónico.
|
|
2259
|
+
|
|
2260
|
+
## [1.0.0-beta.11] — 2026-05-03
|
|
2261
|
+
|
|
2262
|
+
### Added
|
|
2263
|
+
- Stories `Componentes/Checkbox/MasterSelectAll` y
|
|
2264
|
+
`Componentes/Switch/MasterSelectAll` con el patrón canónico de
|
|
2265
|
+
indeterminate (maestro + grupo de hijos). El `indeterminate` no es
|
|
2266
|
+
un tercer estado del toggle, es una etiqueta visual derivada del
|
|
2267
|
+
estado de los hijos — patrón usado por GitHub, Gmail, Material UI
|
|
2268
|
+
checkbox group, Ant Design Tree, etc.
|
|
2269
|
+
|
|
2270
|
+
### Reverted
|
|
2271
|
+
- Cambios en `Checkbox` y `Switch` glyph publicados en `beta.10`
|
|
2272
|
+
(`var(--ig-fundus)` adaptativo): diagnóstico erróneo, el reporte
|
|
2273
|
+
"indeterminate no hace nada" era de comportamiento (sticky
|
|
2274
|
+
behavior), no de contraste visual. El glyph vuelve a
|
|
2275
|
+
`var(--ig-fundus-lux)`. El cambio del Divider en `beta.10` se
|
|
2276
|
+
mantiene.
|
|
2277
|
+
|
|
2278
|
+
## [1.0.0-beta.10] — 2026-05-03
|
|
2279
|
+
|
|
2280
|
+
### Changed
|
|
2281
|
+
- **Divider con texto variante**: el texto ya no toma el color del
|
|
2282
|
+
accent (era idéntico a las líneas y se confundía visualmente). Va
|
|
2283
|
+
en `--ig-cinis`. (Posteriormente revisado en `beta.13`: ahora el
|
|
2284
|
+
texto SÍ va en accent y las líneas en subtle.)
|
|
2285
|
+
|
|
2286
|
+
### Fixed (luego revertido en beta.11 — diagnóstico erróneo)
|
|
2287
|
+
- ~~Glyph del Checkbox/Switch (`tick`, línea de indeterminate, thumb)
|
|
2288
|
+
cambiado a `var(--ig-fundus)` adaptativo. Pretendía resolver bajo
|
|
2289
|
+
contraste blanco-sobre-cyan en DARK; resultó no ser el bug
|
|
2290
|
+
reportado.~~
|
|
2291
|
+
|
|
2292
|
+
## [1.0.0-beta.9] — 2026-05-03
|
|
2293
|
+
|
|
2294
|
+
### Fixed
|
|
2295
|
+
- **Progress bar invisible**: el componente renderizaba el bar como
|
|
2296
|
+
`<span class="ig-progress-bar">`. Como `<span>` es `display: inline`
|
|
2297
|
+
por defecto y el CSS no le aplicaba `display: block`, el `width`
|
|
2298
|
+
inline se ignoraba — todas las variantes se veían iguales (solo el
|
|
2299
|
+
track gris del fondo). Cambiado a `<div>`, alineado con la doc del
|
|
2300
|
+
CSS que ya prescribía `<div>`.
|
|
2301
|
+
|
|
2302
|
+
## [1.0.0-beta.8] — 2026-05-03
|
|
2303
|
+
|
|
2304
|
+
Consolida el trabajo que estuvo bajo `rc.1` / `rc.2` / `rc.3` (tags
|
|
2305
|
+
borrados, no llegaron a publicarse) más los fixes finales sobre
|
|
2306
|
+
`Progress` y `Checkbox`/`Switch` detectados al revisar el visual.
|
|
2307
|
+
|
|
2308
|
+
### Added
|
|
2309
|
+
- **Prop `indeterminate?: boolean`** en `Checkbox` y `Switch`. Hasta
|
|
2310
|
+
`beta.7` el tercer estado solo se podía aplicar via ref-callback
|
|
2311
|
+
(`ref={el => { if (el) el.indeterminate = true }}`), patrón
|
|
2312
|
+
documentado pero poco ergonómico. Ahora basta `<Checkbox indeterminate>`
|
|
2313
|
+
/ `<Switch indeterminate>`. Internamente un `useEffect` sincroniza la
|
|
2314
|
+
prop con `el.indeterminate` nativo (la propiedad del DOM, ya que
|
|
2315
|
+
`indeterminate` no es atributo HTML válido). El ref-callback sigue
|
|
2316
|
+
funcionando — los componentes ahora usan un `internalRef` y forwardean
|
|
2317
|
+
al ref del consumer si se pasa.
|
|
2318
|
+
- **El estado `indeterminate` es sticky tras click**. El navegador limpia
|
|
2319
|
+
`el.indeterminate` automáticamente cuando el usuario clica un checkbox
|
|
2320
|
+
con `.indeterminate=true` (toggle nativo a checked/unchecked). Si la
|
|
2321
|
+
prop sigue `true`, `handleChange` re-aplica `el.indeterminate=true` —
|
|
2322
|
+
solo el parent decide cuándo salir del estado mixto pasando
|
|
2323
|
+
`indeterminate={false}`. Sin esto, tras el primer click el visual
|
|
2324
|
+
pasaba a checked normal aunque la prop dijera "parcial".
|
|
2325
|
+
- **`aria-checked="mixed"`** automático en `Checkbox`/`Switch` cuando
|
|
2326
|
+
`indeterminate=true`, para que NVDA/JAWS/VoiceOver anuncien el estado
|
|
2327
|
+
parcial correctamente.
|
|
2328
|
+
- **Visual `:indeterminate`** en `Checkbox` y `Switch`. Hasta `beta.7`
|
|
2329
|
+
el `<input>` nativo se ocultaba detrás de un overlay y las reglas CSS
|
|
2330
|
+
solo cubrían `:checked` / `:focus-visible` / `:disabled`, así que
|
|
2331
|
+
`el.indeterminate=true` no tenía diferencia visual.
|
|
2332
|
+
- **Checkbox**: fondo lleno (color de variante, igual que `:checked`)
|
|
2333
|
+
y glyph "−" (línea horizontal centrada).
|
|
2334
|
+
- **Switch**: thumb centrado en el track con background de variante
|
|
2335
|
+
(Material-like — comunica "ni on ni off" sin glyphs adicionales).
|
|
2336
|
+
- Las 6 variantes (`brand`/`secondary`/`success`/`warning`/`danger`/`info`)
|
|
2337
|
+
tienen su color correspondiente en ambos.
|
|
2338
|
+
- **Story `Switch.Indeterminate`** (no existía).
|
|
2339
|
+
- **Story `Checkbox.Indeterminate`** refactorizada: usa
|
|
2340
|
+
`args: { indeterminate: true }` en vez del ref-callback (mejor ejemplo).
|
|
2341
|
+
|
|
2342
|
+
### Changed
|
|
2343
|
+
- **Progress bars siempre usan los `-nox`** (cardinales brillantes) en
|
|
2344
|
+
ambos temas, no los adaptativos `var(--ig-{role})`. Razón: en LIGHT
|
|
2345
|
+
los `-lux` tienen luminosidad y chroma demasiado bajos para
|
|
2346
|
+
distinguirse en una franja de 8-12 px (las 6 variantes se veían
|
|
2347
|
+
"todas oscuras" y costaba diferenciar hue). El track sigue adaptativo
|
|
2348
|
+
via `var(--ig-progress-track)`. Los demás componentes mantienen
|
|
2349
|
+
tokens adaptativos porque tienen texto encima donde el contraste
|
|
2350
|
+
WCAG sí importa; en `Progress` el bar es bloque sólido sin texto.
|
|
2351
|
+
- **`Progress.Variantes`** story rediseñada — antes 5 barras
|
|
2352
|
+
`size="md"` (8 px) sin label. Ahora 6 barras `size="lg"` (12 px)
|
|
2353
|
+
con `<span class="ig-story-label">` arriba de cada una. `play()`
|
|
2354
|
+
afirma via `getComputedStyle` que las 6 barras tienen
|
|
2355
|
+
`background-color` distinto — regresión si alguien rompe la cascade
|
|
2356
|
+
`.ig-progress-{v} .ig-progress-bar`.
|
|
2357
|
+
- **8 stories de Interacción propagan `args`** — `Radio.SelectInteraction`,
|
|
2358
|
+
`Modal.OpenInteraction`, `Accordion.Interaction`,
|
|
2359
|
+
`Sidebar.ToggleInteraction`, `Stepper.Interactivo`,
|
|
2360
|
+
`Tabs.KeyboardNavInteraction`, `Toast.FireInteraction`,
|
|
2361
|
+
`Tooltip.A11yInteraction` tenían `render: () => (<Comp prop="…" />)`
|
|
2362
|
+
con valores hardcoded, lo que dejaba el panel Controls desconectado
|
|
2363
|
+
del render. Ahora `render: (args) => (<Comp {...args} … />)` con
|
|
2364
|
+
overrides finales solo donde el `play()` necesita un valor concreto
|
|
2365
|
+
(state controlado, `defaultValue` predecible, payload del toast).
|
|
2366
|
+
Cuando hace falta un default distinto al del meta se añade
|
|
2367
|
+
`args: {…}` a nivel de story. `FireToastButton` se inlinea para
|
|
2368
|
+
leer `args.title/message/variant`.
|
|
2369
|
+
- **`laurus` recalibrado de H=149° a H≈140°** para subir la separación
|
|
2370
|
+
perceptual ΔE OKLab `laurus ↔ vitreus` en LIGHT de ≈0.054 (rozaba el
|
|
2371
|
+
umbral del Check 3 = 0.05) a ≈0.074. Hex nuevos:
|
|
2372
|
+
`--ig-laurus-lux: #143d0a`, `--ig-laurus-nox: #6aed4a` (antes
|
|
2373
|
+
`#113d1c` / `#5eeb82`). Geometría dual preservada (L_lux=0.319,
|
|
2374
|
+
L_nox=0.841, ΔH=0.11°, suma 1.160). El alias `--ig-success` sigue
|
|
2375
|
+
apuntando a `--ig-laurus`, ningún consumer que use rol `success` debe
|
|
2376
|
+
tocar nada. Las alphas `--ig-laurus-alpha-{10,20,30,50,70}` siguen
|
|
2377
|
+
siendo `color-mix` y arrastran el cambio automáticamente.
|
|
2378
|
+
Visualmente: más vegetal puro, menos "menta caribeña". Re-baseline
|
|
2379
|
+
de Chromatic obligatorio.
|
|
2380
|
+
|
|
2381
|
+
### Fixed (Storybook only — no afecta al paquete publicado)
|
|
2382
|
+
- **Toggle light/dark del toolbar de Storybook funciona también en
|
|
2383
|
+
páginas MDX puras** (`Contrast`, `DesignTokens`, `Introduction`,
|
|
2384
|
+
`Spacing`, `Variants`). Antes el toggle solo afectaba a las Stories
|
|
2385
|
+
CSF; las MDX se quedaban en el último `data-theme` aplicado por la
|
|
2386
|
+
Story anterior. Causa raíz: el decorator `withThemeByDataAttribute`
|
|
2387
|
+
solo se ejecuta cuando se monta una Story; las MDX sin `<Story of=…>`
|
|
2388
|
+
ni `<Canvas>` que referencie un export son páginas estáticas que no
|
|
2389
|
+
disparan decorators. Fix: `.storybook/preview-head.html` se suscribe
|
|
2390
|
+
al canal `__STORYBOOK_ADDONS_CHANNEL__` y reacciona al evento
|
|
2391
|
+
`updateGlobals` aplicando `data-theme` al `<html>` del iframe.
|
|
2392
|
+
Idempotente con el decorator existente.
|
|
2393
|
+
|
|
2394
|
+
### Documentation
|
|
2395
|
+
- **README** bloque "Estado": ahora documenta `1.0.0-beta.8` con la
|
|
2396
|
+
paleta final + rename `cyaneus → kobalium` (de `beta.7`) +
|
|
2397
|
+
recalibración de `laurus` + nueva prop `indeterminate` +
|
|
2398
|
+
Progress bars `-nox`.
|
|
2399
|
+
- **README** ejemplo de `Pagination` corregido —
|
|
2400
|
+
`ariaLabel="Pagination"` → `aria-label="Pagination"` (era
|
|
2401
|
+
inconsistente con la migración a `aria-*` estándar de `beta.4`).
|
|
2402
|
+
- **`igoded-components.css`** cabecera: `info → kobalium (cyan)` →
|
|
2403
|
+
`info → kobalium (cobalt blue)` (residuo del sed `cyaneus → kobalium`
|
|
2404
|
+
de `beta.7` que dejó `(cyan)` colgando). Aclarado "6 roles UI (el
|
|
2405
|
+
sistema tiene 7 cardinales)" para no confundir cardinales con roles.
|
|
2406
|
+
- **Migration note ampliada** sobre el rename `cyaneus → kobalium` de
|
|
2407
|
+
`beta.7` distinguiendo:
|
|
2408
|
+
- **API estable** (no tocar nada): `var(--ig-info)`, `.ig-bg-info`,
|
|
2409
|
+
`.ig-text-info`, `.ig-alert-info`, `.ig-badge-info`, `.ig-btn-info`,
|
|
2410
|
+
etc. (30+ clases con sufijo de **rol**, no de cardinal).
|
|
2411
|
+
- **Primitivos internos** (renombrar si los usabas directo):
|
|
2412
|
+
`--ig-cyaneus` / `--ig-cyaneus-{lux,nox}` /
|
|
2413
|
+
`--ig-cyaneus-alpha-{10,20,30,50,70}` / `--ig-text-on-cyaneus` →
|
|
2414
|
+
`--ig-kobalium*` / `--ig-text-on-kobalium`.
|
|
2415
|
+
|
|
2416
|
+
### Self-correction
|
|
2417
|
+
- La entrada de `1.0.0-beta.6` decía que `.ig-story-canvas` se movía a
|
|
2418
|
+
`.storybook/preview-head.html`. La ruta correcta es
|
|
2419
|
+
`.storybook/storybook.css` (consumido como `className` desde
|
|
2420
|
+
`.storybook/preview.tsx`). El `preview-head.html` no se tocó en
|
|
2421
|
+
`beta.6` — sigue como en `beta.5` con la inyección de
|
|
2422
|
+
`data-theme="dark"`.
|
|
2423
|
+
|
|
2424
|
+
## [1.0.0-beta.7] — 2026-05-03
|
|
2425
|
+
|
|
2426
|
+
### Renamed (BREAKING — token rename + hue reassignment)
|
|
2427
|
+
- **`cyaneus` → `kobalium`** y **reasignación de hue de cyan H≈214° a
|
|
2428
|
+
cobalt blue H≈260°**. Razón: la separación perceptual ΔE OKLab entre
|
|
2429
|
+
`cyaneus` y `vitreus` (brand teal) era marginal (≈0.054 en dark), lo
|
|
2430
|
+
que producía confusión visual cuando ambos cardinales aparecían
|
|
2431
|
+
contiguos. Con kobalium en H≈260° la separación pasa a ≈0.123 (cómoda).
|
|
2432
|
+
El nombre cambia porque "cyaneus" describía un cyan que ya no existe
|
|
2433
|
+
como color real; "kobalium" (latinización de cobalto) refleja el nuevo
|
|
2434
|
+
hue. Hex nuevos: `--ig-kobalium-lux: #082e6d`, `--ig-kobalium-nox: #b1ccf7`
|
|
2435
|
+
(ambos cumplen geometría dual L_lux≈0.32, L_nox≈0.84, ΔH≤10°, AAA contra
|
|
2436
|
+
los 5 fondos del tema).
|
|
2437
|
+
|
|
2438
|
+
**Migración**:
|
|
2439
|
+
- Si tu código usa solo el alias `var(--ig-info)`, **no tocar nada** —
|
|
2440
|
+
el alias sigue funcionando y ahora apunta internamente a `--ig-kobalium`.
|
|
2441
|
+
- Si tu código referencia literalmente `--ig-cyaneus`, `--ig-cyaneus-lux`,
|
|
2442
|
+
`--ig-cyaneus-nox`, `--ig-cyaneus-alpha-{10,20,30,50,70}` o
|
|
2443
|
+
`--ig-text-on-cyaneus`, **renombrar a `--ig-kobalium*`** /
|
|
2444
|
+
`--ig-text-on-kobalium`.
|
|
2445
|
+
- Re-baseline de Chromatic obligatorio: el cambio de hex altera todas
|
|
2446
|
+
las stories que usan info/cyaneus en cualquier forma.
|
|
2447
|
+
|
|
2448
|
+
### Added
|
|
2449
|
+
- **Check 3 (warning) en `scripts/check-component-contrast.mjs`**:
|
|
2450
|
+
separación perceptual ΔE OKLab ≥ 0.05 entre pares de cardinales de UI
|
|
2451
|
+
activa (los 6 cardinales menos `cinis`, que es texto del cuerpo). Útil
|
|
2452
|
+
para detectar cuándo un cardinal nuevo entra en zona de confusión con
|
|
2453
|
+
uno existente. **No falla CI** — solo imprime aviso. Razón de no
|
|
2454
|
+
promocionar a error en esta beta: hay pares preexistentes (p.ej.
|
|
2455
|
+
`vitreus↔laurus` en light, ΔE≈0.054) que rozan el umbral; auditarlos
|
|
2456
|
+
con cabeza fría requiere su propio commit.
|
|
2457
|
+
|
|
2458
|
+
### Changed
|
|
2459
|
+
- Doc cabecera de `igoded-tokens.css`, `SKILL.md`, `Introduction.mdx`,
|
|
2460
|
+
`Variants.mdx`, `Palette.stories.tsx`: actualizada la descripción de
|
|
2461
|
+
kobalium (cobalto, H≈260°) y la migration note desde cyaneus.
|
|
2462
|
+
|
|
2463
|
+
## [1.0.0-beta.6] — 2026-05-03
|
|
2464
|
+
|
|
2465
|
+
### Changed (BREAKING — visual)
|
|
2466
|
+
- **`reset.css` ya no estiliza `<button>` con la marca**. Hasta beta.4
|
|
2467
|
+
el reset aplicaba `background: var(--ig-vitreus); color: var(--ig-text-on-vitreus)`
|
|
2468
|
+
a todo `<button>` sin clase. Desde beta.5 los `<button>` que importas
|
|
2469
|
+
con `reset.css` salen `background: transparent; color: inherit;
|
|
2470
|
+
border: 0; padding: 0; cursor: pointer;`. Si tu app dependía de que
|
|
2471
|
+
cualquier `<button>` nativo apareciese con look brand "gratis", ahora
|
|
2472
|
+
los verás transparentes sobre el fondo de su contenedor. Migración:
|
|
2473
|
+
añade la clase `.ig-button` (o variante `.ig-button-primary`,
|
|
2474
|
+
`.ig-button-secondary`…) a esos `<button>`. Razón del cambio: evitar
|
|
2475
|
+
combinaciones bg/color heredadas que rompían contraste cuando un
|
|
2476
|
+
wrapper aplicaba sus propios colores (caso real: SidebarItem con
|
|
2477
|
+
texto `cinis` sobre `<button>` con bg vitreus → ratio 1.06).
|
|
2478
|
+
|
|
2479
|
+
### Documentation
|
|
2480
|
+
- `igoded-components.css` cabecera: `info → axis` corregido a
|
|
2481
|
+
`info → cyaneus`.
|
|
2482
|
+
- `igoded-tokens.css` "CUÁNDO USAR": `info` quitado de la línea de Axis
|
|
2483
|
+
y añadido a una línea propia para Cyaneus.
|
|
2484
|
+
- `SKILL.md` y `README.md`: documentado el scope real de
|
|
2485
|
+
`npm run test:contrast` (qué cubre y qué no).
|
|
2486
|
+
|
|
2487
|
+
### Added
|
|
2488
|
+
- Bloque `@media (forced-colors: active)` quirúrgico en
|
|
2489
|
+
`igoded-components.css` para estados `*-active`/`*-selected` (Tabs,
|
|
2490
|
+
Pagination, Stepper, Chip, Sidebar, Navbar) → `Highlight`/`HighlightText`,
|
|
2491
|
+
e inputs (`.ig-input`, `.ig-native-select`, `.ig-textarea`) → `Field`/`FieldText`.
|
|
2492
|
+
Complementa el mapeo semántico de `igoded-tokens.css` sin duplicarlo.
|
|
2493
|
+
- Wrapper canvas de Storybook movido de inline-style a clase
|
|
2494
|
+
`.ig-story-canvas` en `.storybook/preview-head.html`.
|
|
2495
|
+
|
|
2496
|
+
## [1.0.0-beta.5] — 2026-05-03
|
|
2497
|
+
|
|
2498
|
+
### Changed (BREAKING)
|
|
2499
|
+
- **Rediseño total de la paleta cardinal — geometría OKLCH uniforme**.
|
|
2500
|
+
Los 14 hex de los pares `{cardinal}-{lux,nox}` (vitreus, axis, cinis,
|
|
2501
|
+
rutilus, laurus, malum, cyaneus) cambian para cumplir
|
|
2502
|
+
`L_lux ≈ 0.32 ± 0.04`, `L_nox ≈ 0.84 ± 0.04`, `ΔH OKLCH ≤ 10°` entre
|
|
2503
|
+
lux/nox del mismo cardinal y AAA frente a los 5 fondos del tema.
|
|
2504
|
+
Diferencia visible más fuerte: `vitreus-nox` pasa de teal pastel
|
|
2505
|
+
`#5eded5` a cyan eléctrico `#30e6e6`; `laurus-nox` a verde brillante
|
|
2506
|
+
`#5eeb82`; `axis-lux` a violeta `#411271`. Resto: cambios sutiles.
|
|
2507
|
+
`--ig-fundus-{lux,nox}` se mantienen.
|
|
2508
|
+
- **`--ig-bg-{surface,sunken,elevated,muted}` derivados de `fundus`**.
|
|
2509
|
+
Ahora se generan vía `color-mix(in oklch, var(--ig-fundus-{lux,nox}), …)`.
|
|
2510
|
+
Si cambias `fundus`, todo el tema acompaña automáticamente. Output
|
|
2511
|
+
visual ±1 punto del previo.
|
|
2512
|
+
- **`engines.node`**: `>=20` → `>=22`. Node 20 (Iron) llegó a EOL el
|
|
2513
|
+
2026-04-30 y ya no recibe parches de seguridad. El VPS de despliegue
|
|
2514
|
+
(Debian 13) corre Node 22.22 LTS (Jod), que pasa a ser el floor
|
|
2515
|
+
soportado oficialmente.
|
|
2516
|
+
|
|
2517
|
+
### Fixed (a11y / contraste)
|
|
2518
|
+
- 32 violaciones WCAG AA por texto `--ig-fundus-lux` hardcoded sobre
|
|
2519
|
+
fondos cardinales adaptativos en Badge (×6), Button outline:hover (×6),
|
|
2520
|
+
Tabs Pills (×6), Pagination active (×1), Stepper dot active+complete
|
|
2521
|
+
(×2) y Chip selected (×2). Ahora todos usan `--ig-text-on-{role}`,
|
|
2522
|
+
que invierte automáticamente entre claro/oscuro según el tema.
|
|
2523
|
+
- **Reset CSS de `<button>`**: ya no fuerza `background: vitreus +
|
|
2524
|
+
color: text-on-vitreus`. Pasa a `transparent + inherit` para que
|
|
2525
|
+
cualquier wrapper (SidebarItem, otros) pueda aplicar sus colores sin
|
|
2526
|
+
riesgo de mezcla incoherente (texto cinis sobre fondo vitreus =
|
|
2527
|
+
ratio 1.06 que el runner cazó tras el fix de Bloque 1).
|
|
2528
|
+
- **Runner storybook+axe local**: deja de ser ciego al modo dark.
|
|
2529
|
+
Antes, `useEffect` del theme decorator aplicaba `data-theme="dark"`
|
|
2530
|
+
POST-paint y chromium headless evaluaba en light (vía
|
|
2531
|
+
`prefers-color-scheme: light`), donde no había violaciones. Fix
|
|
2532
|
+
con `.storybook/preview-head.html` que inyecta `data-theme="dark"`
|
|
2533
|
+
antes del primer paint del iframe.
|
|
2534
|
+
- **Token huérfano**: eliminado `--ig-text-on-cinis` (cinis nunca se
|
|
2535
|
+
usa como background, solo como color de texto-body).
|
|
2536
|
+
- **`--ig-text-muted` en light**: ajustado a `#5e5667` (ratio 5.06 sobre
|
|
2537
|
+
el nuevo `bg-muted`). Antes daba 4.45 (< 4.5 AA).
|
|
2538
|
+
|
|
2539
|
+
### Added
|
|
2540
|
+
- **`scripts/check-component-contrast.mjs`** como guardrail CI: parsea
|
|
2541
|
+
`igoded-components.css` con postcss y valida (a) WCAG ≥ 4.5 en cada
|
|
2542
|
+
par bg/color resuelto en ambos temas y (b) geometría OKLCH dual
|
|
2543
|
+
(ΔH ≤ 10°, L_lux/nox y suma dentro de banda). Conectado a
|
|
2544
|
+
`npm run test:contrast` y `npm run verify`.
|
|
2545
|
+
- Story `Fundamentos/Paleta` (visualización viva de los 7 cardinales
|
|
2546
|
+
con hex, OKLCH (L,C,H) y ratios contra `fundus`).
|
|
2547
|
+
- `:root { color-scheme: dark light }` + por tema, para que los
|
|
2548
|
+
scrollbars y form-controls del UA sigan el tema activo.
|
|
2549
|
+
- Bloque `@media (forced-colors: active)` que mapea los semánticos
|
|
2550
|
+
críticos a system-color keywords (CanvasText, Canvas, LinkText,
|
|
2551
|
+
GrayText) para Windows High Contrast Mode.
|
|
2552
|
+
- Token `--ig-theme-transition` con override en
|
|
2553
|
+
`@media (prefers-reduced-motion: reduce)`.
|
|
2554
|
+
- Cabecera explícita en `igoded-tokens.css` documentando los 3 tiers
|
|
2555
|
+
de tokens (primitivos / semánticos / escalas universales) y los
|
|
2556
|
+
guardrails de CI.
|
|
2557
|
+
- `SKILL.md` con las reglas inviolables del DS (geometría dual, uso de
|
|
2558
|
+
`text-on-*`, prohibición de `fundus-*` directo, separación
|
|
2559
|
+
info/secondary, escala neutral universal).
|
|
2560
|
+
|
|
2561
|
+
## [1.0.0-beta.4] — 2026-05-02
|
|
2562
|
+
|
|
2563
|
+
Última pasada de pulido pre-`1.0.0`. Cierra los hallazgos de la auditoría
|
|
2564
|
+
externa sobre beta.3 + endurecimiento adicional (size budgets, dev-warnings,
|
|
2565
|
+
config split lib vs dev/storybook, registry de Tabs robusto frente a
|
|
2566
|
+
defaultValue inválido).
|
|
2567
|
+
|
|
2568
|
+
### Fixed (a11y / regression / robustness)
|
|
2569
|
+
- **Rating**: `value` o `defaultValue` fuera de `[0, max]` se clampa
|
|
2570
|
+
silenciosamente. `value=10 max=5` antes dejaba todos los radios con
|
|
2571
|
+
`tabIndex=-1` (tablist sin tab stop accesible). Ahora va al tope. `max`
|
|
2572
|
+
no entero se redondea hacia abajo, mínimo 1.
|
|
2573
|
+
- **Tabs `defaultValue` inválido**: si el value pasado no matchea ningún
|
|
2574
|
+
`<Tab>` montado, fallback al primero registrado tras un useEffect
|
|
2575
|
+
post-mount (no durante el registro tab-a-tab — eso producía un falso
|
|
2576
|
+
positivo cuando los Tabs se montaban en orden alfabético). En modo
|
|
2577
|
+
controlled NO se auto-corrige; solo console.warn dev-only.
|
|
2578
|
+
- **Tab.tsx pre-register con `useLayoutEffect`** (en cliente; SSR cae a
|
|
2579
|
+
noop): el primer paint visible ya tiene tab activo, sin flicker.
|
|
2580
|
+
- **Slider**: `defaultValue` array (legalmente permitido por
|
|
2581
|
+
`InputHTMLAttributes` aunque `<input type="range">` no lo soporta) ya no
|
|
2582
|
+
se reenvía al DOM como `[object Array]`. Filtramos a number/string
|
|
2583
|
+
válidos.
|
|
2584
|
+
- **Modal**: el effect de sincronización `open` ahora resetea
|
|
2585
|
+
`closingFromSyncRef` en su cleanup. No hay leak real (state muere con
|
|
2586
|
+
unmount), pero queda explícitamente correcto frente a re-mounts en
|
|
2587
|
+
desmontaje rápido (navegación SPA durante animación de cierre).
|
|
2588
|
+
- **Reset.css `<button>` color**: usaba `--ig-fundus-lux` fijo que daba
|
|
2589
|
+
contraste bajo en dark (texto casi blanco sobre teal claro). Ahora usa
|
|
2590
|
+
`--ig-text-on-vitreus` adaptativo. Se aplicó en beta.3 pero faltaba
|
|
2591
|
+
validación AA en dark — verificada ahora con axe.
|
|
2592
|
+
|
|
2593
|
+
### Added
|
|
2594
|
+
- **`vite.lib.config.ts` separado** del `vite.config.ts` general. El
|
|
2595
|
+
build de librería (`npm run build`) usa explícitamente esta config con
|
|
2596
|
+
`dts` plugin + `copyDesignSystemStyles`. Más robusto que el guard env
|
|
2597
|
+
`STORYBOOK !== "true"` de beta.3 (Storybook ya nunca puede contaminar
|
|
2598
|
+
el build de librería, independientemente de qué env vars setee).
|
|
2599
|
+
- **`size-limit` en CI** con budgets por archivo:
|
|
2600
|
+
- `dist/index.{js,cjs}` → ≤ 15 KB gzipped (real: ~10.7 KB ESM)
|
|
2601
|
+
- `dist/styles/igoded-design.css` → ≤ 5 KB (real: 1 KB, es solo @import)
|
|
2602
|
+
- `dist/styles/igoded-tokens.css` → ≤ 25 KB (real: 22 KB)
|
|
2603
|
+
- `dist/styles/igoded-components.css` → ≤ 75 KB (real: 67 KB)
|
|
2604
|
+
- `dist/styles/igoded-base.css` → ≤ 2 KB (real: 1.4 KB)
|
|
2605
|
+
- `dist/styles/igoded-reset.css` → ≤ 2 KB (real: 1.6 KB)
|
|
2606
|
+
- `dist/styles/igoded-fonts.css` → ≤ 1 KB (real: 0.9 KB)
|
|
2607
|
+
Script `npm run verify:size`. Forma parte de `npm run verify`.
|
|
2608
|
+
- **Card**: dev-only `console.warn` si `interactive` + `onClick` está
|
|
2609
|
+
presente pero sin `role="button"`. Avisa una vez por instancia. La
|
|
2610
|
+
card sin role no activa por teclado, este caso suele ser un descuido.
|
|
2611
|
+
- **`Fundamentos/Variantes` (nueva MDX)**: visualización del mapeo
|
|
2612
|
+
`variant` → cardinal con los 7 colores nuevos (incluye `cyaneus`).
|
|
2613
|
+
Documentación de override por consumer + ratios WCAG AA.
|
|
2614
|
+
- **JSDoc `@example` en hooks públicos**: `useTheme`, `useToast`,
|
|
2615
|
+
`useTabs`, `useAccordion`, `useAccordionItem`, `useOptionsMenu`,
|
|
2616
|
+
`useSidebar`. Mejora autocompletado en LSP del consumer.
|
|
2617
|
+
- **Test SSR-hydration ThemeSwitch**: `localStorage.theme="light"` gana
|
|
2618
|
+
al default `dark` y NO se sobreescribe en el primer effect.
|
|
2619
|
+
- **`src/env.d.ts`**: declaración tipada de `import.meta.env.DEV/PROD`
|
|
2620
|
+
para que los warnings dev-only de Card/Tabs typechequen sin pisar el
|
|
2621
|
+
`vite-env.d.ts` (que está excluido del build para no exportar
|
|
2622
|
+
declaraciones de módulos `*.css`/`*.svg` al consumer).
|
|
2623
|
+
|
|
2624
|
+
### Changed (BREAKING — momento ideal pre-1.0)
|
|
2625
|
+
- **`ariaLabel` prop → `aria-label` HTML estándar** en 9 componentes:
|
|
2626
|
+
`Pagination`, `Spinner`, `Stepper`, `Rating`, `Sidebar`, `SidebarNav`,
|
|
2627
|
+
`NavbarNav`, `Tabs.TabList`, `Breadcrumb`, `Progress`, `Avatar`,
|
|
2628
|
+
`Timeline`. Cada uno extrae `aria-label` del rest con fallback ES por
|
|
2629
|
+
defecto. Coherente con el resto del DS (ThemeSwitch ya usaba esto
|
|
2630
|
+
desde beta.3) y con la convención web.
|
|
2631
|
+
- **Migration**: rename en tu JSX `ariaLabel={...}` → `aria-label={...}`.
|
|
2632
|
+
- **`docs.defaultName: "Documentación"` → `"Docs"`** (ya estaba en beta.3,
|
|
2633
|
+
reconfirmado: URLs ASCII, sin %C3%B3n).
|
|
2634
|
+
- **README CSS imports actualizado**: 7 entradas → 8 entradas (incluye
|
|
2635
|
+
`fonts.css`); la versión del estado pasa de beta.2 a beta.4.
|
|
2636
|
+
- **README script anti-flash de tema**: fallback `|| "light"` →
|
|
2637
|
+
`prefers-color-scheme: dark` con fallback `dark` (alineado con el
|
|
2638
|
+
branding dark-first del DS). Try/catch para entornos sin localStorage.
|
|
2639
|
+
- **`tokens.css` comentarios**: "FUNDUS + 6 CARDINALES" → "FUNDUS + 7
|
|
2640
|
+
CARDINALES" + tabla de los 7 incluyendo `CYANEUS`. Mención obsoleta a
|
|
2641
|
+
`@font-face` en tokens.css eliminada (vive en fonts.css desde beta.3).
|
|
2642
|
+
- **`package.json#keywords`**: quitado `"headless"` (engañoso, el DS es
|
|
2643
|
+
CSS-first opinionated). Añadidos `"design-tokens"`, `"css-first"`,
|
|
2644
|
+
`"themeable"`, `"dark-mode"`.
|
|
2645
|
+
- **`vite.config.ts` simplificado**: ya no contiene `build.lib` ni `dts`
|
|
2646
|
+
(eso vive en `vite.lib.config.ts`). Se queda con plugins comunes
|
|
2647
|
+
(react, optimizeDeps) compartidos por playground dev y Storybook.
|
|
2648
|
+
|
|
2649
|
+
### Migration desde beta.3
|
|
2650
|
+
1. **Si tu JSX usa la prop `ariaLabel`** en alguno de estos componentes,
|
|
2651
|
+
renombra a `aria-label`:
|
|
2652
|
+
```diff
|
|
2653
|
+
- <Stepper ariaLabel="Checkout">
|
|
2654
|
+
+ <Stepper aria-label="Checkout">
|
|
2655
|
+
- <Pagination ariaLabel="Paginación principal">
|
|
2656
|
+
+ <Pagination aria-label="Paginación principal">
|
|
2657
|
+
- <TabList ariaLabel="Cuenta">
|
|
2658
|
+
+ <TabList aria-label="Cuenta">
|
|
2659
|
+
```
|
|
2660
|
+
La búsqueda+reemplazo es trivial: tu IDE te marcará los errores TS
|
|
2661
|
+
(la prop ya no existe en los tipos).
|
|
2662
|
+
2. **Si tu app inyecta el script anti-flash de tema** del README,
|
|
2663
|
+
actualízalo al nuevo (system-aware + dark fallback) — el viejo
|
|
2664
|
+
pisaba la preferencia del usuario en sistemas dark-first.
|
|
2665
|
+
|
|
2666
|
+
### Coverage
|
|
2667
|
+
- Statements 92.29% · Branches 86.44% · Functions 95.17% · Lines 95.11%
|
|
2668
|
+
- 339 unit tests + 179 storybook (axe-a11y) = 518 tests verdes.
|
|
2669
|
+
|
|
2670
|
+
## [1.0.0-beta.3] — 2026-05-02
|
|
2671
|
+
|
|
2672
|
+
Pasada agresiva pre-`1.0.0`: a11y real, SSR-safe, naming/types fix y un
|
|
2673
|
+
nuevo color cardinal para diferenciar `info` de `secondary`.
|
|
2674
|
+
|
|
2675
|
+
### Fixed (a11y / regression)
|
|
2676
|
+
- **Input/NativeSelect/Textarea**: `aria-describedby` que el consumer pasara vía
|
|
2677
|
+
`{...rest}` se sobreescribía a `undefined` cuando no se pasaba la prop
|
|
2678
|
+
`describedBy`. La propia story `FormularioCompleto` estaba rota.
|
|
2679
|
+
Solucionado con un nuevo helper `mergeDescribedBy(native, prop)` que
|
|
2680
|
+
concatena ambos. +6 tests de regresión.
|
|
2681
|
+
- **Rating**: añadido **roving tabindex + keyboard nav completo** (←/→/↑/↓,
|
|
2682
|
+
Home, End, Space, Enter) para cumplir el patrón WAI-ARIA APG de
|
|
2683
|
+
radiogroup. Antes solo respondía a click. +9 tests de regresión.
|
|
2684
|
+
- **ToastProvider SSR**: ya no produce hydration mismatch. Antes el
|
|
2685
|
+
servidor renderizaba inline y el primer paint cliente ya pintaba portal.
|
|
2686
|
+
Ahora arranca inline (idéntico al server) y conmuta al portal en el
|
|
2687
|
+
primer `useEffect` post-mount.
|
|
2688
|
+
- **OptionsMenu a11y**: el selector de navegación excluye también
|
|
2689
|
+
`[aria-disabled="true"]` (anchors no tienen `disabled` HTML); items
|
|
2690
|
+
aria-disabled bloquean activación por click y por Enter/Space; **button**
|
|
2691
|
+
menuitem ahora también tiene `tabIndex={-1}` (antes anchor sí lo tenía
|
|
2692
|
+
y button no, inconsistente).
|
|
2693
|
+
- **Tabs sin `defaultValue`**: el tablist quedaba sin tab stop si el
|
|
2694
|
+
consumer no pasaba `value`/`defaultValue` (todos los tabs `tabIndex=-1`).
|
|
2695
|
+
Ahora un registry interno selecciona el primer `Tab` montado de forma
|
|
2696
|
+
automática.
|
|
2697
|
+
- **Modal `onClose` doble disparo**: cuando el consumer hacía
|
|
2698
|
+
`setOpen(false)`, el effect llamaba `dialog.close()` que disparaba el
|
|
2699
|
+
evento `close` nativo y volvía a invocar `onClose`. Añadido flag
|
|
2700
|
+
`closingFromSyncRef` que distingue cierre user-driven de cierre por
|
|
2701
|
+
sincronización con la prop.
|
|
2702
|
+
- **ThemeSwitch `aria-label`**: ahora se puede sobrescribir vía rest
|
|
2703
|
+
(i18n). Antes el hardcoded ganaba al rest por orden de spread.
|
|
2704
|
+
- **Slider `defaultValue` string/array**: `defaultValue="60"` (string)
|
|
2705
|
+
dejaba el state interno en `0` mientras el `<input>` mostraba 60.
|
|
2706
|
+
Ahora normaliza a número finito; arrays se ignoran (no soportados por
|
|
2707
|
+
`<input type="range">`).
|
|
2708
|
+
|
|
2709
|
+
### Added
|
|
2710
|
+
- **`igoded-fonts.css` (nuevo, opt-in)** — `@import` de Google Fonts
|
|
2711
|
+
(Electrolize/Saira/JetBrains Mono). **Antes vivía dentro de
|
|
2712
|
+
`tokens.css`**, lo que metía un request remoto en el bundle de
|
|
2713
|
+
cualquier consumer. Ahora `tokens.css` declara solo los `--ig-font-*`
|
|
2714
|
+
con fallback `system-ui`/`monospace`. Storybook lo importa
|
|
2715
|
+
explícitamente en `preview.tsx`. Expuesto como
|
|
2716
|
+
`reactigoded/styles/fonts.css`.
|
|
2717
|
+
- **Color cardinal `cyaneus`** (cian-azul) — nuevo `--ig-cyaneus-{lux,nox}`
|
|
2718
|
+
+ `--ig-text-on-cyaneus-{lux,nox}`. `--ig-info` ahora apunta a `cyaneus`
|
|
2719
|
+
en vez de a `axis` (que es violet/secondary). Antes `secondary` e `info`
|
|
2720
|
+
eran visualmente idénticos; ahora son colores distintos. Verificado
|
|
2721
|
+
WCAG AA en light y dark.
|
|
2722
|
+
- **`mergeDescribedBy` helper** en `src/utils/`. Tests propios + integrados
|
|
2723
|
+
en Input/NativeSelect/Textarea.
|
|
2724
|
+
- **Tabs `register` API** en `TabsContext` para auto-selección del primer
|
|
2725
|
+
Tab.
|
|
2726
|
+
- **Modal `closingFromSyncRef`** flag interno.
|
|
2727
|
+
- **`test:unit:ci` script** (vitest con `--isolate --pool=forks`) para CI
|
|
2728
|
+
estricto. El `test:unit` por defecto sigue con `isolate=false` por el
|
|
2729
|
+
workaround WSL.
|
|
2730
|
+
- **4 stories interactivas con `play`**: `Input/TypeInteraction`,
|
|
2731
|
+
`NativeSelect/ChangeInteraction`, `Slider/KeyboardInteraction`,
|
|
2732
|
+
`Stepper/Interactivo` (ahora con play que verifica `aria-current`).
|
|
2733
|
+
|
|
2734
|
+
### Changed
|
|
2735
|
+
- **`vite.config.ts`** ahora sólo activa el modo lib build cuando
|
|
2736
|
+
`command === "build" && mode === "production" && STORYBOOK !== "true"`.
|
|
2737
|
+
Antes `mode === "production"` solo, lo que disparaba `dts` plugin y
|
|
2738
|
+
`copyDesignSystemStyles` también durante `storybook build` (cada deploy
|
|
2739
|
+
de igoded.es).
|
|
2740
|
+
- **`docs.defaultName: "Documentación"` → `"Docs"`**: el unicode `ó`
|
|
2741
|
+
generaba URLs `componentes-x--documentaci%C3%B3n` feas y problemáticas
|
|
2742
|
+
para search/SEO. Las páginas MDX de Foundations siguen en español.
|
|
2743
|
+
- **Tema dark-first uniforme**: `useTheme` (ya estaba dark), `ThemeSwitch`
|
|
2744
|
+
(era light) y Storybook `withThemeByDataAttribute` (era light) ahora
|
|
2745
|
+
todos default `"dark"`. Coherente con el branding.
|
|
2746
|
+
- **Card type assertion**: `onClick?.(event as unknown as MouseEvent)`
|
|
2747
|
+
desde el handler de Enter/Space sustituido por `event.currentTarget.click()`,
|
|
2748
|
+
que dispara un MouseEvent auténtico.
|
|
2749
|
+
- **Alert** `style={{flex:1, minWidth:0}}` inline → clase
|
|
2750
|
+
`.ig-alert-content` publicada en `components.css`.
|
|
2751
|
+
- **Card story `Interactiva`** quita `ig-story-clickable` (la clase
|
|
2752
|
+
`.ig-card-interactive` ya implica `cursor: pointer`, y la helper de
|
|
2753
|
+
storybook.css confundía a consumers que copiaran el código).
|
|
2754
|
+
- **`eslint.config.js`** comentario `postinstall` → `prepare` (coincide
|
|
2755
|
+
con el script real de `package.json`).
|
|
2756
|
+
- **README** métricas obsoletas (~285/~175) → texto sin números fijos.
|
|
2757
|
+
|
|
2758
|
+
### Migration
|
|
2759
|
+
- Si confiabas en el `@import` de Google Fonts dentro de `design.css` /
|
|
2760
|
+
`tokens.css`, ahora también necesitas:
|
|
2761
|
+
```ts
|
|
2762
|
+
import "reactigoded/styles/fonts.css";
|
|
2763
|
+
```
|
|
2764
|
+
…o self-host con `next/font`/`@fontsource/*` y override de los
|
|
2765
|
+
`--ig-font-*`.
|
|
2766
|
+
- **Cambio visual: `info` ahora es CYAN, no violet.** Si tu app usa
|
|
2767
|
+
`<Toast variant="info">`, `<Alert variant="info">`, `<Badge variant="info">`,
|
|
2768
|
+
`<Card variant="info">`, `.ig-btn-info`, `.ig-bg-info`, etc., verás un
|
|
2769
|
+
color cian-azul (`--ig-cyaneus`) en vez del violet (`--ig-axis`) de antes.
|
|
2770
|
+
La razón: en beta.0–beta.2, `secondary` e `info` apuntaban ambos a `axis`
|
|
2771
|
+
y eran visualmente idénticos. Ahora `info` tiene su propio cardinal
|
|
2772
|
+
`cyaneus` (WCAG AA verificado en light + dark). Si necesitas el aspecto
|
|
2773
|
+
anterior, sobreescribe `--ig-info: var(--ig-axis)` en tu `:root`.
|
|
2774
|
+
- Si usabas `<Toast variant="default">` en el primitivo (no lo deberías
|
|
2775
|
+
desde beta.1, ya estaba renombrado a `neutral`), nada cambia aquí.
|
|
2776
|
+
- Si `defaultValue` de Slider venía como string, antes el value visible no
|
|
2777
|
+
cuadraba con el internal — ahora cuadra. Si dependías del bug, comprueba.
|
|
2778
|
+
|
|
2779
|
+
## [1.0.0-beta.2] — 2026-05-02
|
|
2780
|
+
|
|
2781
|
+
Refactor de la arquitectura CSS y pulido completo del catálogo Storybook.
|
|
2782
|
+
|
|
2783
|
+
### Added
|
|
2784
|
+
- **`igoded-base.css` (nuevo, ~3 KB)** — globales mínimos del DS:
|
|
2785
|
+
`box-sizing` universal, `html` (scroll-behavior, scrollbar-gutter,
|
|
2786
|
+
accent-color, caret-color), scrollbar tematizada (`::-webkit-scrollbar*`),
|
|
2787
|
+
`::selection`, `@media (prefers-reduced-motion: reduce)`,
|
|
2788
|
+
`(prefers-contrast: more)`, `(forced-colors: active)`. Standalone, depende
|
|
2789
|
+
solo de tokens. Expuesto como `reactigoded/styles/base.css`.
|
|
2790
|
+
- **`igoded-components.css` (nuevo, ~270 KB)** — utilities + componentes
|
|
2791
|
+
(clases `.ig-*`). Sin selectores globales. Depende de tokens + base.
|
|
2792
|
+
Expuesto como `reactigoded/styles/components.css`.
|
|
2793
|
+
- `.storybook/manager.ts` no era nuevo; sí lo es **`managerHead`** en
|
|
2794
|
+
`main.ts`: meta description, og:title/description/url/type, theme-color,
|
|
2795
|
+
canonical, twitter:card, y un MutationObserver que reescribe
|
|
2796
|
+
`<title>` `"… ⋅ Storybook"` → `"… · Igoded Design System"` en cada
|
|
2797
|
+
navegación. Sin polling.
|
|
2798
|
+
- `docs.defaultName: "Documentación"` (era `"Docs"`).
|
|
2799
|
+
- Helpers en `.storybook/storybook.css`: `.ig-story-card-{sm,md,lg}`,
|
|
2800
|
+
`.ig-story-clickable`, `.ig-story-min-h-{sm,md,lg}`,
|
|
2801
|
+
`.ig-foundation-{grid,swatch,token-row,contrast,typo}` para los MDX.
|
|
2802
|
+
- README: tabla detallada de globales que aplica `base.css`, escenarios
|
|
2803
|
+
de import por caso de uso (5 filas).
|
|
2804
|
+
|
|
2805
|
+
### Changed
|
|
2806
|
+
- **`igoded-design.css` ahora es solo un meta-importer** que hace
|
|
2807
|
+
`@import` de `tokens.css` + `base.css` + `components.css`. Backward
|
|
2808
|
+
compat: un consumer que importa `design.css` recibe lo mismo que antes.
|
|
2809
|
+
- **`igoded-tokens.css` es ahora 100% variables CSS**: el
|
|
2810
|
+
`*, *::before, *::after { box-sizing: border-box }` se movió a
|
|
2811
|
+
`base.css`. Tokens.css ya no tiene ningún selector global.
|
|
2812
|
+
- **Stories renombradas (ES uniforme)**:
|
|
2813
|
+
- `Switch.Toggle` → `InteracciónToggle` (no era prop literal).
|
|
2814
|
+
- `Accordion.Controlled` / `Sidebar.Controlled` /
|
|
2815
|
+
`ThemeSwitch.Controlled` → `Controlado` (consistencia con
|
|
2816
|
+
`Switch.Controlado` y `Rating.Controlado`).
|
|
2817
|
+
- **Card stories**: 4 inline `style={{ maxWidth, cursor }}` → clases
|
|
2818
|
+
helper `.ig-story-card-md`, `.ig-story-card-lg`, `.ig-story-clickable`.
|
|
2819
|
+
- **MDX foundations** (`Spacing.mdx`, `Contrast.mdx`, `DesignTokens.mdx`)
|
|
2820
|
+
migrados de inline styles a clases `.ig-foundation-*`. Markup más
|
|
2821
|
+
limpio y consistente entre páginas.
|
|
2822
|
+
- **`igoded-design.css` cabecera reescrita**: las ~190 líneas de índice
|
|
2823
|
+
legacy con números de línea desactualizados ("RESET BASE OPCIONAL...215"
|
|
2824
|
+
apuntaban a líneas que ya no existen) sustituidas por bloque conciso
|
|
2825
|
+
que documenta la arquitectura modular real.
|
|
2826
|
+
|
|
2827
|
+
### Migración (sin breaking)
|
|
2828
|
+
- `import "reactigoded/styles/design.css"` sigue funcionando igual que en
|
|
2829
|
+
beta.1. Internamente ahora `@import`a 3 archivos en vez de 2.
|
|
2830
|
+
- Si importabas `tokens.css` esperando el `box-sizing` global, ahora
|
|
2831
|
+
importa `base.css` también (`tokens.css` + `base.css` reproduce el
|
|
2832
|
+
comportamiento anterior).
|
|
2833
|
+
|
|
2834
|
+
## [1.0.0-beta.1] — 2026-05-02
|
|
2835
|
+
|
|
2836
|
+
Pasada de pulido orientada a percepción del catálogo Storybook + saneamiento
|
|
2837
|
+
de la API pública antes del `1.0.0`.
|
|
2838
|
+
|
|
2839
|
+
### Added
|
|
2840
|
+
- `igoded-tokens.css` (nuevo, ~98 KB) — solo variables `--ig-*`, keyframes y
|
|
2841
|
+
`@font-face`, sin clases de componentes. Útil para consumers que construyen
|
|
2842
|
+
su propia capa de componentes sobre los tokens del DS.
|
|
2843
|
+
Expuesto como `reactigoded/styles/tokens.css`.
|
|
2844
|
+
- `igoded-reset.css` (nuevo, ~5 KB) — estilos por defecto para HTML nativo
|
|
2845
|
+
(h1-h6, p, a, button, input, table…). **Opt-in**, no se importa
|
|
2846
|
+
automáticamente con `design.css`. Expuesto como
|
|
2847
|
+
`reactigoded/styles/reset.css`.
|
|
2848
|
+
- `Button.appearance="link"` — quinta apariencia (aplica `ig-btn-link`,
|
|
2849
|
+
ignora variant).
|
|
2850
|
+
- `Card.appearance="outline"|"filled"` — sustituye al flag `filled` (ver
|
|
2851
|
+
Changed).
|
|
2852
|
+
- Card activación Enter/Space automática cuando `interactive` +
|
|
2853
|
+
`role="button"` + `onClick`. Encadenable con `onKeyDown` del consumer
|
|
2854
|
+
(preventDefault cancela). +6 tests.
|
|
2855
|
+
- `.storybook/manager.ts` con branding Igoded (createTheme dark,
|
|
2856
|
+
Vitreus/Axis colors, brandTitle "Igoded Design System", fonts Saira +
|
|
2857
|
+
JetBrains Mono).
|
|
2858
|
+
- `.storybook/storybook.css` con clases helper `ig-story-{stack,row,grid,
|
|
2859
|
+
frame,form,shell,label}` para layouts de stories. NO se publica al paquete.
|
|
2860
|
+
- Foundations docs MDX: `Spacing.mdx` (visualización de los 25 tokens
|
|
2861
|
+
`--ig-space-*`, equivalencias Tailwind, guidance) y `Contrast.mdx`
|
|
2862
|
+
(9 pares texto+fondo verificados WCAG AA + garantías a11y de CI).
|
|
2863
|
+
|
|
2864
|
+
### Changed
|
|
2865
|
+
- **CSS reset extraído** de `igoded-design.css` a `igoded-reset.css` opt-in.
|
|
2866
|
+
En `design.css` queda solo `*, *::before, *::after { box-sizing: border-box }`
|
|
2867
|
+
(necesario para el sizing de los componentes). Eliminado el comentario
|
|
2868
|
+
legacy "ESTÁ COMENTADO A PROPÓSITO" que mentía sobre el estado del reset.
|
|
2869
|
+
- **CSS modular split**: `igoded-design.css` ahora `@import`a
|
|
2870
|
+
`igoded-tokens.css` internamente. Self-containment preservado (consumer
|
|
2871
|
+
que importa solo `design.css` recibe lo mismo que antes).
|
|
2872
|
+
- **Storybook preview**: eliminado wrapper global con padding/100vh que
|
|
2873
|
+
rompía `layout: "fullscreen"` en Navbar/Sidebar. Eliminado import de
|
|
2874
|
+
`state.css` (7.1 MB innecesarios en HMR). Backgrounds reactivados con
|
|
2875
|
+
4 valores tokenizados (base/surface/muted/sunken).
|
|
2876
|
+
- **Stories en español uniforme**: 28 archivos renombrados
|
|
2877
|
+
`Default→PorDefecto`, `Variants→Variantes`, `Sizes→Tamaños`. Otros nombres
|
|
2878
|
+
ya en ES (`ConImagen`, `Compuesta`, `Interactiva`, etc.) o nombres
|
|
2879
|
+
técnicos propios (`Pills`, `Vertical`, `KeepMounted`, `BackdropBlur`)
|
|
2880
|
+
intactos.
|
|
2881
|
+
- **External assets fuera del catálogo**: pravatar/placehold.co reemplazados
|
|
2882
|
+
por SVG data URIs inline; 11 emojis de Sidebar reemplazados por SVG inline
|
|
2883
|
+
feather-style (consistencia visual entre SO/navegadores).
|
|
2884
|
+
- **Storybook propFilter** excluye HTML attributes heredados
|
|
2885
|
+
(HTMLAttributes, AriaAttributes, DOMAttributes,
|
|
2886
|
+
{Button,Input,Textarea,NativeSelect,Anchor}HTMLAttributes) — Controls panel
|
|
2887
|
+
muestra solo props del propio package.
|
|
2888
|
+
- **Glass Navbar story**: gradiente Tailwind genérico
|
|
2889
|
+
(#4f46e5/#ec4899/#f59e0b) → gradientes con tokens
|
|
2890
|
+
`--ig-vitreus-alpha-*` + `--ig-axis-alpha-*` sobre `--ig-bg-base`.
|
|
2891
|
+
- **~40 inline styles repetidos** en stories migrados a clases
|
|
2892
|
+
`.ig-story-*`. Los inline styles component-internal (sizing de Card,
|
|
2893
|
+
padding de inputs internos, márgenes decorativos) se conservan.
|
|
2894
|
+
- Comentario WCAG en `igoded-design.css:437` corregido AAA → AA (coincide
|
|
2895
|
+
con keyword `wcag-aa` y los memos reales de tokens).
|
|
2896
|
+
|
|
2897
|
+
### Breaking
|
|
2898
|
+
- `Button.variant="outline"|"ghost"|"link"` → migrar a
|
|
2899
|
+
`Button.appearance="outline"|"ghost"|"link"`. `ButtonVariant` se reduce a
|
|
2900
|
+
los 6 colores semánticos. Razón: dos ejes ortogonales
|
|
2901
|
+
(color × estilo visual) en vez de mezclar 9 valores en uno.
|
|
2902
|
+
- `Card.filled` (boolean flag) → migrar a `Card.appearance="filled"`. El
|
|
2903
|
+
comportamiento default (`appearance="outline"`) es idéntico.
|
|
2904
|
+
- `Toast.variant="default"` → renombrado a `"neutral"` por consistencia con
|
|
2905
|
+
Alert. Comportamiento idéntico (no añade clase de variant).
|
|
2906
|
+
- Estilos para HTML nativo (`<h1>`, `<p>`, `<a>`, `<table>`…) ya **no se
|
|
2907
|
+
cargan** desde `reactigoded/styles/design.css`. Si los necesitas, importa
|
|
2908
|
+
también `reactigoded/styles/reset.css`.
|
|
2909
|
+
|
|
2910
|
+
## [1.0.0-beta.0] — 2026-05-01
|
|
2911
|
+
|
|
2912
|
+
Cambios desde la migración inicial JSX→TSX. Agrupa el trabajo de las 6
|
|
2913
|
+
auditorías profundas previas a `1.0.0`.
|
|
2914
|
+
|
|
2915
|
+
### Added
|
|
2916
|
+
- 32 componentes React 19 + TypeScript estricto, con tests unit
|
|
2917
|
+
(happy-dom) + Storybook tests (Chromium real + axe-a11y) + 16 stories
|
|
2918
|
+
con `play` (interaction tests).
|
|
2919
|
+
- Compound components: `Accordion`, `Card`, `OptionsMenu`, `Input`, `Modal`,
|
|
2920
|
+
`Navbar`, `Sidebar`, `Stepper`, `Table`, `Tabs`, `Timeline`, `Toast`.
|
|
2921
|
+
- Hooks públicos: `useTheme`, `useToast`, `useAccordion`,
|
|
2922
|
+
`useAccordionItem`, `useOptionsMenu`, `useSidebar`, `useTabs`.
|
|
2923
|
+
- API controlled+uncontrolled en `Accordion`, `Alert`, `OptionsMenu`,
|
|
2924
|
+
`Sidebar`, `Slider`, `Switch`, `Tabs`, `ThemeSwitch`, `Rating`.
|
|
2925
|
+
- `Modal.loading` (aplica `ig-dialog-loading` + `aria-busy`).
|
|
2926
|
+
- `Badge.dot` (modo punto sin texto, con `role="img"` automático).
|
|
2927
|
+
- `Button.appearance="solid"|"outline"|"ghost"` combinable con variant
|
|
2928
|
+
color (12 clases CSS antes huérfanas ahora expuestas).
|
|
2929
|
+
- `Input/NativeSelect/Textarea.describedBy` (string|string[]) — auto-`aria-describedby`.
|
|
2930
|
+
- `Slider.onValueChange(v:number)` — alternativa al `onChange` nativo.
|
|
2931
|
+
- `Slider aria-valuetext` automático cuando hay `formatValue`.
|
|
2932
|
+
- `Modal.aria-labelledby` automático vía `ModalContext`+`ModalHeader`.
|
|
2933
|
+
- `Switch role="switch"` + `aria-checked` (refactor desde checkbox plano).
|
|
2934
|
+
- `Tooltip.aria-describedby` concatena con el del child existente.
|
|
2935
|
+
- `BreadcrumbItem` discriminated union por `current`.
|
|
2936
|
+
- `Step.aria-current` en el span del círculo (no en el div wrapper).
|
|
2937
|
+
- `Textarea.auto` con `field-sizing: content` (Chrome/Edge 123+,
|
|
2938
|
+
Safari 17.4+; Firefox cae al `rows` por defecto).
|
|
2939
|
+
- `browserslist` declarado en package.json.
|
|
2940
|
+
- Stories `play` (interaction) en 16 componentes interactivos.
|
|
2941
|
+
- Tests "fuera de provider" para los 5 hooks de context.
|
|
2942
|
+
- Tests de cleanup en unmount: OptionsMenu limpia listeners globales,
|
|
2943
|
+
ToastProvider limpia timers.
|
|
2944
|
+
- Tests de transición controlled↔uncontrolled (warning de React).
|
|
2945
|
+
- `scripts/strip-orphan-css.mjs` — limpia con postcss utilities
|
|
2946
|
+
pseudo-class del `state-css` que apuntan a tokens eliminados.
|
|
2947
|
+
|
|
2948
|
+
### Changed
|
|
2949
|
+
- `Toast.closable` → `Toast.dismissible` (alineado con `Alert`).
|
|
2950
|
+
- `Rating.onChange` → `Rating.onValueChange` (consistencia con `Tabs`,
|
|
2951
|
+
`Accordion`, `Slider`).
|
|
2952
|
+
- `Spinner.label` / `Progress.label` / `Rating.label` → `ariaLabel`
|
|
2953
|
+
(estandarización a camelCase).
|
|
2954
|
+
- `Stepper.active` ahora opcional + `defaultActive`.
|
|
2955
|
+
- `Alert.open` ahora opcional + `defaultOpen` + `onOpenChange`.
|
|
2956
|
+
- `dist/styles/index.css` ahora usa `@import` (no concatenación física —
|
|
2957
|
+
ahorra 7.85 MB en el unpacked).
|
|
2958
|
+
- Publish sin sourcemaps de JS (`.js.map`/`.cjs.map`); `.d.ts.map` sí se
|
|
2959
|
+
publica.
|
|
2960
|
+
- `tsconfig.json#exactOptionalPropertyTypes: true`.
|
|
2961
|
+
- Sección CSS "78b. DIALOG" renombrada a "78. DIALOG / MODAL" (la 78
|
|
2962
|
+
legacy se eliminó).
|
|
2963
|
+
- `Tabs`, `Sidebar`, `OptionsMenu`, `Modal`, `Accordion`, `ToastProvider`
|
|
2964
|
+
con `Provider value` memoizado (`useMemo`+`useCallback`).
|
|
2965
|
+
- `OptionsMenu.setOpen` ahora `useCallback` con deps correctas (era stale
|
|
2966
|
+
closure cuando `onOpenChange` cambiaba entre renders).
|
|
2967
|
+
- `Button.disabled || loading` (antes `??`, permitía que `disabled={false}`
|
|
2968
|
+
anulara `loading={true}`).
|
|
2969
|
+
- `{...rest}` ahora se aplica ANTES de role/aria/tabIndex/disabled/hidden
|
|
2970
|
+
en todos los componentes, evitando que un consumer pueda sobreescribir
|
|
2971
|
+
atributos críticos accidentalmente.
|
|
2972
|
+
|
|
2973
|
+
### Removed
|
|
2974
|
+
- `.ig-modal-*` (variante legacy con `<div>` — el componente React usa
|
|
2975
|
+
`<dialog>` nativo). 11 clases CSS + 7 tokens `--ig-modal-*` + 164
|
|
2976
|
+
utilities pseudo-class autogeneradas.
|
|
2977
|
+
- `.ig-input-{brand,secondary,info,danger,warning}` (variantes color
|
|
2978
|
+
nunca expuestas en TS).
|
|
2979
|
+
- `dist/test/setup.d.ts` del publish (Vitest setup, no público).
|
|
2980
|
+
- `useState` muerto en `Stepper` (era presentational, no necesitaba
|
|
2981
|
+
estado interno).
|
|
2982
|
+
- Cast `alt={alt ?? ""}` en `Avatar` (`alt` ya required en el tipo
|
|
2983
|
+
`AvatarImage`).
|
|
2984
|
+
|
|
2985
|
+
### Fixed
|
|
2986
|
+
- 4 missings de barrel export: `useOptionsMenu`+`OptionsMenuContextValue`,
|
|
2987
|
+
`useTabs`+`TabsContextValue`, `SidebarContextValue`,
|
|
2988
|
+
`ToastContextValue`, `ModalContextValue`.
|
|
2989
|
+
- Colisión de `Theme` (definido en dos sitios) — ahora única fuente en
|
|
2990
|
+
`hooks/useTheme.ts`.
|
|
2991
|
+
- `Badge.outline-{variant}` y `Badge.pill` referenciaban CSS inexistente.
|
|
2992
|
+
- `Spinner.size="xs"` referenciaba CSS inexistente.
|
|
2993
|
+
- `TabPanel.tabIndex={isActive ? 0 : -1}` (antes siempre 0, panel oculto
|
|
2994
|
+
era focusable).
|
|
2995
|
+
- Tooltip ya no sobreescribe `aria-describedby` del child existente.
|
|
2996
|
+
|
|
2997
|
+
### Métricas finales
|
|
2998
|
+
- Pack tarball: 860 KB.
|
|
2999
|
+
- 32 unit test files / 285 tests + 32 storybook test files / 175 tests.
|
|
3000
|
+
- 0 TSX→CSS missings, 0 hooks/contexts sin barrel, 0 CSS huérfanas reales.
|
|
3001
|
+
- Bundle ESM: 50 KB / gzip 11 KB para 74 componentes.
|