@studiolxd/brand 41.1.0 → 43.0.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 (41) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/dist/_shared/datepicker.js +75 -78
  3. package/dist/_types/atoms/AsyncMultiSelect/AsyncMultiSelect.d.ts +28 -5
  4. package/dist/_types/atoms/AsyncSelect/AsyncSelect.d.ts +26 -6
  5. package/dist/_types/atoms/InputPhone/InputPhone.d.ts +15 -4
  6. package/dist/_types/atoms/MultiSelect/MultiSelect.d.ts +20 -1
  7. package/dist/_types/atoms/NumberInput/NumberInput.d.ts +14 -4
  8. package/dist/_types/atoms/OtpInput/OtpInput.d.ts +15 -4
  9. package/dist/_types/atoms/Select/Select.d.ts +18 -1
  10. package/dist/_types/atoms/TimeSelect/TimeSelect.d.ts +34 -6
  11. package/dist/_types/messages/BrandMessages.d.ts +31 -1
  12. package/dist/_types/messages/index.d.ts +1 -1
  13. package/dist/_types/molecules/Calendar/Calendar.d.ts +44 -13
  14. package/dist/_types/molecules/DatePicker/DatePicker.d.ts +56 -26
  15. package/dist/_types/molecules/DatePicker/dateMask.d.ts +9 -2
  16. package/dist/_types/molecules/DateTimeField/DateTimeField.d.ts +12 -12
  17. package/dist/_types/molecules/DocsSearch/DocsSearch.d.ts +31 -12
  18. package/dist/_types/molecules/FilterBar/FilterBar.d.ts +10 -1
  19. package/dist/_types/molecules/InputField/InputField.d.ts +16 -2
  20. package/dist/_types/molecules/MultiSelectField/MultiSelectField.d.ts +9 -1
  21. package/dist/_types/molecules/PasswordField/PasswordField.d.ts +22 -2
  22. package/dist/_types/molecules/SearchForm/SearchForm.d.ts +18 -6
  23. package/dist/_types/molecules/SelectField/SelectField.d.ts +5 -0
  24. package/dist/_types/molecules/TimeField/TimeField.d.ts +9 -2
  25. package/dist/_types/molecules/_shared/calendarGrid.d.ts +6 -2
  26. package/dist/async-multi-select.js +50 -49
  27. package/dist/async-select.js +89 -88
  28. package/dist/calendar.js +87 -86
  29. package/dist/docs-search.js +38 -37
  30. package/dist/filter-bar.css +1 -1
  31. package/dist/filter-bar.js +19 -17
  32. package/dist/input-field.js +66 -65
  33. package/dist/input-phone.js +39 -38
  34. package/dist/multi-select.js +109 -108
  35. package/dist/number-input.js +61 -60
  36. package/dist/otp-input.js +86 -85
  37. package/dist/password-field.js +46 -45
  38. package/dist/search-form.js +35 -34
  39. package/dist/select.js +75 -71
  40. package/dist/time-select.js +57 -56
  41. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,124 @@ 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
+ ## [43.0.0] — 2026-09-15
11
+
12
+ > **Major.** Cuarta familia al proveedor de textos: fecha y hora. `Calendar`, `DatePicker`
13
+ > y `TimeSelect` dejan de traer castellano puesto, y con ellos se cierran los reenvíos que
14
+ > las olas anteriores dejaron inventariados.
15
+
16
+ ### Fecha y hora leen del proveedor
17
+
18
+ Tres espacios nuevos en `BrandMessages`: **`calendar`** (previousMonth, nextMonth,
19
+ previousYears, nextYears, yearGrid), **`datePicker`** (openCalendar, invalid, calendar y
20
+ las letras de la máscara) y **`timeSelect`** (hours, minutes y sus máscaras). Los campos
21
+ que los envuelven —`DatePickerField`, `DateTimeField`, `TimeField`— no pierden props: sus
22
+ reenvíos quedan como anulación puntual, pero ya no hay que enhebrar nada para traducir,
23
+ porque el proveedor llega por contexto a la pieza que pinta el texto. Se ha retirado
24
+ `SPANISH_MASK_LETTERS`, el último juego de letras castellanas vivo en el código.
25
+
26
+ Mismo orden que en las tres olas anteriores —**prop → proveedor → error**— y misma
27
+ naturaleza: incompatible **en tiempo de compilación**. El texto que no se pinta no se
28
+ exige: un `Calendar` sin navegación no pide las flechas, un `DatePicker` con fecha válida no
29
+ pide el aviso, un `TimeSelect` con hora no pide las máscaras.
30
+
31
+ ### Una máscara tiene dos mitades
32
+
33
+ `dd/mm/aaaa` no es una cosa, son dos decididas en sitios distintos. **El orden y el
34
+ separador son formato**: salen del `locale` vía `Intl.DateTimeFormat`, y una aplicación en
35
+ inglés que enseñe fechas españolas las quiere en orden español. **Las letras son idioma**:
36
+ `aaaa` es «año», `yyyy` es *year*, `jjjj` es *Jahr* — la abreviatura de una palabra, y se
37
+ traduce. Por eso el catálogo guarda las tres letras sueltas y nunca la máscara montada: la
38
+ arma el componente poniendo las letras en el orden del `locale`.
39
+
40
+ Lo mismo con `HH/MM`: parecen notación técnica, pero son la inicial de una palabra, y en
41
+ alemán son `SS/MM`. Lo que no se traduce es el dibujo —dos cifras, la hora antes que el
42
+ minuto, dos puntos en medio—.
43
+
44
+ La regla, en una frase, escrita en `Foundations › Internacionalización`: **si cambia al
45
+ cambiar de idioma es cromo; si cambia al cambiar de país es formato.**
46
+
47
+ ### Lo que se queda como prop
48
+
49
+ `label`, `helperText` y `errorMessage` de los tres campos; `gridLabel` del `Calendar`, que
50
+ nombra a ESE calendario («Fecha de alta») y sin el cual la rejilla toma el título del mes,
51
+ que ya viene del `locale`; `calendarLabel` cuando lo pone un campo, porque es su etiqueta;
52
+ y el `placeholder` del `DatePicker` cuando dice algo del campo («Desde»), que sustituye a la
53
+ máscara entera.
54
+
55
+ ### Pendiente, anotado
56
+
57
+ `CalendarPlanner` y `CalendarRoster` siguen con «Mes anterior» y «Mes siguiente» cableados:
58
+ es el mismo texto que `Calendar` y pueden leer de `calendar.*` tal cual. Y `TimeSelect` es
59
+ de 24 horas por construcción; el reloj de 12 con AM/PM es cosa del `locale`, no del idioma,
60
+ y hoy una aplicación en inglés americano ve las horas en formato militar. No es un texto,
61
+ y queda por decidir.
62
+
63
+ ## [42.0.0] — 2026-09-15
64
+
65
+ > **Major.** Tercera familia al proveedor de textos: los átomos y moléculas de formulario
66
+ > dejan de traer castellano puesto. Y la barra de filtros apila de verdad por debajo del
67
+ > punto de ruptura, no solo cuando la aritmética de la rejilla lo permitía.
68
+
69
+ ### Los formularios leen del proveedor
70
+
71
+ Doce componentes, doce espacios nuevos en `BrandMessages`: `inputField`, `passwordField`,
72
+ `select`, `multiSelect`, `numberInput`, `otpInput`, `inputPhone`, `asyncSelect`,
73
+ `asyncMultiSelect`, `searchForm`, `docsSearch` y `filterBar`. Los `*Field` que los envuelven
74
+ son reenvío puro y no tienen espacio propio: pasan la prop al átomo, y el átomo lee del
75
+ catálogo. Mismo orden que en las dos olas anteriores —**prop → proveedor → error**— y
76
+ mismo carácter: incompatible **en tiempo de compilación**, que es lo que se busca.
77
+
78
+ Esta familia iba ahora, y no más tarde, por un reenvío: `DataTable` pasaba
79
+ `searchClearLabel` a un `InputField` que seguía con su «Borrar» cableado, así que las
80
+ tablas no quedaban limpias del todo en una aplicación francesa hasta que cayera el átomo.
81
+ Ese reenvío está cerrado. Y de paso se ha visto lo que tapaba: `removeLabel`,
82
+ `decrementLabel`, `incrementLabel`, `digitLabel`, `countryLabel` y `loadingLabel` **no se
83
+ pasaban en ningún sitio de la suite** — esos controles salían en castellano dentro de las
84
+ aplicaciones en francés, alemán, neerlandés y portugués, y nadie lo había parcheado porque
85
+ nadie lo veía. Con el proveedor se corrigen sin tocar un solo punto de uso.
86
+
87
+ ### Lo que se queda como prop, y las dudosas
88
+
89
+ `label`, `helperText`, `errorMessage` y el `placeholder` que dice algo del campo son
90
+ **contenido de ese campo** y siguen siendo props; ninguno traía castellano que retirar. Las
91
+ que hubo que decidir mirando **el valor y no el nombre**: `Select.placeholder`
92
+ («Seleccionar…») es cromo, y la prop gana cuando el marcador dice algo del campo;
93
+ `SearchForm.label` («Buscar») es cromo, porque el buscador de sitio se llama igual en toda
94
+ la suite; `AsyncSelect.emptyMessage` («Sin resultados») es cromo, con la prop intacta para
95
+ un vacío propio. `DocsSearch.clearLabel` no tiene clave: el aspa es la del `InputField` de
96
+ debajo, y darle clave obligaría a traducir «Borrar» dos veces y a que coincidieran.
97
+ `InputPhone.internationalLabel` conserva su «🌐»: un glifo no se traduce.
98
+
99
+ El sistema **no tiene marca de obligatorio ni de «opcional»** — ni `Label` ni ningún
100
+ `*Field` la emiten. No había nada que migrar ahí; si algún día se quiere, es una pieza
101
+ nueva.
102
+
103
+ ### `FilterBar`: una columna por debajo de `md`, de verdad
104
+
105
+ Su documentación prometía una columna por debajo del punto de ruptura y sus acciones sí
106
+ conmutaban ahí, pero los filtros se dejaban a la aritmética de la rejilla
107
+ (`minmax(min(192px, 100%), 1fr)`), que solo apila por fuerza por debajo de unos 400px.
108
+ Medido en Chromium: a 320 y 390px, una columna; **a 430, 600, 700 y 760px, dos o tres
109
+ columnas y un selector a media línea**. Es justo lo que se veía en un teléfono apaisado.
110
+ Ahora la base es `1fr` y la rejilla `auto-fill` vive dentro del `@media (min-width: 768px)`
111
+ que ya existía.
112
+
113
+ El test que debía haberlo cogido medía la caja equivocada: comprobaba que la **celda**
114
+ llenaba la línea —y lo hacía siempre—, no el **control** de dentro, que era lo que se
115
+ quedaba corto, y solo en una franja que ningún preset de ventana de Storybook toca. El test
116
+ nuevo mide el control y mide a 600px.
117
+
118
+ `FilterBar` pasa a ser componente cliente: leer del contexto lo exige.
119
+
120
+ ### Un solo fixture inglés para las historias
121
+
122
+ Las historias «Textos desde el proveedor (otro idioma)» de esta ola y de las dos anteriores
123
+ importan un único `.storybook/brandMessagesFixtureEn.ts`, hermano del castellano, en vez de
124
+ repetir el literal en cada una. El test que vigila que ningún fixture se publica ahora
125
+ enumera lo que hay en `.storybook/`, así que el de la próxima ola queda cubierto por
126
+ existir.
127
+
10
128
  ## [41.1.0] — 2026-09-15
11
129
 
12
130
  > **Minor.** El interruptor de la barra de filtros se centra con el campo que tiene al
@@ -1,16 +1,13 @@
1
1
  import '../datepicker.css';
2
- import { Icon as e } from "../icon.js";
3
- import { Input as t } from "../input.js";
4
- import { Popover as n } from "../popover.js";
5
- import { Calendar as r } from "../calendar.js";
6
- import { forwardRef as i, useCallback as a, useId as o, useMemo as s, useRef as c, useState as l } from "react";
7
- import { jsx as u, jsxs as d } from "react/jsx-runtime";
2
+ import { n as e } from "./brandmessagescontext.js";
3
+ import { Icon as t } from "../icon.js";
4
+ import { Input as n } from "../input.js";
5
+ import { Popover as r } from "../popover.js";
6
+ import { Calendar as i } from "../calendar.js";
7
+ import { forwardRef as a, useCallback as o, useId as s, useMemo as c, useRef as l, useState as u } from "react";
8
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
8
9
  //#region src/stories/molecules/DatePicker/dateMask.ts
9
- var f = {
10
- day: "dd",
11
- month: "mm",
12
- year: "aaaa"
13
- }, p = /[‎‏؜]/g, m = new Date(2026, 8, 25);
10
+ var p = /[‎‏؜]/g, m = new Date(2026, 8, 25);
14
11
  function h(e, t) {
15
12
  return String(e).padStart(t, "0");
16
13
  }
@@ -60,110 +57,110 @@ function g(e) {
60
57
  function _(e) {
61
58
  return `${String(e.getFullYear()).padStart(4, "0")}-${String(e.getMonth() + 1).padStart(2, "0")}-${String(e.getDate()).padStart(2, "0")}`;
62
59
  }
63
- var v = i(function({ value: i, onChange: p, placeholder: m, maskLetters: h = f, invalidMessage: v = "Escribe una fecha completa, con el día, el mes y el año.", openCalendarLabel: y = "Abrir calendario", minDate: b, maxDate: ee, disabledDates: x, size: S = "md", disabled: C, readOnly: w, error: T = !1, locale: E = "es-ES", id: D, name: O, describedBy: k, "aria-describedby": te, "aria-label": ne, calendarLabel: A = "Calendario", previousMonthLabel: re, nextMonthLabel: j, previousYearsLabel: M, nextYearsLabel: N, yearGridLabel: P, gridLabel: F, onBlur: I, className: L }, R) {
64
- let [z, B] = l(!1), V = s(() => g(E), [E]), H = i instanceof Date ? V.format(i) : "", [U, W] = l(H), [G, K] = l(H);
65
- H !== G && (K(H), W(H));
66
- let q = c(null), J = a((e) => {
67
- q.current = e, typeof R == "function" ? R(e) : R && (R.current = e);
68
- }, [R]), Y = U.trim(), X = Y ? V.parse(U) : null, Z = Y !== "" && !X, ie = T || Z, Q = `${o()}-date-picker-invalid`, ae = [k ?? te, Z ? Q : void 0].filter(Boolean).join(" ") || void 0, oe = a((e) => {
69
- (w || C) && e || B(e);
70
- }, [C, w]), $ = a((e) => {
60
+ var v = a(function({ value: a, onChange: p, placeholder: m, maskLetters: h, invalidMessage: v, openCalendarLabel: y, minDate: b, maxDate: x, disabledDates: S, size: C = "md", disabled: w, readOnly: T, error: E = !1, locale: D = "es-ES", id: O, name: k, describedBy: ee, "aria-describedby": te, "aria-label": ne, calendarLabel: re, previousMonthLabel: ie, nextMonthLabel: ae, previousYearsLabel: A, nextYearsLabel: j, yearGridLabel: M, gridLabel: N, onBlur: P, className: F }, I) {
61
+ let L = e("datePicker"), [R, z] = u(!1), B = c(() => g(D), [D]), V = a instanceof Date ? B.format(a) : "", [H, U] = u(V), [W, G] = u(V);
62
+ V !== W && (G(V), U(V));
63
+ let K = l(null), q = o((e) => {
64
+ K.current = e, typeof I == "function" ? I(e) : I && (I.current = e);
65
+ }, [I]), J = H.trim(), Y = J ? B.parse(H) : null, X = J !== "" && !Y, oe = E || X, Z = `${s()}-date-picker-invalid`, se = [ee ?? te, X ? Z : void 0].filter(Boolean).join(" ") || void 0, ce = o((e) => {
66
+ (T || w) && e || z(e);
67
+ }, [w, T]), le = o((e) => {
71
68
  let t = e.target.value;
72
- if (W(t), t.trim() === "") {
69
+ if (U(t), t.trim() === "") {
73
70
  p?.(null);
74
71
  return;
75
72
  }
76
- let n = V.parse(t);
73
+ let n = B.parse(t);
77
74
  n && p?.(n);
78
- }, [V, p]), se = a((e) => {
79
- if (e.key === "ArrowDown" && !w && !C) {
80
- e.preventDefault(), B(!0);
75
+ }, [B, p]), ue = o((e) => {
76
+ if (e.key === "ArrowDown" && !T && !w) {
77
+ e.preventDefault(), z(!0);
81
78
  return;
82
79
  }
83
- e.key === "Escape" && z && (e.preventDefault(), B(!1));
80
+ e.key === "Escape" && R && (e.preventDefault(), z(!1));
84
81
  }, [
85
- C,
86
- z,
87
- w
88
- ]), ce = a((e) => {
89
- W(V.format(e)), p?.(e), B(!1), requestAnimationFrame(() => q.current?.focus());
90
- }, [V, p]), le = /* @__PURE__ */ u("button", {
82
+ w,
83
+ R,
84
+ T
85
+ ]), Q = o((e) => {
86
+ U(B.format(e)), p?.(e), z(!1), requestAnimationFrame(() => K.current?.focus());
87
+ }, [B, p]), de = /* @__PURE__ */ d("button", {
91
88
  type: "button",
92
89
  className: "date-picker__button",
93
- "aria-label": y,
90
+ "aria-label": L("openCalendar", y),
94
91
  "aria-haspopup": "dialog",
95
- "aria-expanded": z,
96
- disabled: C,
97
- tabIndex: w ? -1 : void 0,
98
- children: /* @__PURE__ */ u(e, {
92
+ "aria-expanded": R,
93
+ disabled: w,
94
+ tabIndex: T ? -1 : void 0,
95
+ children: /* @__PURE__ */ d(t, {
99
96
  name: "calendar",
100
97
  size: "sm",
101
98
  className: "date-picker__glyph"
102
99
  })
103
- });
104
- return /* @__PURE__ */ d("div", {
100
+ }), $ = L("calendar", re);
101
+ return /* @__PURE__ */ f("div", {
105
102
  className: [
106
103
  "date-picker",
107
- S === "md" ? "" : `date-picker--${S}`,
108
- L ?? ""
104
+ C === "md" ? "" : `date-picker--${C}`,
105
+ F ?? ""
109
106
  ].filter(Boolean).join(" "),
110
107
  children: [
111
- O && /* @__PURE__ */ u("input", {
108
+ k && /* @__PURE__ */ d("input", {
112
109
  type: "hidden",
113
- name: O,
114
- value: i instanceof Date ? _(i) : ""
110
+ name: k,
111
+ value: a instanceof Date ? _(a) : ""
115
112
  }),
116
- /* @__PURE__ */ d("div", {
113
+ /* @__PURE__ */ f("div", {
117
114
  className: "date-picker__control",
118
- children: [/* @__PURE__ */ u(t, {
119
- ref: J,
120
- id: D,
115
+ children: [/* @__PURE__ */ d(n, {
116
+ ref: q,
117
+ id: O,
121
118
  className: "date-picker__input",
122
119
  type: "text",
123
120
  inputMode: "numeric",
124
121
  autoComplete: "off",
125
- size: S,
126
- error: ie,
127
- value: U,
128
- placeholder: m ?? V.mask(h),
129
- disabled: C,
130
- readOnly: w,
122
+ size: C,
123
+ error: oe,
124
+ value: H,
125
+ placeholder: m ?? B.mask(L("maskLetters", h)),
126
+ disabled: w,
127
+ readOnly: T,
131
128
  "aria-label": ne,
132
- "aria-describedby": ae,
133
- onChange: $,
134
- onKeyDown: se,
135
- onBlur: I
136
- }), /* @__PURE__ */ u(n, {
137
- trigger: le,
138
- label: A,
139
- open: z,
140
- onOpenChange: oe,
129
+ "aria-describedby": se,
130
+ onChange: le,
131
+ onKeyDown: ue,
132
+ onBlur: P
133
+ }), /* @__PURE__ */ d(r, {
134
+ trigger: de,
135
+ label: $,
136
+ open: R,
137
+ onOpenChange: ce,
141
138
  side: "bottom",
142
139
  align: "end",
143
140
  sideOffset: -1,
144
141
  className: "date-picker__popover",
145
- children: /* @__PURE__ */ u(r, {
146
- value: X ?? i ?? null,
147
- onChange: ce,
148
- gridLabel: F ?? A,
149
- previousMonthLabel: re,
150
- nextMonthLabel: j,
151
- previousYearsLabel: M,
152
- nextYearsLabel: N,
153
- yearGridLabel: P,
142
+ children: /* @__PURE__ */ d(i, {
143
+ value: Y ?? a ?? null,
144
+ onChange: Q,
145
+ gridLabel: N ?? $,
146
+ previousMonthLabel: ie,
147
+ nextMonthLabel: ae,
148
+ previousYearsLabel: A,
149
+ nextYearsLabel: j,
150
+ yearGridLabel: M,
154
151
  minDate: b,
155
- maxDate: ee,
156
- disabledDates: x,
157
- locale: E,
158
- size: S
152
+ maxDate: x,
153
+ disabledDates: S,
154
+ locale: D,
155
+ size: C
159
156
  })
160
157
  })]
161
158
  }),
162
- Z && /* @__PURE__ */ u("span", {
163
- id: Q,
159
+ X && /* @__PURE__ */ d("span", {
160
+ id: Z,
164
161
  className: "date-picker__message",
165
162
  role: "alert",
166
- children: v
163
+ children: L("invalid", v)
167
164
  })
168
165
  ]
169
166
  });
@@ -4,6 +4,21 @@ export interface AsyncMultiSelectOption {
4
4
  value: string;
5
5
  label: string;
6
6
  }
7
+ /**
8
+ * Los textos que el control emite por su cuenta. Todos son cromo: dicen lo
9
+ * mismo en toda la suite y no hablan de lo que se busca. Lo que sí es de esta
10
+ * pantalla —las opciones que devuelve `onSearch`— no pasa por aquí.
11
+ */
12
+ export interface AsyncMultiSelectMessages {
13
+ /** Pista dentro del campo de búsqueda. */
14
+ placeholder: string;
15
+ /** Aviso cuando la búsqueda no devuelve opciones. */
16
+ empty: string;
17
+ /** Nombre accesible del spinner mientras se busca. */
18
+ loading: string;
19
+ /** Nombre accesible del aspa de cada ficha, con la etiqueta de su opción. */
20
+ remove: (label: string) => string;
21
+ }
7
22
  export interface AsyncMultiSelectProps {
8
23
  onSearch: (query: string) => Promise<AsyncMultiSelectOption[]>;
9
24
  value?: string[];
@@ -21,6 +36,10 @@ export interface AsyncMultiSelectProps {
21
36
  * componente las recuerda—, solo para los valores de `defaultValue`.
22
37
  */
23
38
  selectedOptions?: AsyncMultiSelectOption[];
39
+ /**
40
+ * Pista dentro del campo de búsqueda. **Sin default**: sale de la clave
41
+ * `placeholder` del espacio de este control.
42
+ */
24
43
  placeholder?: string;
25
44
  disabled?: boolean;
26
45
  readOnly?: boolean;
@@ -53,16 +72,20 @@ export interface AsyncMultiSelectProps {
53
72
  */
54
73
  'aria-label'?: string;
55
74
  'aria-describedby'?: string;
56
- /** aria-label del botón que quita un valor. Default: `Quitar ${etiqueta}` (castellano). */
75
+ /**
76
+ * aria-label del botón que quita un valor. **Sin default**: sale de
77
+ * `asyncMultiSelect.remove`.
78
+ */
57
79
  removeLabel?: (label: string) => string;
58
80
  /**
59
- * Texto mostrado cuando la búsqueda no devuelve opciones. Default: "Sin resultados"
60
- * (castellano). Es texto **visible**: una app multiidioma debe pasarlo traducido.
81
+ * Texto mostrado cuando la búsqueda no devuelve opciones. **Sin default**:
82
+ * sale de la clave `empty` del espacio de este control en el
83
+ * `BrandMessagesProvider`.
61
84
  */
62
85
  emptyMessage?: string;
63
86
  /**
64
- * Etiqueta accesible del spinner mientras se busca. Default: "Buscando…" (castellano).
65
- * Una app multiidioma debe pasarla traducida.
87
+ * Etiqueta accesible del spinner mientras se busca. **Sin default**: sale de
88
+ * la clave `loading` del espacio de este control.
66
89
  */
67
90
  loadingLabel?: string;
68
91
  /**
@@ -4,12 +4,31 @@ export interface AsyncSelectOption {
4
4
  value: string;
5
5
  label: string;
6
6
  }
7
+ /**
8
+ * Los textos que el control emite por su cuenta. Todos son cromo: dicen lo
9
+ * mismo en toda la suite y no hablan de lo que se busca. Lo que sí es de esta
10
+ * pantalla —las opciones que devuelve `onSearch`— no pasa por aquí.
11
+ */
12
+ export interface AsyncSelectMessages {
13
+ /** Pista dentro del campo de búsqueda. */
14
+ placeholder: string;
15
+ /** Aviso cuando la búsqueda no devuelve opciones. */
16
+ empty: string;
17
+ /** Nombre accesible del spinner mientras se busca. */
18
+ loading: string;
19
+ /** Nombre accesible del botón que vacía la selección. */
20
+ clear: string;
21
+ }
7
22
  export interface AsyncSelectProps {
8
23
  onSearch: (query: string) => Promise<AsyncSelectOption[]>;
9
24
  value?: string | null;
10
25
  onValueChange?: (value: string | null, option: AsyncSelectOption | null) => void;
11
26
  /** Label of the currently selected option — required when `value` is set so the component can display it */
12
27
  selectedOption?: AsyncSelectOption | null;
28
+ /**
29
+ * Pista dentro del campo de búsqueda. **Sin default**: sale de la clave
30
+ * `placeholder` del espacio de este control.
31
+ */
13
32
  placeholder?: string;
14
33
  disabled?: boolean;
15
34
  readOnly?: boolean;
@@ -43,18 +62,19 @@ export interface AsyncSelectProps {
43
62
  'aria-label'?: string;
44
63
  'aria-describedby'?: string;
45
64
  /**
46
- * Texto mostrado cuando la búsqueda no devuelve opciones. Default: "Sin resultados"
47
- * (castellano). Es texto **visible**: una app multiidioma debe pasarlo traducido.
65
+ * Texto mostrado cuando la búsqueda no devuelve opciones. **Sin default**:
66
+ * sale de la clave `empty` del espacio de este control en el
67
+ * `BrandMessagesProvider`.
48
68
  */
49
69
  emptyMessage?: string;
50
70
  /**
51
- * Etiqueta accesible del spinner mientras se busca. Default: "Buscando…" (castellano).
52
- * Una app multiidioma debe pasarla traducida.
71
+ * Etiqueta accesible del spinner mientras se busca. **Sin default**: sale de
72
+ * la clave `loading` del espacio de este control.
53
73
  */
54
74
  loadingLabel?: string;
55
75
  /**
56
- * aria-label del botón de limpiar selección. Default: "Limpiar selección" (castellano).
57
- * Una app multiidioma debe pasarla traducida.
76
+ * aria-label del botón de limpiar selección. **Sin default**: sale de
77
+ * `asyncSelect.clear`.
58
78
  */
59
79
  clearLabel?: string;
60
80
  /**
@@ -1,6 +1,15 @@
1
1
  import { Select as BaseSelect } from '@base-ui/react/select';
2
2
  import type { Country } from 'react-phone-number-input';
3
3
  import './InputPhone.css';
4
+ /**
5
+ * El único texto que el campo emite por su cuenta: el nombre accesible del
6
+ * selector de país. Los **nombres de los países** no están aquí: los resuelve
7
+ * `Intl` desde el `locale`, como los meses y las fechas.
8
+ */
9
+ export interface InputPhoneMessages {
10
+ /** Nombre accesible del selector de prefijo/país. */
11
+ country: string;
12
+ }
4
13
  export interface InputPhoneProps {
5
14
  value?: string;
6
15
  defaultCountry?: Country;
@@ -24,14 +33,16 @@ export interface InputPhoneProps {
24
33
  onBlur?: React.FocusEventHandler<HTMLInputElement>;
25
34
  onFocus?: React.FocusEventHandler<HTMLInputElement>;
26
35
  /**
27
- * aria-label del selector de país. Default: "País" (castellano).
28
- * Una app multiidioma debe pasarla traducida.
36
+ * aria-label del selector de país. **Sin default**: sale de
37
+ * `inputPhone.country` del `BrandMessagesProvider`.
29
38
  */
30
39
  countryLabel?: string;
31
40
  /**
32
41
  * Lo que enseña el selector cuando no hay país elegido (número en formato
33
- * internacional). Default: "🌐". Es contenido visible: una app que no quiera
34
- * el emoji pasa aquí su propio texto o glifo.
42
+ * internacional). Default: "🌐". **No pasa por el catálogo de textos a
43
+ * propósito**: un glifo no se traduce, dice lo mismo en los seis idiomas y
44
+ * meterlo en el catálogo obligaría a repetirlo seis veces. Una app que no
45
+ * quiera el emoji pasa aquí su propio texto o glifo.
35
46
  */
36
47
  internationalLabel?: string;
37
48
  /**
@@ -1,5 +1,17 @@
1
1
  import { Popover as BasePopover } from '@base-ui/react/popover';
2
2
  import './MultiSelect.css';
3
+ /**
4
+ * Los dos textos que el control emite por su cuenta: el marcador de sitio sin
5
+ * nada elegido y el nombre del aspa de cada ficha. Los dos son cromo — dicen
6
+ * lo mismo en toda la suite, y el segundo solo interpola la etiqueta de la
7
+ * opción, que es un dato.
8
+ */
9
+ export interface MultiSelectMessages {
10
+ /** Marcador de sitio de la caja sin valores elegidos. */
11
+ placeholder: string;
12
+ /** Nombre accesible del aspa de una ficha, con la etiqueta de su opción. */
13
+ remove: (label: string) => string;
14
+ }
3
15
  export interface MultiSelectOption {
4
16
  value: string;
5
17
  label: string;
@@ -9,6 +21,10 @@ export interface MultiSelectProps {
9
21
  options: MultiSelectOption[];
10
22
  value?: string[];
11
23
  defaultValue?: string[];
24
+ /**
25
+ * Marcador de sitio sin valores elegidos. **Sin default**: sale de
26
+ * `multiSelect.placeholder` del `BrandMessagesProvider`.
27
+ */
12
28
  placeholder?: string;
13
29
  disabled?: boolean;
14
30
  readOnly?: boolean;
@@ -32,7 +48,10 @@ export interface MultiSelectProps {
32
48
  'aria-labelledby'?: string;
33
49
  /** Ids de ayuda/error que describen el control (lo pone el campo). */
34
50
  'aria-describedby'?: string;
35
- /** aria-label del botón que quita un valor. Default: `Quitar ${etiqueta}` (castellano). */
51
+ /**
52
+ * aria-label del botón que quita un valor. **Sin default**: sale de
53
+ * `multiSelect.remove` del `BrandMessagesProvider`.
54
+ */
36
55
  removeLabel?: (label: string) => string;
37
56
  /**
38
57
  * Nodo DOM donde montar el portal del dropdown (reenviado a Base UI
@@ -1,5 +1,15 @@
1
1
  import { type ComponentPropsWithoutRef } from 'react';
2
2
  import './NumberInput.css';
3
+ /**
4
+ * Los dos textos que el control emite por su cuenta: los nombres accesibles de
5
+ * sus dos botones. Cromo puro — no dicen nada de qué se cuenta.
6
+ */
7
+ export interface NumberInputMessages {
8
+ /** Nombre accesible del botón que resta un paso. */
9
+ decrement: string;
10
+ /** Nombre accesible del botón que suma un paso. */
11
+ increment: string;
12
+ }
3
13
  export interface NumberInputProps extends Omit<ComponentPropsWithoutRef<'input'>, 'size' | 'type' | 'value' | 'defaultValue' | 'onChange'> {
4
14
  value?: number;
5
15
  defaultValue?: number;
@@ -20,13 +30,13 @@ export interface NumberInputProps extends Omit<ComponentPropsWithoutRef<'input'>
20
30
  /** Se añade DESPUÉS de las clases propias del componente (el consumidor añade, no sustituye). */
21
31
  className?: string;
22
32
  /**
23
- * aria-label del botón de decremento. Default: "Decrementar" (castellano).
24
- * Una app multiidioma debe pasarla traducida.
33
+ * aria-label del botón de decremento. **Sin default**: sale de
34
+ * `numberInput.decrement` del `BrandMessagesProvider`.
25
35
  */
26
36
  decrementLabel?: string;
27
37
  /**
28
- * aria-label del botón de incremento. Default: "Incrementar" (castellano).
29
- * Una app multiidioma debe pasarla traducida.
38
+ * aria-label del botón de incremento. **Sin default**: sale de
39
+ * `numberInput.increment` del `BrandMessagesProvider`.
30
40
  */
31
41
  incrementLabel?: string;
32
42
  onChange?: (value: number) => void;
@@ -1,4 +1,15 @@
1
1
  import './OtpInput.css';
2
+ /**
3
+ * Los textos que el control emite por su cuenta: el nombre del grupo cuando va
4
+ * suelto y el de cada celda. Cromo — el segundo solo interpola la posición de
5
+ * la celda, que es una cifra.
6
+ */
7
+ export interface OtpInputMessages {
8
+ /** Nombre accesible del grupo de celdas cuando el control va suelto. */
9
+ group: string;
10
+ /** Nombre accesible de una celda, con su posición y el total. */
11
+ digit: (index: number, length: number) => string;
12
+ }
2
13
  export interface OtpInputProps {
3
14
  /** Número de celdas a renderizar. Requerido. */
4
15
  length: number;
@@ -22,8 +33,8 @@ export interface OtpInputProps {
22
33
  'aria-labelledby'?: string;
23
34
  /**
24
35
  * Nombre accesible del grupo cuando no se pasa `aria-label` ni
25
- * `aria-labelledby` — el caso del control suelto. Default: "Código de
26
- * verificación" (castellano). Una app multiidioma debe pasarla traducida.
36
+ * `aria-labelledby` — el caso del control suelto. **Sin default**: sale de
37
+ * `otpInput.group` del `BrandMessagesProvider`.
27
38
  */
28
39
  groupLabel?: string;
29
40
  id?: string;
@@ -39,8 +50,8 @@ export interface OtpInputProps {
39
50
  /** Se añade DESPUÉS de las clases propias del componente. */
40
51
  className?: string;
41
52
  /**
42
- * Etiqueta accesible de cada celda. Default: `Dígito N de M` (castellano).
43
- * Una app multiidioma debe pasarla traducida.
53
+ * Etiqueta accesible de cada celda. **Sin default**: sale de
54
+ * `otpInput.digit` del `BrandMessagesProvider`.
44
55
  */
45
56
  digitLabel?: (index: number, length: number) => string;
46
57
  }
@@ -2,6 +2,18 @@ import type { ReactNode } from 'react';
2
2
  import { Select as BaseSelect } from '@base-ui/react/select';
3
3
  import type { SeparatorProps as BaseSeparatorProps } from '@base-ui/react/separator';
4
4
  import './Select.css';
5
+ /**
6
+ * El único texto que el desplegable emite por su cuenta: el marcador de sitio
7
+ * genérico del disparador, el que dice «aquí no has elegido nada todavía».
8
+ *
9
+ * Es cromo, no contenido: cuando el marcador dice algo de ESTE campo («Elige
10
+ * un país») lo escribe quien monta el campo y viaja por la prop `placeholder`,
11
+ * que sigue ganando. Las **opciones** tampoco están aquí: son datos.
12
+ */
13
+ export interface SelectMessages {
14
+ /** Marcador de sitio del disparador sin valor elegido. */
15
+ placeholder: string;
16
+ }
5
17
  export interface SelectOption {
6
18
  value: string;
7
19
  label: string;
@@ -34,7 +46,12 @@ export interface SelectProps {
34
46
  options: SelectOptionOrGroup[];
35
47
  value?: string;
36
48
  defaultValue?: string;
37
- /** Placeholder del trigger. Default: "Seleccionar…" (en la API compuesta lo pone cada consumidor vía `Select.Value`). */
49
+ /**
50
+ * Placeholder del trigger. **Sin default**: sale de `select.placeholder` del
51
+ * `BrandMessagesProvider`, y esta prop es la anulación puntual cuando el
52
+ * marcador dice algo de este campo concreto. En la API compuesta lo pone cada
53
+ * consumidor vía `Select.Value`.
54
+ */
38
55
  placeholder?: string;
39
56
  disabled?: boolean;
40
57
  readOnly?: boolean;