@studiolxd/brand 49.18.0 → 49.19.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,135 @@ 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
+ ## [49.19.0] — 2026-10-04
11
+
12
+ > **Minor.** Primera versión con **Brand nativo**: tokens, fuentes y componentes para SwiftUI (iOS 17 y macOS 14) y
13
+ > Jetpack Compose (Android), fuera del paquete npm. En React, opciones de fila en `List`/`ListItem`
14
+ > (`showSeparators`, `secondary`, `trailing`, `leading`), `NumberInput` sin valor y el aviso neutro de `Toast` en
15
+ > oscuro. Todo opcional: sin las props nuevas el HTML no cambia.
16
+
17
+ ### React — huecos de la primera app nativa (minor)
18
+
19
+ Todo opcional: sin las props nuevas el HTML es idéntico al anterior (pruebas con `toMatchInlineSnapshot` generadas
20
+ contra el código de antes, en `InputField.test.tsx`, `NumberInputField.test.tsx`, `PasswordField.test.tsx` y
21
+ `List.test.tsx`).
22
+
23
+ - **`ListItem` `leading`** (`ReactNode`): accesorio al principio de la fila (icono, avatar), pareja de `trailing`.
24
+ Clase nueva `list__item-leading`; story «Filas con accesorio inicial».
25
+ - **`NumberInput` / `NumberInputField` sin valor**: `value` y `defaultValue` admiten `null` (campo vacío), y la prop
26
+ nueva `onEmpty` hace que vaciar el texto sea un valor (sin ella, el campo se comporta como siempre y recupera el
27
+ último número al salir). Desde vacío, − y + cuentan como si el campo valiera 0 y se ajustan a `min`/`max`.
28
+ Story «Sin valor».
29
+ - **Foco desde fuera**: el `ref` ya llegaba al `<input>` en `InputField` (también en búsqueda), `NumberInputField` y
30
+ `PasswordField` (y al botón en `SelectField`); ahora lo fija una prueba y lo documenta `InputField.mdx`.
31
+ - **Documentación de `Button`**: la nota de `destructive` decía que valía para `primary` y `ghost` y mencionaba una
32
+ variante `accent` que ya no existe; en el código solo `outline` y `text` tienen estilo destructivo. Corregida la
33
+ nota (y `destructiveIntent` → `destructive`); el comportamiento no cambia.
34
+
35
+ ### Nativo — huecos de la primera app nativa (minor)
36
+
37
+ - **Generador de Kotlin**: los tamaños de letra de componente cuyo nombre no termina en `font-size`
38
+ (`empty-state.title-font-size-sm`, `description-font-size-sm`) salían como `Dp`; ahora todo token con `font-size`
39
+ como segmento es `TextUnit` (`sp`). `BrandEmptyState` deja de convertir a mano. Grupo `password-field` en
40
+ `NATIVE_COMPONENT_GROUPS`.
41
+ - **`PasswordField` nativo** (`BrandPasswordField`, SwiftUI y Compose): campo con ojo de mostrar u ocultar, etiqueta
42
+ oculta por defecto como en React, textos accesibles como parámetros. Ficha, capturas y parejas. **Compose: `BrandInputField`
43
+ pierde el ojo** (`passwordToggle`, `showPasswordLabel`, `hidePasswordLabel`; breaking menor en Compose): quien lo quiera usa `BrandPasswordField`.
44
+ - **Foco desde fuera** en `InputField`, `NumberInputField` y `PasswordField`: SwiftUI `.brandFocused($focus)` /
45
+ `.brandFocused($focus, equals:)` (un `FocusState` de la app aplicado al texto, no a los botones); Compose
46
+ `focusRequester: FocusRequester?`.
47
+ - **`NumberInputField` sin valor**: `Binding<Double?>` (SwiftUI; `Binding<Double>` sigue compilando) y `value: Double?`
48
+ (Compose; **cambio de firma**: `onValueChange` recibe `Double?`). Vacío = `nil`/`null`, con `placeholder` y
49
+ «Sin valor» (`emptyValueLabel`) para lectores de pantalla; desde vacío − y + parten de 0 y respetan `min`/`max`.
50
+ - **`BrandListItem`**: `leading` en SwiftUI (ya existía en Compose) y `action` (fila pulsable entera) en SwiftUI, pareja de
51
+ `onClick` de Compose, anotado en la ficha como diferencia deliberada.
52
+
53
+ ### Nativo — Jetpack Compose con los mismos componentes que SwiftUI (minor)
54
+
55
+ - **Tokens de componente en Kotlin**: `pnpm build:tokens` genera `BrandComponentTokens.kt` (mismos grupos y reglas que
56
+ `BrandComponentTokens.swift`; los colores y las medidas con par `surface-dark-*` salen como `BrandSchemeValue`, que se
57
+ resuelve con `.current`). El Swift generado no cambia.
58
+ - **Componentes de Compose** (`native/android/brand`): `BrandButton`, `BrandCloseButton`, `BrandHeading`/`BrandParagraph`/
59
+ `BrandText`, `BrandIcon` (77 iconos; `pnpm build:native-icons` genera también `BrandIconData.kt`), `BrandInputField`,
60
+ `BrandNumberInputField`, `BrandSelectField`, `BrandSwitcherField`, `BrandToggleGroup`, `BrandThemeSwitcher`,
61
+ `BrandList`/`BrandListItem` (`showSeparators`, `secondary`, `trailing`), `BrandTag`, `BrandEmptyState`, `BrandSkeleton`,
62
+ `BrandSheet`, `BrandConfirmDialog` y `ToastCenter`/`ToastHost`, con estados, escala de fuente, TalkBack, 48 dp, claro y
63
+ oscuro y «quitar animaciones». Cumplen las fichas de paridad (`ParityTest`), con capturas Paparazzi en claro y oscuro,
64
+ `@Preview` y parejas React ↔ Compose en `native/android/Comparisons/`. Sin dependencias nuevas en producción.
65
+ - Fichas de paridad: líneas «Android: …» en `differences`. `native/README.md` documenta los componentes de Compose.
66
+
67
+ ### React — `List`/`ListItem` (minor) y `Toast` (patch)
68
+
69
+ - **`List` `showSeparators`** (default `false`): línea entre filas con los tokens `separator.*`
70
+ (clase nueva `list--separated`). **`ListItem` `secondary` y `trailing`** (`ReactNode`): línea
71
+ menor atenuada y accesorio al final de la fila. Sin las props nuevas el HTML es exactamente el
72
+ de antes; la estructura de fila (`list__item-row`, `list__item-main`, `list__item-secondary`,
73
+ `list__item-trailing`) solo aparece al usarlas. Tokens nuevos en `text.list.*`
74
+ (`item-gap`, `secondary-*`, `separator-*`, con pares `surface-dark-*`). Prueba que fija el HTML
75
+ anterior en `List.test.tsx`.
76
+ - **Fix `Toast` neutro en oscuro**: el neutro (y `info`/`loading`) llevaba `.surface-dark` en la
77
+ raíz, que volteaba su relleno a blanco incluso sobre página clara y dejaba el aspa en blanco
78
+ sobre blanco. Ahora, como el `Alert`, la raíz solo la lleva `success`/`error`, y el interior
79
+ (contenido y aspa) lleva `.surface-invert` (neutro) o `.surface-light` (`warning`).
80
+
81
+ ### Nativo
82
+
83
+ - `BrandList(showsSeparators:)` pasa a **`showSeparators:`** (mismo nombre que en React; lo nativo
84
+ aún no se había publicado). `BrandListItem` `secondary`/`trailing` y los separadores leen ahora
85
+ los tokens `text.list.*`. Fichas `List.json`/`ListItem.json` actualizadas: ya no son «solo nativo».
86
+
87
+ > Infraestructura nativa (iOS, macOS y Android). **No cambia nada del paquete npm**: lo
88
+ > nativo vive en `native/`, `Package.swift` y `jitpack.yml`, fuera de `package.json#files`.
89
+
90
+ ### Nativo — nuevo (`native/`)
91
+
92
+ - **Tokens nativos con Style Dictionary**: dos plataformas nuevas en `sd.config.mjs`
93
+ (`swift`, `kotlin`) con formatos propios en `sd.formats.mjs`. `pnpm build:tokens`
94
+ genera `native/apple/…/Tokens/BrandTokens.swift` y
95
+ `native/android/…/tokens/BrandTokens.kt`: colores y roles claro/oscuro (`Color` dinámico
96
+ en Swift; `BrandColorRoles.light|dark` + `CompositionLocal` en Compose), espaciado,
97
+ radios, anchos de borde, tallas, opacidad, tipografía, sombras y motion. Los tokens de
98
+ componente quedan para cuando se porte cada componente.
99
+ - **Paquete SwiftPM** `StudiolxdBrand` (`Package.swift` en la raíz; iOS 17, macOS 14,
100
+ Swift 6): `registerFonts()`, `BrandTextStyle` y `Font.brand(_:)`.
101
+ - **Módulo Gradle** `com.github.studiolxd:brand` (`native/android`, minSdk 26, publicable
102
+ por JitPack con `jitpack.yml`): `BrandTheme`, `BrandFontFamily`, `BrandTypography`.
103
+ - **Fuentes**: `pnpm build:native-fonts` descomprime los woff2 variables (Google Sans Flex,
104
+ Google Sans Code, Libre Bodoni; cara `latin`) a TTF para ambas plataformas.
105
+ - **Paridad con React**: fichas por componente (`native/parity/`), validador
106
+ (`pnpm native:parity`, con TypeScript y `ajv`) y pruebas de paridad en Swift y Kotlin.
107
+ - **Capturas**: swift-snapshot-testing (Apple) y Paparazzi (Android), con una captura de
108
+ ejemplo de los roles de color en claro y oscuro.
109
+ - **`release:check`** comprueba que `npm pack` no incluye nada nativo, que los tokens
110
+ nativos están al día y la paridad; con `--with-native` compila y prueba Swift (macOS e
111
+ iOS) y Gradle.
112
+ - Documentación en `native/README.md` y en `CLAUDE.md` § «Nativo (iOS y Android)».
113
+
114
+ ### Nativo — componentes de SwiftUI (iOS 17 y macOS 14)
115
+
116
+ > Los primeros componentes nativos, los que pide Homenize. Mismos nombres de props y casos que React, solo tokens,
117
+ > claro y oscuro con colores dinámicos, tipo dinámico, VoiceOver, zona táctil de 44 pt, puntero y foco de teclado en
118
+ > macOS y «reducir movimiento». Detalle y ejemplos en `native/README.md` § «Componentes de SwiftUI». Compose recibirá los
119
+ > mismos componentes en la tarea siguiente.
120
+
121
+ - **15 componentes** portados, cada uno con su ficha de paridad (`native/parity/components/`), capturas en claro y
122
+ oscuro (iOS y macOS), `#Preview` con todas las variantes y la pareja con su story de Storybook
123
+ (`native/apple/Comparisons/<Componente>/`): `Button` (como `ButtonStyle` + `BrandButton`), `Heading`/`Paragraph`/`Text`,
124
+ `Icon`, `InputField`, `NumberInputField`, `SelectField`, `SwitcherField`, `ToggleGroup`, `ThemeSwitcher`, `List` +
125
+ `ListItem`, `Tag`, `EmptyState`, `Skeleton`, `Sheet` (`.sheet` nativo), `ConfirmDialog` y `Toast`/`Toaster`
126
+ (`ToastCenter` + `toastHost`).
127
+ - **Tokens de componente para Swift**: `pnpm build:tokens` genera también `BrandComponentTokens.swift` (un enum por
128
+ grupo de `NATIVE_COMPONENT_GROUPS`, con los `surface-dark-*` como `Color` dinámico, también los heredados por
129
+ referencia). Una medida con par oscuro sale como `BrandSchemeValue`. El CSS puro (`solid`, `vh`, `min()`…) se omite.
130
+ - **`BrandIcon`**: los mismos trazos que el `Icon` de React (77 iconos, retícula de 24), generados desde `Icon.tsx` con
131
+ `pnpm build:native-icons` (incluido en `build:all`).
132
+ - **Paridad**: las fichas admiten `differences` (lo que se queda a propósito) y `reactOnlyValues` (`block: boolean |
133
+ 'mobile'`), y `native:parity` entiende uniones numéricas (`Heading level: 1 | … | 6`).
134
+ - **Ayudantes**: `brandFont`/`brandLinedFont` (tipo dinámico e interlineado de CSS), `brandHitTarget` (44 pt),
135
+ `BrandControlSize` y `.brandControlSize(_:)`, `assertBrandSnapshots` (capturas de macOS en sRGB),
136
+ `native/apple/scripts/capture-story.mjs` y `pair-comparison.sh` (parejas React ↔ SwiftUI a la misma escala).
137
+ - Los ejemplos del README compilan (`ReadmeExamples.swift`).
138
+
10
139
  ## [49.18.0] — 2026-10-03
11
140
 
12
141
  > **Minor.** `RecoveryCodes` deja pasar el aviso de portapapeles roto, y `react-hook-form`
@@ -1905,6 +1905,13 @@ var e = {
1905
1905
  "--text-list-letter-spacing": "0em",
1906
1906
  "--text-list-gap": "0.5rem",
1907
1907
  "--text-list-padding-inline-start": "2rem",
1908
+ "--text-list-item-gap": "0.75rem",
1909
+ "--text-list-secondary-font-size": "0.875rem",
1910
+ "--text-list-secondary-line-height": "1.65",
1911
+ "--text-list-secondary-color": "#4a4a4a",
1912
+ "--text-list-separator-color": "#111e30",
1913
+ "--text-list-separator-thickness": "1px",
1914
+ "--text-list-separator-spacing": "0.5rem",
1908
1915
  "--textarea-border-radius": "0px",
1909
1916
  "--textarea-border-width": "1px",
1910
1917
  "--textarea-border-style": "solid",
@@ -8,10 +8,30 @@ export interface ListItemProps extends React.ComponentPropsWithoutRef<'li'> {
8
8
  */
9
9
  as?: React.ElementType;
10
10
  children?: React.ReactNode;
11
+ /**
12
+ * Accesorio al principio de la fila: un icono, un avatar. Pareja de
13
+ * `trailing`; no se encoge ni parte línea y se centra con el bloque de texto.
14
+ */
15
+ leading?: React.ReactNode;
16
+ /**
17
+ * Línea menor bajo el contenido, atenuada y un peldaño por debajo del cuerpo
18
+ * (`text.paragraph.small`): la descripción de una fila de datos o de ajustes.
19
+ */
20
+ secondary?: React.ReactNode;
21
+ /**
22
+ * Accesorio al final de la fila, alineado a la derecha: un icono, un valor,
23
+ * un interruptor. No se encoge ni parte línea.
24
+ */
25
+ trailing?: React.ReactNode;
11
26
  }
12
27
  export interface ListProps extends React.ComponentPropsWithoutRef<'ul'> {
13
28
  /** Tipo de lista: con viñetas, numerada o sin decoración. */
14
29
  type?: ListType;
30
+ /**
31
+ * Dibuja una línea entre filas con los tokens `separator.*`, con su aire a
32
+ * cada lado en lugar del `text.list.gap`. Default `false`: sin línea.
33
+ */
34
+ showSeparators?: boolean;
15
35
  children: React.ReactNode;
16
36
  }
17
37
  /**
@@ -11,8 +11,13 @@ export interface NumberInputMessages {
11
11
  increment: string;
12
12
  }
13
13
  export interface NumberInputProps extends Omit<ComponentPropsWithoutRef<'input'>, 'size' | 'type' | 'value' | 'defaultValue' | 'onChange'> {
14
- value?: number;
15
- defaultValue?: number;
14
+ /**
15
+ * Valor controlado. `null` es «sin valor»: el campo se muestra vacío (y el
16
+ * `placeholder` se ve). Sigue siendo controlado; `undefined` es no controlado.
17
+ */
18
+ value?: number | null;
19
+ /** Valor inicial no controlado (default `0`). `null` arranca vacío. */
20
+ defaultValue?: number | null;
16
21
  min?: number;
17
22
  max?: number;
18
23
  step?: number;
@@ -40,6 +45,13 @@ export interface NumberInputProps extends Omit<ComponentPropsWithoutRef<'input'>
40
45
  */
41
46
  incrementLabel?: string;
42
47
  onChange?: (value: number) => void;
48
+ /**
49
+ * Se llama cuando quien teclea deja el campo vacío. Sin ella el campo se
50
+ * comporta como siempre (vaciar no emite nada y al salir recupera el último
51
+ * número); con ella, vaciar **es** un valor: el campo pasa a «sin valor» y
52
+ * esta función avisa de ello.
53
+ */
54
+ onEmpty?: () => void;
43
55
  onBlur?: React.FocusEventHandler<HTMLInputElement>;
44
56
  onFocus?: React.FocusEventHandler<HTMLInputElement>;
45
57
  }
@@ -11,8 +11,9 @@ export interface NumberInputFieldProps extends Omit<ComponentPropsWithoutRef<'in
11
11
  * es la primera de la lista, la etiqueta se oculta sola.
12
12
  */
13
13
  labelHidden?: boolean;
14
- value?: number;
15
- defaultValue?: number;
14
+ /** `null` es «sin valor»: el campo se muestra vacío. Ver `NumberInput`. */
15
+ value?: number | null;
16
+ defaultValue?: number | null;
16
17
  min?: number;
17
18
  max?: number;
18
19
  step?: number;
@@ -27,6 +28,8 @@ export interface NumberInputFieldProps extends Omit<ComponentPropsWithoutRef<'in
27
28
  size?: 'sm' | 'md' | 'lg';
28
29
  /** Recibe el valor ya normalizado, no el evento. */
29
30
  onChange?: (value: number) => void;
31
+ /** Quien teclea dejó el campo vacío; con ella, vaciar es un valor (`null`). Ver `NumberInput`. */
32
+ onEmpty?: () => void;
30
33
  /** Se añade DESPUÉS de las clases propias (el consumidor añade, no sustituye). */
31
34
  className?: string;
32
35
  }