@studiolxd/brand 46.0.0 → 47.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/dist/_shared/portal-container.js +9 -0
  3. package/dist/_types/atoms/AsyncMultiSelect/AsyncMultiSelect.d.ts +8 -7
  4. package/dist/_types/atoms/AsyncSelect/AsyncSelect.d.ts +8 -6
  5. package/dist/_types/atoms/InputPhone/InputPhone.d.ts +8 -5
  6. package/dist/_types/atoms/MenuButton/MenuButton.d.ts +22 -2
  7. package/dist/_types/atoms/MultiSelect/MultiSelect.d.ts +8 -6
  8. package/dist/_types/atoms/Popover/Popover.d.ts +8 -1
  9. package/dist/_types/atoms/Select/Select.d.ts +8 -7
  10. package/dist/_types/atoms/Tooltip/Tooltip.d.ts +7 -0
  11. package/dist/_types/constants/portal-container.d.ts +42 -0
  12. package/dist/_types/messages/BrandMessages.d.ts +29 -1
  13. package/dist/_types/messages/index.d.ts +1 -1
  14. package/dist/_types/molecules/Breadcrumb/Breadcrumb.d.ts +10 -2
  15. package/dist/_types/molecules/EmptyState/EmptyState.d.ts +2 -0
  16. package/dist/_types/molecules/Menu/Menu.d.ts +8 -1
  17. package/dist/_types/molecules/Modal/Modal.d.ts +8 -6
  18. package/dist/_types/molecules/OrgSwitcher/OrgSwitcher.d.ts +15 -1
  19. package/dist/_types/molecules/PageIntro/PageIntro.d.ts +12 -7
  20. package/dist/_types/molecules/PrevNextNav/PrevNextNav.d.ts +17 -2
  21. package/dist/_types/molecules/Sheet/Sheet.d.ts +8 -7
  22. package/dist/_types/molecules/SidebarNav/SidebarNav.d.ts +18 -3
  23. package/dist/_types/molecules/SiteNav/SiteNav.d.ts +9 -0
  24. package/dist/_types/molecules/TableOfContents/TableOfContents.d.ts +11 -2
  25. package/dist/_types/molecules/UserMenu/UserMenu.d.ts +16 -1
  26. package/dist/_types/sections/AppHeader/AppHeader.d.ts +8 -2
  27. package/dist/_types/sections/AppRoot/AppRoot.d.ts +12 -1
  28. package/dist/_types/sections/AppShell/AppShell.d.ts +14 -1
  29. package/dist/_types/sections/Hero/Hero.d.ts +4 -1
  30. package/dist/_types/sections/Sidebar/Sidebar.d.ts +27 -3
  31. package/dist/_types/sections/SiteHeader/SiteHeader.d.ts +22 -3
  32. package/dist/_types/sections/SiteShell/SiteShell.d.ts +11 -5
  33. package/dist/_types/templates/OnboardingShell/OnboardingShell.d.ts +19 -2
  34. package/dist/_types/templates/PublicPageShell/PublicPageShell.d.ts +42 -6
  35. package/dist/app-header.js +1 -1
  36. package/dist/app-launcher.js +62 -58
  37. package/dist/app-root.js +10 -8
  38. package/dist/app-shell.js +51 -50
  39. package/dist/async-multi-select.js +65 -64
  40. package/dist/async-select.js +49 -48
  41. package/dist/brand.css +12 -2
  42. package/dist/breadcrumb.js +24 -21
  43. package/dist/input-phone.js +38 -37
  44. package/dist/link.css +1 -1
  45. package/dist/menu-button.js +16 -14
  46. package/dist/menu.js +44 -40
  47. package/dist/modal.js +32 -31
  48. package/dist/multi-select.js +106 -105
  49. package/dist/onboarding-shell.js +36 -35
  50. package/dist/org-switcher.js +64 -59
  51. package/dist/page-intro.css +1 -1
  52. package/dist/page-intro.js +23 -17
  53. package/dist/popover.js +27 -22
  54. package/dist/prev-next-nav.js +38 -36
  55. package/dist/public-page-shell.js +24 -20
  56. package/dist/select.js +70 -69
  57. package/dist/sheet.js +30 -29
  58. package/dist/sidebar-nav.js +91 -90
  59. package/dist/sidebar.js +72 -71
  60. package/dist/site-header.js +44 -43
  61. package/dist/site-nav.js +27 -25
  62. package/dist/site-shell.js +23 -15
  63. package/dist/table-of-contents.js +30 -27
  64. package/dist/toaster.js +57 -53
  65. package/dist/tokens.css +2 -2
  66. package/dist/tooltip.js +45 -41
  67. package/dist/user-menu.js +58 -52
  68. package/package.json +1 -1
  69. package/src/tokens/molecules/page-intro.css +1 -1
  70. package/src/tokens/scss/molecules/_page-intro.scss +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,182 @@ El paquete sigue [semver](https://semver.org/lang/es/): **patch** para bug fixes
7
7
  regeneración de `dist`, **minor** para componentes/props/variantes/tokens nuevos, **major**
8
8
  para breaking changes.
9
9
 
10
+ ## [47.1.0] — 2026-09-15
11
+
12
+ > **Minor.** La norma de puntuación de subtítulos y estados vacíos, fijada por escrito en
13
+ > Foundations → Redacción, con el JSDoc de `Hero`, `PageIntro` y `EmptyState` remitiendo a
14
+ > ella. Documentación y ejemplos, sin cambios de comportamiento ni de CSS de por sí — salvo
15
+ > el `fix` suelto que viaja en la misma release: un `Link` dejaba de medir su texto al ser
16
+ > ítem directo de un `Stack align="stretch"`.
17
+
18
+ ### `PageIntro` gana `eyebrow`: la ranura encima del título
19
+
20
+ Prop nueva, `eyebrow?: ReactNode`: lo que va **encima** del título —una `Tag` de estado,
21
+ una categoría, un `Breadcrumb` corto—, nunca un subtítulo ni una frase larga (para eso
22
+ sigue estando `description`). Con `actions`, el eyebrow se queda en la **columna del
23
+ título**, nunca en la de las acciones: el título y las acciones pasan a compartir fila
24
+ dentro de una nueva `.page-intro__title-group` que agrupa eyebrow y título, y las reglas
25
+ de la fila (ancho flexible, línea base, aire bajo el bloque) se mueven de `.heading` a
26
+ ese grupo. El hueco entre eyebrow y título es `--page-intro-row-gap`, el mismo que ya
27
+ separaba el título de las acciones apiladas — ningún token nuevo. Sin `eyebrow`, el
28
+ marcado es exactamente el de siempre. Story «Con eyebrow» y su test de contrato en
29
+ `Molecules/PageIntro`.
30
+
31
+ ### Redacción: subtítulo y estado vacío terminan en punto
32
+
33
+ Página nueva, `Foundations/Redacción`: todo subtítulo (`Hero.description`,
34
+ `PageIntro.description`) y toda `EmptyState.description` terminan en punto; el `title` de
35
+ `EmptyState`, los rótulos, los botones y los tags, no. Sin puntos suspensivos ni
36
+ exclamación; el signo de interrogación solo si la frase es una pregunta de verdad. El
37
+ JSDoc de las tres props afectadas remite a la página. `dataTable.empty` del catálogo de
38
+ Storybook (`.storybook/brandMessagesFixture.ts`) llevaba un punto de más: alimenta el
39
+ `title` de `EmptyState` dentro de `DataTable`, y un título no lleva punto.
40
+
41
+ ### Fix: un `Link` directo de un `Stack align="stretch"` no se estira
42
+
43
+ `Link` fijaba su color, su subrayado y su relleno pero no su ancho: como ítem de un flex en
44
+ columna con `align-items: stretch`, ocupaba todo el ancho del `Stack` y la línea de su
45
+ subrayado cruzaba la página entera —el caso de un «← Volver» sobre un `PageIntro`—. Ahora
46
+ `inline-size: fit-content` en la cara base del enlace (`a:not(.button)`, `.link`): su caja
47
+ vuelve a ser la de su texto, sin tocar el enlace suelto en un párrafo (donde `width` no
48
+ aplica a una caja `inline`) ni el centrado vertical de `Inline` (`align-items: center`),
49
+ que un `align-self` habría descuadrado. Story «El enlace dentro de una columna» en
50
+ `Atoms/Link`, con su test de contrato.
51
+
52
+ ## [47.0.0] — 2026-09-15
53
+
54
+ > **Major.** Octava familia al proveedor de textos: el cromo de aplicación y la navegación.
55
+ > Catorce espacios nuevos, y con ellos la familia donde más claves ya existían en el
56
+ > catálogo de la suite. Además, los portales dejan de abrirse en la talla equivocada dentro
57
+ > de una página pública, y el `main` de `PublicPageShell` se puede pedir a sangre.
58
+
59
+ ### La navegación lee del proveedor
60
+
61
+ Catorce espacios nuevos en `BrandMessages`: `menuButton`, `appRoot`, `appShell`, `sidebar`,
62
+ `sidebarNav`, `siteNav`, `siteHeader`, `userMenu`, `orgSwitcher`, `breadcrumb`,
63
+ `tableOfContents`, `prevNextNav`, `publicPageShell` y `onboardingShell`. Mismo orden —**prop
64
+ → proveedor → error**— y cada texto se lee **donde se pinta**: una barra fuera de un
65
+ `AppShell` no tiene asa y no exige sus dos textos, una navegación sin entradas vacías no
66
+ exige su marca, un menú de cuenta sin contador no exige el plural, un paso sin acciones no
67
+ exige el nombre de su pie.
68
+
69
+ La frontera se decide por el valor. **Cromo**: el nombre de una región de navegación
70
+ («Principal», «En esta página», «Migas de pan»), el salto al contenido, qué hace el
71
+ logotipo, la dirección de un par anterior/siguiente. **Contenido**: los ítems de un menú,
72
+ los nombres de las organizaciones, el nombre de la persona, los títulos de destino del
73
+ `PrevNextNav`, el rótulo visible de un índice. `PrevNextNav` es el caso que mejor lo
74
+ enseña: «Anterior» y «Siguiente» son catálogo, pero un par que navega semanas quiere decir
75
+ «Semana anterior» y para eso siguen estando las props, que ganan.
76
+
77
+ ### Tres piezas entran al alcance por arrastre
78
+
79
+ `MenuButton` es donde vivía el castellano que reenviaban las dos cabeceras: migrarlo deja
80
+ `AppHeader.menuLabel` y `SiteHeader.menuLabel` como **reenvíos puros**, una sola clave en
81
+ vez de tres. `AppRoot` tiene la misma frase de salto que `AppShell` y se habría quedado
82
+ cableada. Y `PublicPageShell` tenía que migrar para que `OnboardingShell.preferencesLabel`
83
+ pudiera ser reenvío puro: es la misma banda y la misma clave.
84
+
85
+ `Switcher` estaba en la lista y **no entra**: no emite ningún texto propio. Lo nombra la
86
+ etiqueta de su campo.
87
+
88
+ ### Breaking — `MenuButton` abierto dice «cerrar»
89
+
90
+ El nombre accesible pasa a seguir al estado: cerrado lee `menuButton.open`, abierto lee
91
+ `menuButton.close`. Antes, con el menú abierto el botón seguía llamándose igual salvo que
92
+ el consumidor pasara `closeLabel` a mano, así que la cara de cerrar casi nunca se pintaba.
93
+ Ahora que el catálogo trae las dos, el nombre ya no se queda a medias. **Un test que
94
+ consulte el botón por su nombre con la barra abierta deja de encontrarlo**: es el punto
95
+ donde esto rompe en silencio.
96
+
97
+ `UserMenu` tenía un texto que ni siquiera era prop —el contador de notificaciones llevaba
98
+ `${count} notificaciones sin leer` escrito en el JSX—. Ahora es `userMenu.unread`, una
99
+ función, y `notifications.unreadCount` de la suite ya la satisface con su plural.
100
+
101
+ ### Los portales heredan la superficie
102
+
103
+ La v45.1.0 puso los controles en talla `lg` dentro de `.site-shell` por herencia CSS, y
104
+ dejó apuntado lo que no cubría: la lista de un `Select` y el calendario de un `DatePicker`
105
+ los monta un portal en `document.body`, que no es descendiente del bloque. El campo se
106
+ pintaba a 48px/20 y su lista se abría a 40px/16. El tema oscuro no tenía este problema
107
+ porque se activa en `<html>` y cascadea a todo el documento; la superficie de lectura se
108
+ activa a media altura del árbol.
109
+
110
+ `SiteShell` publica ahora su nodo raíz por `PortalContainerContext`, y todo componente con
111
+ portal lo toma como destino cuando no recibe `container`. La resolución es **prop →
112
+ contexto → `document.body`**, y la prop gana siempre. No hay nada que pasar en cada uso: el
113
+ árbol de React sabe dónde está el shell aunque el árbol del DOM no lo sepa. El nodo viaja
114
+ en **estado y no en una ref**, porque el destino tiene que existir en el render en el que
115
+ el portal se monta — el mismo patrón que `ChatShell` ya usaba para el `container` de su
116
+ cajón. `SiteShell` pasa a ser componente cliente.
117
+
118
+ Lo consumen por defecto `Select`, `MultiSelect`, `AsyncSelect`, `AsyncMultiSelect`,
119
+ `InputPhone`, `Modal`, `Sheet`, `AppLauncher`, `OrgSwitcher`, `UserMenu` y `Toaster`.
120
+ `Popover`, `Tooltip` y `Menu` **estrenan prop `container`** —son los primitivos
121
+ componibles, y sin ella no había escotilla—, y con ellos heredan sin tocar nada
122
+ `DatePicker`, `DateTimeField`, `TimeSelect`, `TimeField`, `ContextMenu`, `DropdownField`,
123
+ `NotificationButton`, `ConfirmDialog`, `ImageCropDialog`, `AvatarUpload`, `CommandPalette`
124
+ y `Consent`. `FloatingDock` queda intacto: monta su panel dentro de su propia ancla a
125
+ propósito y no pasa por el contexto.
126
+
127
+ **`AppShell` no provee el contexto, y es deliberado.** La superficie de aplicación es la
128
+ del `:root`, así que `document.body` ya resuelve los mismos valores; meter los portales
129
+ dentro de `.app-shell` —que es `overflow: clip` y de altura fija— solo añadiría riesgo de
130
+ recorte sin arreglar nada. El día que la superficie de aplicación cambie de talla, el
131
+ arreglo es montar ahí el mismo proveedor: una línea.
132
+
133
+ Los campos `*Field` siguen **sin** reenviar `container`, y también a propósito: obligar a
134
+ cada app a pasar un nodo en cada uso era el problema, no la solución. La escotilla vive en
135
+ el primitivo.
136
+
137
+ ### El `main` de `PublicPageShell` se puede pedir a sangre
138
+
139
+ `PublicPageShell` montaba su `main` como `Container as="main" space="xl"` fijo. Es lo que
140
+ quiere una página corriente, pero no una **portada**: la que abre con un `Hero` de lado a
141
+ lado no cabía en el molde y tenía que montarse a mano con `SiteShell` + `Container`, que es
142
+ justo lo que el molde único existe para evitar.
143
+
144
+ El `main` lleva ahora los mismos tres mandos que un `Container` —`mainWidth`, `mainSpace` y
145
+ `mainFlush`—, con el prefijo de la ranura porque el marco tiene cuatro y un `width` pelado
146
+ no diría cuál gobierna. **Los defaults son los de hoy** (`'xl'`, `'xl'`, `false`): quien no
147
+ los toque se pinta exactamente igual, así que esta parte no es breaking. Una portada pide
148
+ `mainWidth="full" mainSpace="none" mainFlush` y apila secciones, poniendo su propio
149
+ `Container` a lo que quiera columna.
150
+
151
+ El `main` sigue siendo el `main`, con su `id` y su `tabIndex`, así que el enlace de salto al
152
+ contenido llega igual a una portada abierta a sangre.
153
+
154
+ ### Para quien actualice
155
+
156
+ El catálogo del `BrandMessagesProvider` tiene que crecer con los catorce espacios, o la
157
+ primera pantalla con un `Breadcrumb`, un `AppRoot` o un `MenuButton` **lanza en render**.
158
+ Estas son las claves que hay que aportar:
159
+
160
+ | Espacio | Claves | Qué es |
161
+ | --- | --- | --- |
162
+ | `menuButton` | `open`, `close` | qué hace el botón de menú, en sus dos caras |
163
+ | `appRoot` | `skipToContent` | el salto al contenido del documento |
164
+ | `appShell` | `skipToContent` | el salto al contenido de la aplicación |
165
+ | `sidebar` | `label`, `resizer`, `resizerValue(width)` | la región, el asa y su ancho hablado |
166
+ | `sidebarNav` | `label`, `empty` | la región y la marca de una entrada sin contenido |
167
+ | `siteNav` | `label` | la región del índice del sitio |
168
+ | `siteHeader` | `logo` | qué hace el logotipo — **lleva la marca dentro** |
169
+ | `userMenu` | `trigger(name)`, `unread(count)` | el botón de cuenta y su contador |
170
+ | `orgSwitcher` | `trigger(name)` | el botón del conmutador de organización |
171
+ | `breadcrumb` | `label` | la región del rastro |
172
+ | `tableOfContents` | `label` | la región del índice de la página |
173
+ | `prevNextNav` | `previous`, `next` | la dirección, no el destino |
174
+ | `publicPageShell` | `preferences` | la banda de idioma y tema |
175
+ | `onboardingShell` | `actions` | el grupo de acciones del pie del alta |
176
+
177
+ Cuatro de ellas interpolan un dato (`sidebar.resizerValue`, `userMenu.trigger`,
178
+ `userMenu.unread`, `orgSwitcher.trigger`), así que son funciones. `siteHeader.logo` la
179
+ escribe cada producto con su propia marca: el default retirado decía «Studio LXD — ir al
180
+ inicio».
181
+
182
+ A cambio, dejan de hacer falta para traducir `AppHeader.menuLabel` y `menuCloseLabel`,
183
+ `SiteHeader.menuLabel` y `menuCloseLabel`, y `OnboardingShell.preferencesLabel`: son
184
+ reenvíos puros y siguen existiendo solo como anulación puntual.
185
+
10
186
  ## [46.0.0] — 2026-09-15
11
187
 
12
188
  > **Major.** Séptima familia al proveedor de textos: los envoltorios de diálogo. `Consent`,
@@ -0,0 +1,9 @@
1
+ import { createContext as e, useContext as t } from "react";
2
+ //#region src/stories/constants/portal-container.ts
3
+ var n = e(null);
4
+ function r(e) {
5
+ let r = t(n);
6
+ return e === void 0 ? r ?? void 0 : e;
7
+ }
8
+ //#endregion
9
+ export { r as n, n as t };
@@ -89,13 +89,14 @@ export interface AsyncMultiSelectProps {
89
89
  */
90
90
  loadingLabel?: string;
91
91
  /**
92
- * Nodo DOM donde montar el portal del dropdown (reenviado a Base UI
93
- * `Portal.container`). Por defecto se monta en `document.body`, que
94
- * hereda el tema activado a nivel raíz (`html.dark`/`[data-theme="dark"]`)
95
- * sin configuración adicional. Solo hace falta pasarlo cuando el
96
- * AsyncMultiSelect vive dentro de un `.surface-dark` **anidado** (no en
97
- * la raíz), ya que ese contexto no llega a `document.body` por la
98
- * cascada.
92
+ * Nodo DOM donde montar el portal del dropdown (reenviado a Base UI `Portal.container`).
93
+ * Por defecto, el nodo de la superficie que llegue por contexto:
94
+ * `SiteShell` publica el suyo, de modo que la capa hereda la talla de la
95
+ * superficie pública en vez de abrirse a la de aplicación. Si no hay
96
+ * superficie, `document.body` — que ya hereda el tema activado en la raíz
97
+ * (`html.dark`/`[data-theme="dark"]`) sin configuración adicional. Pásalo
98
+ * solo para llevar la capa a otro sitio: un `.surface-dark` **anidado**, el
99
+ * cajón de un shell propio. Gana siempre.
99
100
  */
100
101
  container?: React.ComponentPropsWithoutRef<typeof BasePopover.Portal>['container'];
101
102
  }
@@ -78,12 +78,14 @@ export interface AsyncSelectProps {
78
78
  */
79
79
  clearLabel?: string;
80
80
  /**
81
- * Nodo DOM donde montar el portal del dropdown (reenviado a Base UI
82
- * `Portal.container`). Por defecto se monta en `document.body`, que
83
- * hereda el tema activado a nivel raíz (`html.dark`/`[data-theme="dark"]`)
84
- * sin configuración adicional. Solo hace falta pasarlo cuando el
85
- * AsyncSelect vive dentro de un `.surface-dark` **anidado** (no en la
86
- * raíz), ya que ese contexto no llega a `document.body` por la cascada.
81
+ * Nodo DOM donde montar el portal del dropdown (reenviado a Base UI `Portal.container`).
82
+ * Por defecto, el nodo de la superficie que llegue por contexto:
83
+ * `SiteShell` publica el suyo, de modo que la capa hereda la talla de la
84
+ * superficie pública en vez de abrirse a la de aplicación. Si no hay
85
+ * superficie, `document.body` — que ya hereda el tema activado en la raíz
86
+ * (`html.dark`/`[data-theme="dark"]`) sin configuración adicional. Pásalo
87
+ * solo para llevar la capa a otro sitio: un `.surface-dark` **anidado**, el
88
+ * cajón de un shell propio. Gana siempre.
87
89
  */
88
90
  container?: React.ComponentPropsWithoutRef<typeof BasePopover.Portal>['container'];
89
91
  }
@@ -47,11 +47,14 @@ export interface InputPhoneProps {
47
47
  internationalLabel?: string;
48
48
  /**
49
49
  * Nodo DOM donde montar el portal del dropdown de país (reenviado a
50
- * `Select.Portal` de Base UI). Por defecto se monta en `document.body`, que
51
- * hereda el tema activado a nivel raíz (`html.dark`/`[data-theme="dark"]`)
52
- * sin configuración adicional. Solo hace falta pasarlo cuando el
53
- * InputPhone vive dentro de un `.surface-dark` **anidado** (no en la
54
- * raíz), ya que ese contexto no llega a `document.body` por la cascada.
50
+ * `Select.Portal` de Base UI).
51
+ * Por defecto, el nodo de la superficie que llegue por contexto:
52
+ * `SiteShell` publica el suyo, de modo que la capa hereda la talla de la
53
+ * superficie pública en vez de abrirse a la de aplicación. Si no hay
54
+ * superficie, `document.body` — que ya hereda el tema activado en la raíz
55
+ * (`html.dark`/`[data-theme="dark"]`) sin configuración adicional. Pásalo
56
+ * solo para llevar la capa a otro sitio: un `.surface-dark` **anidado**, el
57
+ * cajón de un shell propio. Gana siempre.
55
58
  */
56
59
  container?: React.ComponentPropsWithoutRef<typeof BaseSelect.Portal>['container'];
57
60
  }
@@ -1,11 +1,31 @@
1
1
  import { type ComponentPropsWithoutRef } from 'react';
2
2
  import './MenuButton.css';
3
+ /**
4
+ * Los dos textos del botón de menú, y los dos son **cromo**: no dicen de qué
5
+ * menú son, dicen qué hace el botón. Son los mismos en la cabecera de la
6
+ * aplicación y en la del sitio, así que van al catálogo una sola vez y las dos
7
+ * cabeceras los reenvían sin repetir la clave.
8
+ */
9
+ export interface MenuButtonMessages {
10
+ /** Nombre accesible del botón con el menú cerrado. */
11
+ open: string;
12
+ /** Nombre accesible del botón con el menú abierto. */
13
+ close: string;
14
+ }
3
15
  export interface MenuButtonProps extends Omit<ComponentPropsWithoutRef<'button'>, 'children'> {
4
16
  /** Estado del menú que gobierna. Abierto, el glifo `menu` se convierte en `close`. */
5
17
  isOpen?: boolean;
6
- /** Texto accesible. Dice qué abre, no qué forma tiene. */
18
+ /**
19
+ * Texto accesible con el menú cerrado. Dice qué abre, no qué forma tiene.
20
+ * **Sin default**: sin él, sale de `menuButton.open` del
21
+ * `BrandMessagesProvider`.
22
+ */
7
23
  label?: string;
8
- /** Texto accesible cuando el menú está abierto («Cerrar menú»). Sin él, se usa `label` con `aria-expanded`. */
24
+ /**
25
+ * Texto accesible con el menú abierto. **Sin default**: sin él, sale de
26
+ * `menuButton.close` del `BrandMessagesProvider`. Solo se lee cuando el menú
27
+ * está abierto: un botón que nunca se abre no exige esa clave.
28
+ */
9
29
  closeLabel?: string;
10
30
  /** Talla del botón: un cuadrado de 32, 40 o 48px. En `lg` el glifo mide 48px. */
11
31
  size?: 'sm' | 'md' | 'lg';
@@ -54,12 +54,14 @@ export interface MultiSelectProps {
54
54
  */
55
55
  removeLabel?: (label: string) => string;
56
56
  /**
57
- * Nodo DOM donde montar el portal del dropdown (reenviado a Base UI
58
- * `Portal.container`). Por defecto se monta en `document.body`, que
59
- * hereda el tema activado a nivel raíz (`html.dark`/`[data-theme="dark"]`)
60
- * sin configuración adicional. Solo hace falta pasarlo cuando el
61
- * MultiSelect vive dentro de un `.surface-dark` **anidado** (no en la
62
- * raíz), ya que ese contexto no llega a `document.body` por la cascada.
57
+ * Nodo DOM donde montar el portal del dropdown (reenviado a Base UI `Portal.container`).
58
+ * Por defecto, el nodo de la superficie que llegue por contexto:
59
+ * `SiteShell` publica el suyo, de modo que la capa hereda la talla de la
60
+ * superficie pública en vez de abrirse a la de aplicación. Si no hay
61
+ * superficie, `document.body` — que ya hereda el tema activado en la raíz
62
+ * (`html.dark`/`[data-theme="dark"]`) sin configuración adicional. Pásalo
63
+ * solo para llevar la capa a otro sitio: un `.surface-dark` **anidado**, el
64
+ * cajón de un shell propio. Gana siempre.
63
65
  */
64
66
  container?: React.ComponentPropsWithoutRef<typeof BasePopover.Portal>['container'];
65
67
  }
@@ -54,6 +54,13 @@ export interface PopoverProps {
54
54
  * un `ref`, o una función que lo devuelva. Es la prop de Base UI, tal cual.
55
55
  */
56
56
  initialFocus?: React.ComponentProps<typeof BasePopover.Popup>['initialFocus'];
57
+ /**
58
+ * Nodo DOM donde montar el portal. Por defecto, el nodo de la superficie que
59
+ * llegue por contexto —`SiteShell` publica el suyo, para que la capa herede
60
+ * la talla de la superficie pública— y, si no hay ninguna, `document.body`.
61
+ * Pásalo solo para llevar la capa a otro sitio: gana siempre.
62
+ */
63
+ container?: HTMLElement | null;
57
64
  /** Clase adicional para el panel. */
58
65
  className?: string;
59
66
  }
@@ -67,4 +74,4 @@ export interface PopoverProps {
67
74
  * mantener el panel abierto cuando el clic cae en algo que el motor no
68
75
  * reconoce como suyo.
69
76
  */
70
- export declare function Popover({ trigger, children, label, open, defaultOpen, onOpenChange, onPointerDownOutside, onFocusOutside, onEscapeKeyDown, side, align, sideOffset, initialFocus, className, }: PopoverProps): import("react/jsx-runtime").JSX.Element;
77
+ export declare function Popover({ trigger, children, label, open, defaultOpen, onOpenChange, onPointerDownOutside, onFocusOutside, onEscapeKeyDown, side, align, sideOffset, initialFocus, container, className, }: PopoverProps): import("react/jsx-runtime").JSX.Element;
@@ -70,13 +70,14 @@ export interface SelectProps {
70
70
  /** Estado de error accesible (lo pone el campo). */
71
71
  'aria-invalid'?: boolean;
72
72
  /**
73
- * Nodo DOM donde montar el portal del dropdown (reenviado a `Select.Portal`
74
- * de Base UI). Por defecto el portal se monta en `document.body`, que hereda
75
- * el tema activado a nivel raíz (`html.dark`/`[data-theme="dark"]`) sin
76
- * configuración adicional. Solo hace falta pasar `container` cuando el Select
77
- * vive dentro de un `.surface-dark` **anidado** (no en la raíz): ese contexto
78
- * no llega a `document.body` por la cascada, así que hay que montar el portal
79
- * dentro del propio contenedor con la clase.
73
+ * Nodo DOM donde montar el portal del dropdown (reenviado a `Select.Portal` de Base UI).
74
+ * Por defecto, el nodo de la superficie que llegue por contexto:
75
+ * `SiteShell` publica el suyo, de modo que la capa hereda la talla de la
76
+ * superficie pública en vez de abrirse a la de aplicación. Si no hay
77
+ * superficie, `document.body` — que ya hereda el tema activado en la raíz
78
+ * (`html.dark`/`[data-theme="dark"]`) sin configuración adicional. Pásalo
79
+ * solo para llevar la capa a otro sitio: un `.surface-dark` **anidado**, el
80
+ * cajón de un shell propio. Gana siempre.
80
81
  */
81
82
  container?: SelectPortalContainer;
82
83
  }
@@ -41,6 +41,13 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLElement>, 'c
41
41
  * @default true
42
42
  */
43
43
  describe?: boolean;
44
+ /**
45
+ * Nodo DOM donde montar el portal. Por defecto, el nodo de la superficie que
46
+ * llegue por contexto —`SiteShell` publica el suyo, para que la capa herede
47
+ * la talla de la superficie pública— y, si no hay ninguna, `document.body`.
48
+ * Pásalo solo para llevar la capa a otro sitio: gana siempre.
49
+ */
50
+ container?: HTMLElement | null;
44
51
  /** Clase adicional para el **bocadillo** (no para el disparador). */
45
52
  className?: string;
46
53
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * El nodo donde montan su portal los componentes que abren una capa flotante
3
+ * —la lista de un `Select`, el calendario de un `DatePicker`, el panel de un
4
+ * `Popover`, el velo de un `Modal`— cuando no se les pasa `container`.
5
+ *
6
+ * **Por qué hace falta.** Un portal monta en `document.body`, que no es
7
+ * descendiente de `.site-shell`. El tema oscuro sobrevive a eso porque se
8
+ * activa en `<html>` y cascadea a todo el documento; la **superficie de
9
+ * lectura** no: `.site-shell` remapea los tokens de texto y pone los controles
10
+ * en talla `lg` (v45.1.0) desde una clase que está a media altura del árbol.
11
+ * Resultado antes de esto: el campo se pintaba a 48px/20 y su lista se abría a
12
+ * 40px/16, fuera del shell.
13
+ *
14
+ * **Cómo se arregla.** `SiteShell` publica su nodo raíz por este contexto y
15
+ * todo componente con portal lo toma como destino por defecto. No hay nada que
16
+ * pasar en cada uso: el árbol de React ya sabe dónde está el shell aunque el
17
+ * árbol del DOM no lo sepa. El valor se guarda en **estado**, no en una ref —
18
+ * el destino tiene que existir en el render en el que el portal se monta, y
19
+ * una ref no provoca repintado al rellenarse. Es el patrón que `ChatShell` ya
20
+ * usaba para el `container` de su cajón.
21
+ *
22
+ * La prop `container` sigue existiendo y **gana siempre**: es la salida para
23
+ * quien quiera otro destino (el ancla de `FloatingDock`, un `.surface-dark`
24
+ * anidado).
25
+ *
26
+ * `AppShell` **no** lo provee, y es deliberado: la superficie de aplicación es
27
+ * la del `:root`, así que `document.body` ya resuelve los mismos valores y
28
+ * meter los portales dentro de `.app-shell` —que es `overflow: clip` y de
29
+ * altura fija— solo añadiría riesgo de recorte sin arreglar nada. El día que
30
+ * la superficie de aplicación cambie de talla, el arreglo es una línea: montar
31
+ * este mismo proveedor en `AppShell`.
32
+ */
33
+ export declare const PortalContainerContext: import("react").Context<HTMLElement | null>;
34
+ /**
35
+ * Resuelve el destino del portal: la prop si el consumidor la pasa, y si no,
36
+ * el nodo de la superficie que venga por contexto.
37
+ *
38
+ * Se llama **siempre**, con o sin prop —es un hook—, y devuelve `undefined`
39
+ * cuando no hay ni una cosa ni la otra, que es lo que Base UI entiende por
40
+ * «monta en `document.body`».
41
+ */
42
+ export declare function usePortalContainer<T>(container: T): T | HTMLElement | undefined;
@@ -31,6 +31,20 @@ import type { AppLauncherMessages } from '../molecules/AppLauncher/AppLauncher';
31
31
  import type { FloatingDockMessages } from '../sections/FloatingDock/FloatingDock';
32
32
  import type { NotificationButtonMessages } from '../molecules/NotificationButton/NotificationButton';
33
33
  import type { NotificationPanelMessages } from '../molecules/NotificationPanel/NotificationPanel';
34
+ import type { MenuButtonMessages } from '../atoms/MenuButton/MenuButton';
35
+ import type { AppRootMessages } from '../sections/AppRoot/AppRoot';
36
+ import type { AppShellMessages } from '../sections/AppShell/AppShell';
37
+ import type { SidebarMessages } from '../sections/Sidebar/Sidebar';
38
+ import type { SidebarNavMessages } from '../molecules/SidebarNav/SidebarNav';
39
+ import type { SiteNavMessages } from '../molecules/SiteNav/SiteNav';
40
+ import type { SiteHeaderMessages } from '../sections/SiteHeader/SiteHeader';
41
+ import type { UserMenuMessages } from '../molecules/UserMenu/UserMenu';
42
+ import type { OrgSwitcherMessages } from '../molecules/OrgSwitcher/OrgSwitcher';
43
+ import type { BreadcrumbMessages } from '../molecules/Breadcrumb/Breadcrumb';
44
+ import type { TableOfContentsMessages } from '../molecules/TableOfContents/TableOfContents';
45
+ import type { PrevNextNavMessages } from '../molecules/PrevNextNav/PrevNextNav';
46
+ import type { PublicPageShellMessages } from '../templates/PublicPageShell/PublicPageShell';
47
+ import type { OnboardingShellMessages } from '../templates/OnboardingShell/OnboardingShell';
34
48
  /**
35
49
  * El contrato de textos de la librería: un espacio por componente, y dentro
36
50
  * de cada espacio **todas las claves obligatorias**.
@@ -86,5 +100,19 @@ export interface BrandMessages {
86
100
  floatingDock: FloatingDockMessages;
87
101
  notificationButton: NotificationButtonMessages;
88
102
  notificationPanel: NotificationPanelMessages;
103
+ menuButton: MenuButtonMessages;
104
+ appRoot: AppRootMessages;
105
+ appShell: AppShellMessages;
106
+ sidebar: SidebarMessages;
107
+ sidebarNav: SidebarNavMessages;
108
+ siteNav: SiteNavMessages;
109
+ siteHeader: SiteHeaderMessages;
110
+ userMenu: UserMenuMessages;
111
+ orgSwitcher: OrgSwitcherMessages;
112
+ breadcrumb: BreadcrumbMessages;
113
+ tableOfContents: TableOfContentsMessages;
114
+ prevNextNav: PrevNextNavMessages;
115
+ publicPageShell: PublicPageShellMessages;
116
+ onboardingShell: OnboardingShellMessages;
89
117
  }
90
- export type { PaginationMessages, TableMessages, DataTableMessages, InputFieldMessages, PasswordFieldMessages, SelectMessages, MultiSelectMessages, NumberInputMessages, OtpInputMessages, InputPhoneMessages, AsyncSelectMessages, AsyncMultiSelectMessages, DocsSearchMessages, SearchFormMessages, FilterBarMessages, CalendarMessages, DatePickerMessages, TimeSelectMessages, FileUploadMessages, ImageCropDialogMessages, AvatarUploadMessages, ModalMessages, SheetMessages, ConfirmDialogMessages, AlertMessages, BannerMessages, ToasterMessages, ConsentMessages, CommandPaletteMessages, AppLauncherMessages, FloatingDockMessages, NotificationButtonMessages, NotificationPanelMessages, };
118
+ export type { PaginationMessages, TableMessages, DataTableMessages, InputFieldMessages, PasswordFieldMessages, SelectMessages, MultiSelectMessages, NumberInputMessages, OtpInputMessages, InputPhoneMessages, AsyncSelectMessages, AsyncMultiSelectMessages, DocsSearchMessages, SearchFormMessages, FilterBarMessages, CalendarMessages, DatePickerMessages, TimeSelectMessages, FileUploadMessages, ImageCropDialogMessages, AvatarUploadMessages, ModalMessages, SheetMessages, ConfirmDialogMessages, AlertMessages, BannerMessages, ToasterMessages, ConsentMessages, CommandPaletteMessages, AppLauncherMessages, FloatingDockMessages, NotificationButtonMessages, NotificationPanelMessages, MenuButtonMessages, AppRootMessages, AppShellMessages, SidebarMessages, SidebarNavMessages, SiteNavMessages, SiteHeaderMessages, UserMenuMessages, OrgSwitcherMessages, BreadcrumbMessages, TableOfContentsMessages, PrevNextNavMessages, PublicPageShellMessages, OnboardingShellMessages, };
@@ -2,4 +2,4 @@ export { BrandMessagesProvider } from './BrandMessagesProvider';
2
2
  export type { BrandMessagesProviderProps } from './BrandMessagesProvider';
3
3
  export { useBrandMessages } from './BrandMessagesContext';
4
4
  export type { BrandMessagesReader } from './BrandMessagesContext';
5
- export type { BrandMessages, PaginationMessages, TableMessages, DataTableMessages, InputFieldMessages, PasswordFieldMessages, SelectMessages, MultiSelectMessages, NumberInputMessages, OtpInputMessages, InputPhoneMessages, AsyncSelectMessages, AsyncMultiSelectMessages, DocsSearchMessages, SearchFormMessages, FilterBarMessages, CalendarMessages, DatePickerMessages, TimeSelectMessages, FileUploadMessages, ImageCropDialogMessages, AvatarUploadMessages, ModalMessages, SheetMessages, ConfirmDialogMessages, AlertMessages, BannerMessages, ToasterMessages, ConsentMessages, CommandPaletteMessages, AppLauncherMessages, FloatingDockMessages, NotificationButtonMessages, NotificationPanelMessages, } from './BrandMessages';
5
+ export type { BrandMessages, PaginationMessages, TableMessages, DataTableMessages, InputFieldMessages, PasswordFieldMessages, SelectMessages, MultiSelectMessages, NumberInputMessages, OtpInputMessages, InputPhoneMessages, AsyncSelectMessages, AsyncMultiSelectMessages, DocsSearchMessages, SearchFormMessages, FilterBarMessages, CalendarMessages, DatePickerMessages, TimeSelectMessages, FileUploadMessages, ImageCropDialogMessages, AvatarUploadMessages, ModalMessages, SheetMessages, ConfirmDialogMessages, AlertMessages, BannerMessages, ToasterMessages, ConsentMessages, CommandPaletteMessages, AppLauncherMessages, FloatingDockMessages, NotificationButtonMessages, NotificationPanelMessages, MenuButtonMessages, AppRootMessages, AppShellMessages, SidebarMessages, SidebarNavMessages, SiteNavMessages, SiteHeaderMessages, UserMenuMessages, OrgSwitcherMessages, BreadcrumbMessages, TableOfContentsMessages, PrevNextNavMessages, PublicPageShellMessages, OnboardingShellMessages, } from './BrandMessages';
@@ -14,10 +14,18 @@ export interface BreadcrumbProps {
14
14
  renderLink?: (props: BreadcrumbRenderLinkProps) => ReactNode;
15
15
  separator?: ReactNode;
16
16
  /**
17
- * `aria-label` del `<nav>`. Default: «Migas de pan» (castellano).
18
- * Una app multiidioma debe pasarlo traducido.
17
+ * `aria-label` del `<nav>`. **Sin default**: sin él, sale de
18
+ * `breadcrumb.label` del `BrandMessagesProvider`.
19
19
  */
20
20
  ariaLabel?: string;
21
21
  className?: string;
22
22
  }
23
+ /**
24
+ * El único texto que las migas dicen por su cuenta, y es **cromo**: el nombre de
25
+ * la región. Los rótulos del rastro son **contenido** y vienen en `items`.
26
+ */
27
+ export interface BreadcrumbMessages {
28
+ /** Nombre accesible del `nav`. */
29
+ label: string;
30
+ }
23
31
  export declare function Breadcrumb({ items, renderLink, separator, ariaLabel, className, }: BreadcrumbProps): import("react/jsx-runtime").JSX.Element;
@@ -5,7 +5,9 @@ export interface EmptyStateAction {
5
5
  href?: string;
6
6
  }
7
7
  export interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
8
+ /** El rótulo del estado: sin punto (ver Foundations → Redacción). */
8
9
  title: string;
10
+ /** La frase que lo explica, opcional: termina en punto (ver Foundations → Redacción). */
9
11
  description?: string;
10
12
  icon?: React.ReactNode;
11
13
  action?: EmptyStateAction;
@@ -29,6 +29,13 @@ export interface MenuProps {
29
29
  maxWidth?: string;
30
30
  /** Talla de los ítems, la del disparador (32/40/48): el panel desplegado casa con el control plegado, como en el Select. */
31
31
  size?: 'sm' | 'md' | 'lg';
32
+ /**
33
+ * Nodo DOM donde montar el portal. Por defecto, el nodo de la superficie que
34
+ * llegue por contexto —`SiteShell` publica el suyo, para que la capa herede
35
+ * la talla de la superficie pública— y, si no hay ninguna, `document.body`.
36
+ * Pásalo solo para llevar la capa a otro sitio: gana siempre.
37
+ */
38
+ container?: HTMLElement | null;
32
39
  className?: string;
33
40
  }
34
41
  /**
@@ -36,4 +43,4 @@ export interface MenuProps {
36
43
  * (tokens `menu.*`) de todos los menús; `ContextMenu`, `UserMenu`,
37
44
  * `OrgSwitcher` o `DropdownField` son este menú con un disparador concreto.
38
45
  */
39
- export declare function Menu({ trigger, items, value, onValueChange, renderLink, open, defaultOpen, onOpenChange, openOnHover, hoverDelay, side, align, sideOffset, minWidth, maxWidth, size, className, }: MenuProps): import("react/jsx-runtime").JSX.Element;
46
+ export declare function Menu({ trigger, items, value, onValueChange, renderLink, open, defaultOpen, onOpenChange, openOnHover, hoverDelay, side, align, sideOffset, minWidth, maxWidth, size, container, className, }: MenuProps): import("react/jsx-runtime").JSX.Element;
@@ -33,12 +33,14 @@ export interface ModalProps extends Omit<React.ComponentPropsWithoutRef<'div'>,
33
33
  */
34
34
  fallbackTitle?: string;
35
35
  /**
36
- * Nodo DOM donde montar el portal del modal (reenviado a Base UI
37
- * `Portal.container`). Por defecto se monta en `document.body`, que
38
- * hereda el tema activado a nivel raíz (`html.dark`/`[data-theme="dark"]`)
39
- * sin configuración adicional. Solo hace falta pasarlo cuando el Modal
40
- * vive dentro de un `.surface-dark` **anidado** (no en la raíz), ya que
41
- * ese contexto no llega a `document.body` por la cascada.
36
+ * Nodo DOM donde montar el portal del modal (reenviado a Base UI `Portal.container`).
37
+ * Por defecto, el nodo de la superficie que llegue por contexto:
38
+ * `SiteShell` publica el suyo, de modo que la capa hereda la talla de la
39
+ * superficie pública en vez de abrirse a la de aplicación. Si no hay
40
+ * superficie, `document.body` — que ya hereda el tema activado en la raíz
41
+ * (`html.dark`/`[data-theme="dark"]`) sin configuración adicional. Pásalo
42
+ * solo para llevar la capa a otro sitio: un `.surface-dark` **anidado**, el
43
+ * cajón de un shell propio. Gana siempre.
42
44
  */
43
45
  container?: React.ComponentPropsWithoutRef<typeof Dialog.Portal>['container'];
44
46
  /**
@@ -6,8 +6,22 @@ export interface OrgOption {
6
6
  name: string;
7
7
  logoUrl?: string;
8
8
  }
9
+ /**
10
+ * El único texto que el conmutador dice por su cuenta, y es **cromo**: cómo se
11
+ * nombra su botón. Interpola la organización activa, así que es una función.
12
+ * Los nombres de las organizaciones son **contenido** y vienen en
13
+ * `organizations`.
14
+ */
15
+ export interface OrgSwitcherMessages {
16
+ /** Nombre accesible del botón, a partir del nombre de la organización. */
17
+ trigger: (name: string) => string;
18
+ }
9
19
  export interface OrgSwitcherProps {
10
- /** Nombre accesible del botón. Por defecto, «Organización: ‹nombre›». */
20
+ /**
21
+ * Nombre accesible del botón. **Sin default**: sin él, sale de
22
+ * `orgSwitcher.trigger` del `BrandMessagesProvider`, que recibe el nombre de
23
+ * la organización activa.
24
+ */
11
25
  label?: string;
12
26
  /** Ocupa todo el ancho disponible (en la Sidebar). Por defecto mide lo que su contenido. */
13
27
  block?: boolean;
@@ -2,16 +2,21 @@ import type { ReactNode } from 'react';
2
2
  import { type HeadingProps } from '../../atoms/Heading/Heading';
3
3
  import './PageIntro.css';
4
4
  export interface PageIntroProps {
5
+ /**
6
+ * Encima del título: una `Tag` de estado, una categoría, un `Breadcrumb`
7
+ * corto. No es un subtítulo ni acepta una frase larga —para eso está
8
+ * `description`—, es una pieza pequeña que sitúa la página antes de
9
+ * nombrarla. Con `actions`, queda en la columna del título, nunca en la
10
+ * de las acciones.
11
+ */
12
+ eyebrow?: ReactNode;
5
13
  /** El título de la página: un `Heading` de nivel 1 (o el que diga `level`). */
6
14
  title: ReactNode;
7
15
  /**
8
16
  * La frase bajo el título, opcional: va como entradilla (`Paragraph
9
- * size="large"`, un peldaño por encima del cuerpo).
10
- *
11
- * **Es una frase y termina con puntuación final** —un punto, o el signo que
12
- * le toque—: no es un rótulo ni un subtítulo. Sin el punto, el párrafo se
13
- * lee como un `Heading` menor y la jerarquía de la cabecera se deshace. Lo
14
- * que no llegue a frase o cabe en el título, o va en `children`.
17
+ * size="large"`, un peldaño por encima del cuerpo). Termina en punto (ver
18
+ * Foundations → Redacción). Lo que no llegue a frase, o cabe en el título,
19
+ * o va en `children`.
15
20
  */
16
21
  description?: ReactNode;
17
22
  /** Más texto bajo la frase (otro `Paragraph`, una lista…): mismo aire. */
@@ -45,4 +50,4 @@ export interface PageIntroProps {
45
50
  * Con `actions` sirve además de cabecera de una sección dentro de la página
46
51
  * (`level={2}`): el título a la izquierda y la acción principal a la derecha.
47
52
  */
48
- export declare function PageIntro({ title, description, actions, level, size, as: Tag, className, children, }: PageIntroProps): import("react/jsx-runtime").JSX.Element;
53
+ export declare function PageIntro({ eyebrow, title, description, actions, level, size, as: Tag, className, children, }: PageIntroProps): import("react/jsx-runtime").JSX.Element;