@studiolxd/brand 49.4.0 → 49.6.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 +76 -0
- package/dist/_shared/dropdownitems.js +29 -18
- package/dist/_types/atoms/Icon/Icon.d.ts +7 -1
- package/dist/_types/atoms/Link/Link.d.ts +1 -1
- package/dist/_types/molecules/_shared/dropdownItems.d.ts +18 -1
- package/dist/brand.css +5 -1
- package/dist/button.css +1 -1
- package/dist/icon.css +1 -1
- package/dist/link.js +1 -1
- package/dist/menu.css +1 -1
- package/dist/menu.js +1 -0
- package/dist/user-menu.css +1 -1
- package/dist/user-menu.js +16 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,82 @@ 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.6.0] — 2026-09-18
|
|
11
|
+
|
|
12
|
+
> **Minor.** El icono deja de tener talla propia dentro de un enlace y de un botón: mide el texto
|
|
13
|
+
> que acompaña, y sube y baja con él.
|
|
14
|
+
|
|
15
|
+
### `Icon size="text"` — el glifo que mide lo que el texto
|
|
16
|
+
|
|
17
|
+
Las cinco tallas de icono (8/16/24/48/64) son medidas fijas, y junto a un texto eso se nota: el
|
|
18
|
+
mismo enlace vive en un pie, en un párrafo y en un título, y un glifo de 16px se ve enorme en el
|
|
19
|
+
primero y diminuto en el tercero. La talla nueva, `text`, no es una medida sino una relación — el
|
|
20
|
+
icono vale `1em`, o sea el cuerpo de letra que lo rodea. No trae token ni color: `1em` ya **es** el
|
|
21
|
+
tamaño del texto ambiente, y no hay valor de diseño que elegir. Las cinco tallas fijas se quedan
|
|
22
|
+
exactamente donde estaban; esto solo añade una sexta opción.
|
|
23
|
+
|
|
24
|
+
### El icono de `Link` sigue al texto del enlace
|
|
25
|
+
|
|
26
|
+
El glifo de `icon` estaba clavado en `sm` (16px), de modo que un «← Volver» dentro de un título
|
|
27
|
+
llevaba una flecha de letra pequeña. Ahora usa `size="text"`: mide el texto del enlace, delante
|
|
28
|
+
(`iconPosition="start"`) y detrás (`end`). El aire entre icono y texto sigue siendo
|
|
29
|
+
`--link-icon-gap`, espaciado del sistema, y no escala con la tipografía a propósito: es
|
|
30
|
+
separación, no cuerpo de letra.
|
|
31
|
+
|
|
32
|
+
### El icono de `Button` sigue al cuerpo de letra del control
|
|
33
|
+
|
|
34
|
+
Un icono pasado como hijo de un `Button` ya no necesita `size`: dentro del botón mide `1em`, que es
|
|
35
|
+
el `font-size` que cada talla fija (14, 16 y 20px en `sm`, `md` y `lg`), así que el glifo acompaña
|
|
36
|
+
al control en los tres tamaños y también cuando quien sube la talla es la superficie pública. Lo
|
|
37
|
+
hace una regla acotada en la hoja del botón, sin `data-slot` ni prop nueva.
|
|
38
|
+
|
|
39
|
+
**`iconOnly` queda fuera de esa regla, a propósito.** Ahí el glifo no acompaña a un texto —no hay
|
|
40
|
+
texto— sino que **es** el contenido del botón: su medida la decide la caja cuadrada (32/40/48), no
|
|
41
|
+
la tipografía, y con `1em` un botón de 40px se habría quedado con un glifo de 16 flotando en el
|
|
42
|
+
centro. En un botón de solo icono la talla la sigue poniendo el `Icon`.
|
|
43
|
+
|
|
44
|
+
Cambio visible para quien ya ponía un icono dentro de un botón con texto: ese glifo pasa de su
|
|
45
|
+
talla fija a la del texto (en un botón `lg`, de 16 a 20px). Es el efecto buscado y no cambia el
|
|
46
|
+
marcado ni la API.
|
|
47
|
+
|
|
48
|
+
Stories nuevas para verlo: «El icono sigue al texto» en `Link` (el mismo enlace en tres
|
|
49
|
+
tipografías, y el caso de «Ver en la plataforma de origen» con el icono detrás) y en `Button` (el
|
|
50
|
+
mismo botón en `sm`, `md` y `lg`), cada una con su test de contrato. Documentado en Foundations →
|
|
51
|
+
Iconografía § «El icono que sigue al texto», con remisión desde `Icon`, `Link` y `Button`.
|
|
52
|
+
|
|
53
|
+
## [49.5.0] — 2026-09-18
|
|
54
|
+
|
|
55
|
+
> **Minor.** La cuenta se lee en dos líneas: el ítem de menú acepta una segunda línea bajo la
|
|
56
|
+
> etiqueta, y la cabecera del menú de cuenta enseña la foto de la cuenta activa.
|
|
57
|
+
|
|
58
|
+
### `MenuItem description` — el ítem de dos líneas
|
|
59
|
+
|
|
60
|
+
Un ítem de menú solo sabía decir una cosa: icono y una línea. El selector de varias cuentas del
|
|
61
|
+
shell tenía que apretar ahí nombre y correo —«Ana García · ana@…»— con el avatar haciendo de
|
|
62
|
+
icono, que es justo la información que se lee peor cuando hay tres cuentas parecidas.
|
|
63
|
+
|
|
64
|
+
`description` (opcional, en los ítems `button` y `link`) pinta esa segunda línea debajo de la
|
|
65
|
+
etiqueta, más tenue, y centra el icono —el avatar— contra el bloque de las dos. Cada línea se
|
|
66
|
+
recorta con puntos suspensivos, así que un correo largo no ensancha el panel. Es **aditivo**: sin
|
|
67
|
+
`description` el ítem sale con el marcado de siempre —ni envoltorio de texto ni segunda línea—, y
|
|
68
|
+
los menús de una línea que ya existen no cambian ni un píxel.
|
|
69
|
+
|
|
70
|
+
No hay tokens nuevos. La segunda línea toma la letra y la tinta del rótulo de sección
|
|
71
|
+
(`menu.label-font-size`, `menu.label-color`), que es el papel que ya cumplen en este menú, y bajo
|
|
72
|
+
el puntero o el teclado va con la tinta del ítem resaltado en vez de con la suya, para no quedarse
|
|
73
|
+
gris sobre el relleno de marca. Las clases son `menu__item-icon`, `-text`, `-label` y
|
|
74
|
+
`-description`; el `UserMenu` las tiene con su prefijo y su propia tinta (la del correo de la
|
|
75
|
+
cabecera), para que una lista de cuentas dentro de ese panel se lea igual.
|
|
76
|
+
|
|
77
|
+
### `UserMenu` — la cabecera enseña a quién pertenece la cuenta
|
|
78
|
+
|
|
79
|
+
La cabecera del panel decía el nombre y el correo, pero no enseñaba la foto: el avatar solo
|
|
80
|
+
estaba en el disparador, y al abrir el menú desaparecía justo cuando hace falta confirmar en qué
|
|
81
|
+
cuenta estás. Ahora la cabecera es una fila: el `Avatar` de siempre a la izquierda (foto o, sin
|
|
82
|
+
`avatarUrl`, las iniciales) y, al lado, nombre y correo en columna. Decorativo (`alt=""`), porque
|
|
83
|
+
el nombre va escrito al lado. El aire entre foto y texto es el mismo que en el disparador
|
|
84
|
+
(`user-menu.trigger-gap`): es la misma pareja. Sin tokens nuevos.
|
|
85
|
+
|
|
10
86
|
## [49.4.0] — 2026-09-17
|
|
11
87
|
|
|
12
88
|
> **Minor.** Cuatro huecos que dejó al descubierto la campaña de crudos de bricks: un bocadillo
|
|
@@ -7,11 +7,22 @@ function i({ children: e, ...n }) {
|
|
|
7
7
|
children: e
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
|
-
function a(r, i) {
|
|
10
|
+
function a(r, i, a, o) {
|
|
11
|
+
let s = a && o ? /* @__PURE__ */ n("span", {
|
|
12
|
+
className: `${o}__item-text`,
|
|
13
|
+
children: [/* @__PURE__ */ t("span", {
|
|
14
|
+
className: `${o}__item-label`,
|
|
15
|
+
children: r
|
|
16
|
+
}), /* @__PURE__ */ t("span", {
|
|
17
|
+
className: `${o}__item-description`,
|
|
18
|
+
children: a
|
|
19
|
+
})]
|
|
20
|
+
}) : r;
|
|
11
21
|
return i ? /* @__PURE__ */ n(e, { children: [/* @__PURE__ */ t("span", {
|
|
12
22
|
"aria-hidden": "true",
|
|
23
|
+
className: o ? `${o}__item-icon` : void 0,
|
|
13
24
|
children: i
|
|
14
|
-
}),
|
|
25
|
+
}), s] }) : /* @__PURE__ */ t(e, { children: s });
|
|
15
26
|
}
|
|
16
27
|
function o(e) {
|
|
17
28
|
return e.reduce((e, t) => {
|
|
@@ -22,8 +33,8 @@ function o(e) {
|
|
|
22
33
|
}), e;
|
|
23
34
|
}, []);
|
|
24
35
|
}
|
|
25
|
-
function s({ items: e, itemClass: n, separatorClass: i, renderLink: s, labelClass: c,
|
|
26
|
-
let
|
|
36
|
+
function s({ items: e, itemClass: n, separatorClass: i, renderLink: s, labelClass: c, blockClass: l, radioValue: u, onRadioValueChange: d }) {
|
|
37
|
+
let f = (e, o) => {
|
|
27
38
|
if (e.type === "separator") return /* @__PURE__ */ t(r.Separator, { className: i }, o);
|
|
28
39
|
if (e.type === "label") return c ? /* @__PURE__ */ t(r.Group, { children: /* @__PURE__ */ t(r.GroupLabel, {
|
|
29
40
|
className: c,
|
|
@@ -34,20 +45,20 @@ function s({ items: e, itemClass: n, separatorClass: i, renderLink: s, labelClas
|
|
|
34
45
|
value: e.value,
|
|
35
46
|
disabled: e.disabled,
|
|
36
47
|
closeOnClick: e.closeOnSelect !== !1,
|
|
37
|
-
children: a(e.label, e.icon)
|
|
48
|
+
children: a(e.label, e.icon, void 0, l)
|
|
38
49
|
}, o);
|
|
39
|
-
let
|
|
50
|
+
let u = a(e.label, e.icon, e.description, l);
|
|
40
51
|
return e.type === "link" ? e.disabled ? /* @__PURE__ */ t(r.Item, {
|
|
41
52
|
className: n(e.destructive),
|
|
42
53
|
disabled: !0,
|
|
43
|
-
children:
|
|
54
|
+
children: u
|
|
44
55
|
}, o) : /* @__PURE__ */ t(r.Item, {
|
|
45
56
|
className: n(e.destructive),
|
|
46
57
|
render: (t) => s({
|
|
47
58
|
...t,
|
|
48
59
|
href: e.href,
|
|
49
60
|
className: t.className ?? n(e.destructive),
|
|
50
|
-
children:
|
|
61
|
+
children: u
|
|
51
62
|
})
|
|
52
63
|
}, o) : /* @__PURE__ */ t(r.Item, {
|
|
53
64
|
className: n(e.destructive),
|
|
@@ -60,18 +71,18 @@ function s({ items: e, itemClass: n, separatorClass: i, renderLink: s, labelClas
|
|
|
60
71
|
}
|
|
61
72
|
setTimeout(() => e.onClick(), 0);
|
|
62
73
|
},
|
|
63
|
-
children:
|
|
74
|
+
children: u
|
|
64
75
|
}, o);
|
|
65
|
-
},
|
|
66
|
-
if (!
|
|
67
|
-
let
|
|
68
|
-
return o(
|
|
69
|
-
let i =
|
|
70
|
-
|
|
71
|
-
let a = e.items.map((e, t) =>
|
|
76
|
+
}, p = e;
|
|
77
|
+
if (!p.some((e) => e.type === "radio")) return p.map(f);
|
|
78
|
+
let m = 0;
|
|
79
|
+
return o(p).map((e, n) => {
|
|
80
|
+
let i = m;
|
|
81
|
+
m += e.items.length;
|
|
82
|
+
let a = e.items.map((e, t) => f(e, i + t));
|
|
72
83
|
return e.radio ? /* @__PURE__ */ t(r.RadioGroup, {
|
|
73
|
-
value:
|
|
74
|
-
onValueChange: (e) =>
|
|
84
|
+
value: u,
|
|
85
|
+
onValueChange: (e) => d?.(String(e)),
|
|
75
86
|
children: a
|
|
76
87
|
}, `radio-${n}`) : a;
|
|
77
88
|
});
|
|
@@ -328,7 +328,13 @@ export type IconName = keyof typeof ICONS;
|
|
|
328
328
|
export declare const ICON_NAMES: IconName[];
|
|
329
329
|
export interface IconProps {
|
|
330
330
|
name: IconName;
|
|
331
|
-
|
|
331
|
+
/**
|
|
332
|
+
* Talla del glifo. Las cinco fijas del sistema (`xs`…`xl`) o `text`, que no
|
|
333
|
+
* es una talla sino una instrucción: el icono mide `1em`, o sea el tamaño
|
|
334
|
+
* de la tipografía que lo rodea, y sube y baja con ella. Es la que usan el
|
|
335
|
+
* enlace y el botón para que el glifo acompañe a su texto.
|
|
336
|
+
*/
|
|
337
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'text';
|
|
332
338
|
className?: string;
|
|
333
339
|
}
|
|
334
340
|
export declare function Icon({ name, size, className }: IconProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -16,7 +16,7 @@ export interface LinkProps extends React.ComponentPropsWithoutRef<'a'> {
|
|
|
16
16
|
* desaparece en hover, igual en las dos.
|
|
17
17
|
*/
|
|
18
18
|
tone?: 'accent' | 'ink' | 'accent-1';
|
|
19
|
-
/** Un icono junto al texto («← Volver», «Descargar ↓»). Decorativo: el texto ya lo dice. */
|
|
19
|
+
/** Un icono junto al texto («← Volver», «Descargar ↓»). Decorativo: el texto ya lo dice. Mide lo que el texto del enlace (`1em`) y sube y baja con él. */
|
|
20
20
|
icon?: IconName;
|
|
21
21
|
/** Dónde va el icono: delante (`start`, por defecto) o detrás del texto. */
|
|
22
22
|
iconPosition?: 'start' | 'end';
|
|
@@ -2,6 +2,13 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
export type MenuButtonItem = {
|
|
3
3
|
type: 'button';
|
|
4
4
|
label: string;
|
|
5
|
+
/**
|
|
6
|
+
* Segunda línea del ítem, más tenue: el correo de una cuenta, el detalle de
|
|
7
|
+
* una acción. Con ella el ítem se pinta a dos líneas —`label` arriba,
|
|
8
|
+
* `description` debajo— y el icono se alinea al bloque entero. Sin ella, el
|
|
9
|
+
* ítem se pinta a una línea, exactamente igual que siempre.
|
|
10
|
+
*/
|
|
11
|
+
description?: string;
|
|
5
12
|
icon?: ReactNode;
|
|
6
13
|
onClick: () => void;
|
|
7
14
|
disabled?: boolean;
|
|
@@ -16,6 +23,8 @@ export type MenuButtonItem = {
|
|
|
16
23
|
export type MenuLinkItem = {
|
|
17
24
|
type: 'link';
|
|
18
25
|
label: string;
|
|
26
|
+
/** Segunda línea del ítem, más tenue. Igual que en el ítem `button`. */
|
|
27
|
+
description?: string;
|
|
19
28
|
icon?: ReactNode;
|
|
20
29
|
href: string;
|
|
21
30
|
disabled?: boolean;
|
|
@@ -63,9 +72,17 @@ interface RenderDropdownItemsOptions {
|
|
|
63
72
|
renderLink: (props: MenuRenderLinkProps) => ReactNode;
|
|
64
73
|
/** Clase del rótulo de sección (ítems `label`). Sin ella, no se renderizan. */
|
|
65
74
|
labelClass?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Bloque BEM del menú que renderiza (`menu`, `user-menu`…): de él salen las
|
|
77
|
+
* clases del icono y de las dos líneas del ítem (`<bloque>__item-icon`,
|
|
78
|
+
* `-text`, `-label`, `-description`). Sin él, el ítem se pinta como siempre
|
|
79
|
+
* —una línea y el icono sin clase— y `description` se ignora: un menú que no
|
|
80
|
+
* viste la segunda línea no la dibuja a medias.
|
|
81
|
+
*/
|
|
82
|
+
blockClass?: string;
|
|
66
83
|
/** Valor activo del grupo de radio. */
|
|
67
84
|
radioValue?: string;
|
|
68
85
|
onRadioValueChange?: (value: string) => void;
|
|
69
86
|
}
|
|
70
|
-
export declare function renderDropdownItems({ items, itemClass, separatorClass, renderLink, labelClass, radioValue, onRadioValueChange, }: RenderDropdownItemsOptions): ReactNode;
|
|
87
|
+
export declare function renderDropdownItems({ items, itemClass, separatorClass, renderLink, labelClass, blockClass, radioValue, onRadioValueChange, }: RenderDropdownItemsOptions): ReactNode;
|
|
71
88
|
export {};
|