@aurea-uds/react 0.32.0 → 0.34.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/dist/actions.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import React, { type ButtonHTMLAttributes, type HTMLAttributes, type ReactElement, type RefAttributes } from "react";
2
2
  import { type Orientation, type Responsive } from "./pure.js";
3
- import { type IconName } from "./system.js";
4
- export type ButtonVariant = "primary" | "secondary" | "tertiary" | "outline" | "ghost" | "primary-outline" | "primary-ghost" | "danger" | "danger-outline" | "danger-ghost" | "link" | "link-primary" | "link-danger" | "nav";
5
- export type ButtonAppearance = "solid" | "outline" | "ghost" | "link" | "nav";
3
+ import { type IconName, type AureaIcon } from "./system.js";
4
+ export type ButtonVariant = "primary" | "secondary" | "tertiary" | "outline" | "ghost" | "primary-outline" | "primary-ghost" | "danger" | "danger-outline" | "danger-ghost" | "link" | "link-primary" | "link-danger" | "nav" | "danger-soft";
5
+ export type ButtonAppearance = "solid" | "outline" | "ghost" | "link" | "nav" | "soft";
6
6
  export type ButtonTone = "neutral" | "brand" | "danger" | "success" | "warning" | "info";
7
7
  export type ComponentSize = "xs" | "sm" | "md" | "lg" | "xl";
8
8
  /** Resolve os dois eixos (ou o atalho) na classe que o core pinta. Exportada porque o teste
@@ -20,10 +20,13 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, Re
20
20
  tone?: ButtonTone;
21
21
  size?: Responsive<ComponentSize>;
22
22
  loading?: boolean;
23
- leadingIcon?: IconName;
24
- trailingIcon?: IconName;
23
+ leadingIcon?: AureaIcon;
24
+ trailingIcon?: AureaIcon;
25
25
  href?: string;
26
26
  fullWidth?: boolean;
27
+ /** Menos peso (15/10/2026): a letra apagada e um degrau menor (12 no `sm`) — o botão de ferramenta de um topo de
28
+ * site, como o "Theme" da referência. A caixa e o fundo são os da aparência escolhida. */
29
+ quiet?: boolean;
27
30
  /** C-03 (24/09/2026): numa fila (`Cluster`), divide o espaço em partes iguais com os outros botões
28
31
  * que também têm `grow`. O `fullWidth` não serve ali: ele pede a linha inteira. */
29
32
  grow?: boolean;
@@ -50,7 +53,9 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, Re
50
53
  * quebrar, e por isso este é o momento mais barato que vai existir.
51
54
  */
52
55
  pressed?: boolean;
53
- kbd?: string;
56
+ /** O atalho, à mostra e anunciado. Texto: uma caixinha só ("⌘K"). LISTA (15/10/2026): uma caixinha por tecla, como
57
+ * a busca da referência (`["Ctrl", "K"]`), e o leitor de tela recebe a combinação (`Control+K`). */
58
+ kbd?: string | readonly string[];
54
59
  /**
55
60
  * GAR-14 (0.28.0): o botão COMPARTILHA. Ele pergunta ao navegador se sabe compartilhar este link
56
61
  * (`navigator.canShare`) — detectar o recurso, e não adivinhar o aparelho, é o padrão da web. Sabe
@@ -96,15 +101,18 @@ export type ToggleProps = ToggleBase & ({
96
101
  export declare function Toggle({ pressed, defaultPressed, onPressedChange, value, icon, label, size: sizePedido, disabled, id, className, children, variant }: ToggleProps): React.JSX.Element;
97
102
  export interface IconButtonProps extends Omit<ButtonProps, "children"> {
98
103
  label: string;
99
- icon: IconName;
104
+ icon: AureaIcon;
100
105
  }
101
106
  export declare const IconButton: React.ForwardRefExoticComponent<Omit<IconButtonProps, "ref"> & RefAttributes<HTMLButtonElement>>;
102
- export interface CloseButtonProps extends Omit<ButtonProps, "variant" | "appearance" | "tone" | "size" | "loading" | "leadingIcon" | "trailingIcon" | "href" | "target" | "rel" | "download" | "render" | "kbd" | "fullWidth" | "grow" | "pressed" | "share"> {
107
+ export interface CloseButtonProps extends Omit<ButtonProps, "quiet" | "variant" | "appearance" | "tone" | "size" | "loading" | "leadingIcon" | "trailingIcon" | "href" | "target" | "rel" | "download" | "render" | "kbd" | "fullWidth" | "grow" | "pressed" | "share"> {
103
108
  /** O nome que o leitor de tela anuncia. Padrão: a frase `close` ("Close"/"Fechar"). */
104
109
  label?: string;
105
110
  }
106
111
  export declare const CloseButton: React.ForwardRefExoticComponent<Omit<CloseButtonProps, "ref"> & RefAttributes<HTMLButtonElement>>;
107
- export interface ThemeToggleProps extends Omit<IconButtonProps, "icon" | "label" | "onClick" | "variant" | "appearance" | "tone" | "href" | "target" | "rel" | "download" | "render" | "kbd" | "loading" | "leadingIcon" | "trailingIcon" | "fullWidth" | "share"> {
112
+ export interface ThemeToggleProps extends Omit<IconButtonProps, "quiet" | "icon" | "label" | "onClick" | "variant" | "appearance" | "tone" | "href" | "target" | "rel" | "download" | "render" | "kbd" | "loading" | "leadingIcon" | "trailingIcon" | "fullWidth" | "share"> {
113
+ /** `"light-dark"` (o padrão): o botão que alterna. `"light-dark-system"` (15/10/2026): a chave de três posições do
114
+ * topo da referência — claro, escuro e sistema (seguir o do computador, e mudar junto). */
115
+ mode?: "light-dark" | "light-dark-system";
108
116
  }
109
117
  export declare const ThemeToggle: React.ForwardRefExoticComponent<Omit<ThemeToggleProps, "ref"> & RefAttributes<HTMLButtonElement>>;
110
118
  export interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement>, RefAttributes<HTMLDivElement> {
package/dist/actions.js CHANGED
@@ -24,13 +24,16 @@ const EIXOS = {
24
24
  "primary-outline": ["outline", "brand"], "primary-ghost": ["ghost", "brand"],
25
25
  danger: ["solid", "danger"], "danger-outline": ["outline", "danger"], "danger-ghost": ["ghost", "danger"],
26
26
  link: ["link", "neutral"], "link-primary": ["link", "brand"], "link-danger": ["link", "danger"],
27
- nav: ["nav", "neutral"]
27
+ nav: ["nav", "neutral"], "danger-soft": ["soft", "danger"]
28
28
  };
29
+ // Atalhos que NÃO têm classe própria (15/10/2026): saem como os dois eixos (`btn-soft btn-tone-danger`), e herdam as
30
+ // regras de cor do tom nos dois temas em vez de repeti-las numa classe nova.
31
+ const SO_EIXOS = new Set(["danger-soft"]);
29
32
  // caminho de volta: par → nome antigo. Existir aqui significa "o core já pinta esta célula com
30
33
  // uma classe própria", e é o que garante que NENHUMA baseline se mexe: `appearance="outline"`
31
34
  // + `tone="danger"` emite `.btn-danger-outline`, o mesmo DOM de sempre.
32
35
  const CLASSE_ANTIGA = new Map(Object.entries(EIXOS)
33
- .filter(([v]) => v !== "secondary").map(([v, [a, t]]) => [`${a}/${t}`, v]));
36
+ .filter(([v]) => v !== "secondary" && !SO_EIXOS.has(v)).map(([v, [a, t]]) => [`${a}/${t}`, v]));
34
37
  /** Resolve os dois eixos (ou o atalho) na classe que o core pinta. Exportada porque o teste
35
38
  * cobra a matriz inteira, e porque quem monta um botão à mão precisa da mesma conta.
36
39
  *
@@ -41,7 +44,7 @@ const CLASSE_ANTIGA = new Map(Object.entries(EIXOS)
41
44
  * que se pede, nunca o que sobra quando a nossa paleta não dá conta. */
42
45
  export function buttonSkin(variant, appearance, tone) {
43
46
  // A variação sem eixo pedido é a própria classe — é o caminho do `secondary`, que não tem célula.
44
- if (appearance === undefined && tone === undefined)
47
+ if (appearance === undefined && tone === undefined && !(variant && SO_EIXOS.has(variant)))
45
48
  return `btn-${variant ?? "tertiary"}`;
46
49
  const [a0, t0] = EIXOS[variant ?? "tertiary"];
47
50
  const a = appearance ?? a0, t = tone ?? t0;
@@ -100,7 +103,13 @@ const GrupoDeBotoesContext = React.createContext(null);
100
103
  // type="button" por default: o default do HTML é submit, e um "Cancelar"/"Remover"
101
104
  // dentro de <form> dispararia a ação principal (auditoria 18/07/2026, ALTO 1).
102
105
  // Quem quer submeter passa type="submit" explícito (como o MessageComposer faz).
103
- export const Button = forwardRef(function Button({ variant: variantPedida, appearance, tone, size: sizePedido, loading, leadingIcon, trailingIcon, className, children, disabled: disabledPedido, type = "button", href, fullWidth, grow, target, rel, download, pressed, kbd, onClick, onClickCapture, "aria-disabled": ariaDisabled, render, share, ...props }, ref) {
106
+ // O nome da combinação para o `aria-keyshortcuts`: as teclas juntas por "+", com os nomes da especificação (o ESPAÇO
107
+ // separa atalhos ALTERNATIVOS — "Ctrl K" quer dizer "Ctrl" ou "K"). O texto solto passa como veio, como antes.
108
+ const NOME_DA_TECLA = { ctrl: "Control", control: "Control", "⌃": "Control", cmd: "Meta", command: "Meta", "⌘": "Meta", meta: "Meta",
109
+ shift: "Shift", "⇧": "Shift", alt: "Alt", option: "Alt", "⌥": "Alt", enter: "Enter", "↵": "Enter", esc: "Escape", escape: "Escape" };
110
+ const atalhoAnunciado = (kbd) => typeof kbd === "string" ? (kbd || undefined)
111
+ : kbd && kbd.length > 0 ? kbd.map(k => NOME_DA_TECLA[k.toLowerCase()] ?? k).join("+") : undefined;
112
+ export const Button = forwardRef(function Button({ variant: variantPedida, appearance, tone, size: sizePedido, quiet, loading, leadingIcon, trailingIcon, className, children, disabled: disabledPedido, type = "button", href, fullWidth, grow, target, rel, download, pressed, kbd, onClick, onClickCapture, "aria-disabled": ariaDisabled, render, share, ...props }, ref) {
104
113
  const grupo = React.useContext(GrupoDeBotoesContext);
105
114
  const variant = variantPedida ?? grupo?.variant;
106
115
  const size = sizePedido ?? grupo?.size ?? "md";
@@ -112,7 +121,7 @@ export const Button = forwardRef(function Button({ variant: variantPedida, appea
112
121
  // Quem decide é o CSS: nada aqui observa largura, e o HTML do servidor já sai correto.
113
122
  const responsivo = ehResponsivo(size);
114
123
  const base = valorBase(size) ?? "md";
115
- const cls = cx("btn", buttonSkin(variant, appearance, tone), responsivo ? classesResponsivas("size", size) : (base !== "md" && `btn-${base}`), fullWidth && "btn-block", grow && "btn-grow", className);
124
+ const cls = cx("btn", buttonSkin(variant, appearance, tone), responsivo ? classesResponsivas("size", size) : (base !== "md" && `btn-${base}`), fullWidth && "btn-block", grow && "btn-grow", quiet && "btn-quiet", className);
116
125
  // kbd dentro do botão: mostra o atalho E o anuncia (aria-keyshortcuts), senão é enfeite.
117
126
  // 🔴 OCUPADO TROCA, NÃO ACRESCENTA (01/10/2026, achado pelo Victor no aureauds.dev): a rodinha
118
127
  // entrava ANTES do ícone, e no `IconButton` — 36 de largura fixa — rodinha e glifo não cabiam:
@@ -122,7 +131,7 @@ export const Button = forwardRef(function Button({ variant: variantPedida, appea
122
131
  // da frente; e sem conteúdo (o `IconButton` ocupado) o invólucro vazio some, senão o `gap` dele
123
132
  // empurraria a rodinha para fora do centro. Fora do estado ocupado o HTML é o mesmo de antes.
124
133
  const temConteudo = children != null && children !== false;
125
- const inner = _jsxs(_Fragment, { children: [loading ? _jsx("span", { className: "spinner" }) : leadingIcon && _jsx(Icon, { name: leadingIcon }), (temConteudo || !loading) && _jsx("span", { children: children }), kbd && _jsx(Kbd, { className: "btn-kbd", children: kbd }), trailingIcon && _jsx(Icon, { name: trailingIcon })] });
134
+ const inner = _jsxs(_Fragment, { children: [loading ? _jsx("span", { className: "spinner" }) : leadingIcon && _jsx(Icon, { name: leadingIcon }), (temConteudo || !loading) && _jsx("span", { children: children }), typeof kbd === "string" ? (kbd && _jsx(Kbd, { className: "btn-kbd", children: kbd })) : kbd && kbd.length > 0 && _jsx("span", { className: "btn-kbds", children: kbd.map((k, i) => _jsx(Kbd, { className: "btn-kbd", children: k }, i)) }), trailingIcon && _jsx(Icon, { name: trailingIcon })] });
126
135
  // INERTE ≠ DESABILITADO, e a diferença é medida (M4, 13/08/2026): `disabled` tira o botão da
127
136
  // ordem de foco, então quem navega por teclado nunca alcança a explicação de POR QUE não dá — e
128
137
  // "não dá porque você não tem permissão" é justamente o caso em que a explicação é tudo. O
@@ -132,7 +141,7 @@ export const Button = forwardRef(function Button({ variant: variantPedida, appea
132
141
  // Mesmo remendo do AUD-0004, que já barrava o link desabilitado; aqui ele alcança o <button>.
133
142
  const inerte = ariaDisabled === true || ariaDisabled === "true";
134
143
  const off = disabled || loading;
135
- const shared = { "aria-keyshortcuts": kbd || undefined, "aria-busy": loading || undefined };
144
+ const shared = { "aria-keyshortcuts": atalhoAnunciado(kbd), "aria-busy": loading || undefined };
136
145
  // GAR-14: o `share` emite a mesma marcação que o `aurea.js` lê ([data-aurea-share]) — o catálogo
137
146
  // estático não hidrata, e é ela que faz o botão funcionar lá. Os textos vão junto, na língua do app.
138
147
  const s = useAureaStrings();
@@ -225,10 +234,22 @@ export const CloseButton = forwardRef(function CloseButton({ label, className, c
225
234
  const s = useAureaStrings();
226
235
  return _jsx(Button, { ref: ref, variant: "tertiary", className: cx("close-button", className), "aria-label": label ?? s.close, ...props, children: children ?? _jsx(Icon, { name: "x", weight: "bold" }) });
227
236
  });
228
- export const ThemeToggle = forwardRef(function ThemeToggle({ className, ...props }, ref) {
229
- const { theme, toggleTheme } = useAureaTheme();
237
+ export const ThemeToggle = forwardRef(function ThemeToggle({ className, mode = "light-dark", ...props }, ref) {
238
+ const { theme, preference, toggleTheme, setTheme } = useAureaTheme();
230
239
  const s = useAureaStrings();
231
240
  const escuro = theme === "dark";
241
+ const nome = React.useId();
242
+ // A CHAVE DE TRÊS POSIÇÕES (15/10/2026, o Victor: "A, B, C, pode"): a do topo da referência, medida nela — a
243
+ // cápsula de 34 com as três opções de 26 e o glifo de 16. São botões de opção NATIVOS, como as amostras do
244
+ // `ColorSwatch`: a escolha única e as setas são do navegador. A escolhida tem o glifo cheio (ADR-0053) e a pele da
245
+ // aba escolhida (ADR-0059). Quem escolhe é o provider (`"system"` segue o sistema).
246
+ if (mode === "light-dark-system") {
247
+ const opcoes = [["light", "sun", s.themeLight], ["dark", "moon", s.themeDark], ["system", "monitor", s.themeSystem]];
248
+ return _jsx("div", { role: "radiogroup", "aria-label": s.themeChoice, className: cx("theme-switch", className), children: opcoes.map(([v, icone, rotulo]) => {
249
+ const marcada = preference === v;
250
+ return _jsxs("label", { className: "theme-switch-option", children: [_jsx("input", { type: "radio", className: "theme-switch-input", name: nome, value: v, checked: marcada, disabled: props.disabled, "aria-label": rotulo, onChange: () => setTheme(v) }), _jsx(Icon, { name: icone, size: "sm", weight: marcada ? "fill" : "regular" })] }, v);
251
+ }) });
252
+ }
232
253
  // O corpo do `IconButton`, escrito aqui só para o glifo sair na forma CHEIA (ADR-0053: a lua e o
233
254
  // sol cheios, como os do conjunto de ícones anterior, aprovados pela imagem em 25/09/2026). O `IconButton` público não
234
255
  // tem peso de ícone.
package/dist/calendar.js CHANGED
@@ -133,7 +133,7 @@ export function DatePicker(props) {
133
133
  setIdDoRotulo(gatilho.current?.labels?.[0]?.id || undefined);
134
134
  }
135
135
  setAberta(o);
136
- }, children: [_jsxs(BasePopover.Trigger, { ref: gatilho, disabled: disabled, id: id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, className: cx("select", "select-trigger", "date-picker-trigger", peleDoEixo("select", size), className), children: [_jsx("span", { className: "select-value", "data-placeholder": vazio ? "" : undefined, lang: vazio ? undefined : locale?.code, children: texto ?? placeholder ?? (props.range ? s.dateRangePlaceholder : s.datePlaceholder) }), icon !== false && _jsx(Icon, { name: icon })] }), name && _jsx("input", { type: "hidden", name: name, value: campo ?? "" }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { side: "bottom", align: "start", sideOffset: 6, children: _jsx(BasePopover.Popup, { className: "popover date-picker-popover", "aria-labelledby": ariaLabelledby ?? idDoRotulo, "aria-label": (ariaLabelledby ?? idDoRotulo) ? undefined
136
+ }, children: [_jsxs(BasePopover.Trigger, { ref: gatilho, disabled: disabled, id: id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, className: cx("select", "select-trigger", "date-picker-trigger", peleDoEixo("select", size), className), children: [_jsx("span", { className: "select-value", "data-placeholder": vazio ? "" : undefined, lang: vazio ? undefined : locale?.code, children: texto ?? placeholder ?? (props.range ? s.dateRangePlaceholder : s.datePlaceholder) }), icon !== false && _jsx(Icon, { name: icon })] }), name && _jsx("input", { type: "hidden", name: name, value: campo ?? "" }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { className: "positioner", side: "bottom", align: "start", sideOffset: 6, children: _jsx(BasePopover.Popup, { className: "popover date-picker-popover", "aria-labelledby": ariaLabelledby ?? idDoRotulo, "aria-label": (ariaLabelledby ?? idDoRotulo) ? undefined
137
137
  : ariaLabel ?? (props.range ? s.dateRangePlaceholder : s.datePlaceholder), children: calendario }) }) })] });
138
138
  }
139
139
  // O `Field` nomeia o gatilho com `<label for>`: o `id` que ele injeta vai para o botão (ver `internal.tsx`).
@@ -37,18 +37,18 @@ import { cx, useAureaStrings } from "./internal.js";
37
37
  // (auditoria 18/07/2026, ALTO 5). Com a anotação + sideEffects:false, o tema e o
38
38
  // CodeEditor (e o CodeMirror) somem quando não usados. Split por subpath = decisão do Victor.
39
39
  export const aureaEditorTheme = /*@__PURE__*/ EditorView.theme({
40
- "&": { backgroundColor: "var(--surface-inset)", color: "var(--foreground)", border: "var(--border-width) solid var(--border)", borderRadius: "var(--radius-lg)", fontSize: "var(--text-xs)" },
40
+ "&": { backgroundColor: "var(--surface-inset)", color: "var(--foreground)", border: "var(--border-width) solid transparent", borderRadius: "var(--radius-lg)", fontSize: "var(--text-xs)" },
41
41
  "&.cm-focused": { outline: "2px solid var(--focus-strong)", outlineOffset: "-2px" },
42
42
  ".cm-scroller": { fontFamily: "var(--font-code)", lineHeight: "var(--leading-relaxed)", borderRadius: "inherit" },
43
43
  ".cm-content": { caretColor: "var(--foreground)", padding: "var(--space-2) 0" },
44
44
  ".cm-line": { padding: "0 var(--space-3)" },
45
- ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--foreground)" },
46
- "&.cm-focused .cm-cursor": { borderLeftColor: "var(--foreground)" },
45
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: "transparent", width: "calc(2 * var(--border-width))", marginLeft: "calc(-1 * var(--border-width))", backgroundColor: "var(--foreground)" },
46
+ "&.cm-focused .cm-cursor": { borderLeftColor: "transparent", backgroundColor: "var(--foreground)" },
47
47
  ".cm-selectionBackground, ::selection": { backgroundColor: "var(--selection)" },
48
48
  "&.cm-focused .cm-selectionBackground": { backgroundColor: "var(--selection)" },
49
49
  ".cm-selectionMatch": { backgroundColor: "var(--selection)" },
50
50
  ".cm-activeLine": { backgroundColor: "color-mix(in srgb,var(--foreground) 5%,transparent)" },
51
- ".cm-gutters": { backgroundColor: "var(--surface-inset)", color: "var(--muted-foreground)", border: "none", borderRight: "var(--border-width) solid var(--border)" },
51
+ ".cm-gutters": { backgroundColor: "var(--surface-inset)", color: "var(--muted-foreground)", border: "none", borderRight: "var(--border-width) solid transparent" },
52
52
  ".cm-gutterElement": { padding: "0 var(--space-2)" },
53
53
  ".cm-activeLineGutter": { backgroundColor: "color-mix(in srgb,var(--foreground) 5%,transparent)", color: "var(--foreground)" },
54
54
  ".cm-matchingBracket, &.cm-focused .cm-matchingBracket": { backgroundColor: "color-mix(in srgb,var(--foreground) 16%,transparent)", color: "var(--foreground)", outline: "none" },
@@ -56,9 +56,9 @@ export const aureaEditorTheme = /*@__PURE__*/ EditorView.theme({
56
56
  ".cm-searchMatch": { backgroundColor: "color-mix(in srgb,var(--warning-400) 30%,transparent)" },
57
57
  ".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "var(--selection)" },
58
58
  ".cm-panels": { backgroundColor: "var(--surface-2)", color: "var(--foreground)" },
59
- ".cm-panels.cm-panels-top": { borderBottom: "var(--border-width) solid var(--border)" },
60
- ".cm-panels.cm-panels-bottom": { borderTop: "var(--border-width) solid var(--border)" },
61
- ".cm-tooltip": { backgroundColor: "var(--popover)", color: "var(--popover-foreground)", border: "var(--border-width) solid var(--border)", borderRadius: "var(--radius-md)" },
59
+ ".cm-panels.cm-panels-top": { borderBottom: "var(--border-width) solid transparent" },
60
+ ".cm-panels.cm-panels-bottom": { borderTop: "var(--border-width) solid transparent" },
61
+ ".cm-tooltip": { backgroundColor: "var(--popover)", color: "var(--popover-foreground)", border: "var(--border-width) solid transparent", borderRadius: "var(--radius-md)" },
62
62
  ".cm-tooltip-autocomplete ul li[aria-selected]": { backgroundColor: "color-mix(in srgb,var(--primary) 14%,transparent)", color: "var(--foreground)" },
63
63
  ".cm-completionLabel": { color: "var(--foreground)" },
64
64
  });
@@ -9,12 +9,14 @@ export declare function Status({ variant, state, children, className, ...props }
9
9
  state?: UniversalState;
10
10
  }): React.JSX.Element;
11
11
  export type AlertVariant = "info" | "success" | "warning" | "danger" | "accent";
12
- export declare function Alert({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
12
+ export declare function Alert({ variant, state, title, icon, onDismiss, action, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
13
13
  variant?: AlertVariant;
14
14
  state?: UniversalState;
15
15
  title?: ReactNode;
16
16
  icon?: IconName;
17
17
  onDismiss?: () => void;
18
+ /** O botão do aviso ("Atualizar", "Desfazer"): depois do texto, antes do X. No celular, embaixo do texto. */
19
+ action?: ReactNode;
18
20
  }): React.JSX.Element;
19
21
  export type BannerVariant = "info" | "success" | "warning" | "danger";
20
22
  export declare function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
@@ -51,7 +51,9 @@ const ICONE_DA_VARIANTE = { info: "info",
51
51
  // universal. Quem passa só `state` recebe a cor derivada da gravidade e o rótulo universal
52
52
  // quando não há filho; quem passa os dois manda, porque só o consumidor sabe se aquele
53
53
  // "esperando" dele é grave.
54
- export function Alert({ variant, state, title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("alert", `alert-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [_jsx(Icon, { name: icon ?? ICONE_DA_VARIANTE[v], weight: icon ? undefined : "fill" }), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(CloseButton, { onClick: onDismiss }) : _jsx("span", {})] }); }
54
+ // `action` (15/10/2026, o "Upgrade" da referência): o botão do aviso, depois do texto e antes do X; no celular ele desce
55
+ // para baixo do texto, na mesma coluna (a referência repete o botão; aqui ele só muda de lugar).
56
+ export function Alert({ variant, state, title, icon, onDismiss, action, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("alert", `alert-${v}`, action != null && "alert-has-action", className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [_jsx(Icon, { name: icon ?? ICONE_DA_VARIANTE[v], weight: icon ? undefined : "fill" }), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), action != null && _jsx("div", { className: "alert-action", children: action }), onDismiss ? _jsx(CloseButton, { onClick: onDismiss }) : _jsx("span", {})] }); }
55
57
  export function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("banner", `banner-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [icon ? _jsx(Icon, { name: icon }) : _jsx("span", {}), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
56
58
  export function Spinner({ size = "md", label, decorative, className, ...props }) { const s = useAureaStrings(); return _jsx("span", { className: cx("spinner", peleDoEixo("spinner", size, "sm", "spinner"), className), ...(decorative ? { "aria-hidden": true } : { role: "status", "aria-label": label ?? s.loading }), ...props }); }
57
59
  // `children` como FUNÇÃO é de propósito para o caso `loading`: assim o consumidor não paga o
@@ -138,7 +140,7 @@ export function NotificationCenter({ items, onItemClick, onMarkAllRead, label, i
138
140
  ? _jsx("button", { type: "button", className: "notification-item", "data-read": it.read || undefined, onClick: () => { it.onClick?.(); onItemClick?.(it); }, children: body })
139
141
  : _jsx("div", { className: "notification-item", "data-read": it.read || undefined, children: body });
140
142
  };
141
- return _jsxs(BasePopover.Root, { children: [_jsxs("span", { className: "notification-trigger", children: [_jsx(BasePopover.Trigger, { render: _jsx(IconButton, { variant: "ghost", icon: icon, label: unread ? `${title} (${unread})` : title }) }), unread > 0 && _jsx("span", { className: "notification-count", "aria-hidden": "true", children: unread > 99 ? "99+" : unread })] }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { side: side, sideOffset: 8, children: _jsxs(BasePopover.Popup, { className: "popover notification-panel", "aria-label": title, children: [_jsxs("div", { className: "notification-head", children: [_jsx(BasePopover.Title, { render: _jsx("strong", {}), children: title }), unread > 0 && onMarkAllRead && _jsx(Button, { variant: "ghost", size: "sm", onClick: onMarkAllRead, children: s.notificationMarkAll })] }), items.length
143
+ return _jsxs(BasePopover.Root, { children: [_jsxs("span", { className: "notification-trigger", children: [_jsx(BasePopover.Trigger, { render: _jsx(IconButton, { variant: "ghost", icon: icon, label: unread ? `${title} (${unread})` : title }) }), unread > 0 && _jsx("span", { className: "notification-count", "aria-hidden": "true", children: unread > 99 ? "99+" : unread })] }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { className: "positioner", side: side, sideOffset: 8, children: _jsxs(BasePopover.Popup, { className: "popover notification-panel", "aria-label": title, children: [_jsxs("div", { className: "notification-head", children: [_jsx(BasePopover.Title, { render: _jsx("strong", {}), children: title }), unread > 0 && onMarkAllRead && _jsx(Button, { variant: "ghost", size: "sm", onClick: onMarkAllRead, children: s.notificationMarkAll })] }), items.length
142
144
  ? _jsx("div", { className: "notification-list", children: groups.map((g, gi) => {
143
145
  const gid = baseId + gi;
144
146
  return _jsxs(React.Fragment, { children: [g.label && _jsx("p", { className: "notification-group-label", id: gid, children: g.label }), _jsx("ul", { className: "notification-sublist", "aria-labelledby": g.label ? gid : undefined, children: g.items.map(it => _jsx("li", { children: renderRow(it) }, it.id)) })] }, gi);
@@ -1,10 +1,15 @@
1
1
  import { type Responsive } from "./pure.js";
2
2
  import React, { type ReactNode } from "react";
3
3
  import type { AvatarSize } from "./markup.js";
4
- export declare function Avatar({ src, alt, fallback, size, className }: {
4
+ export type AvatarTone = "neutral" | "brand" | "danger" | "success" | "warning" | "info";
5
+ export declare function Avatar({ src, alt, fallback, size, tone, emphasis, className }: {
5
6
  src?: string;
6
7
  alt?: string;
7
8
  fallback?: ReactNode;
8
9
  size?: Responsive<AvatarSize>;
10
+ /** A cor da letra ou do ícone de reserva: o que ele significa. */
11
+ tone?: AvatarTone;
12
+ /** `soft`: sem a moldura, com o fundo clarinho do tom. */
13
+ emphasis?: "default" | "soft";
9
14
  className?: string;
10
15
  }): React.JSX.Element;
@@ -6,11 +6,8 @@ import { peleDoEixo } from "./pure.js";
6
6
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
7
7
  import React from "react";
8
8
  import { cx } from "./internal.js";
9
- // O DEGRAU RESPONSIVO volta (merge de 28/08/2026): a ficha declara `responsive.size` e o
10
- // componente tinha perdido a capacidade quando este arquivo entrou da `main`. Mesma perda
11
- // silenciosa do `Input`, do `Tabs` e do `NumberField` — a ficha documentava, o código não fazia.
12
- export function Avatar({ src, alt = "", fallback, size, className }) {
9
+ export function Avatar({ src, alt = "", fallback, size, tone, emphasis, className }) {
13
10
  const [falhou, setFalhou] = React.useState(false);
14
11
  React.useEffect(() => { setFalhou(false); }, [src]);
15
- return _jsx("span", { className: cx("avatar", peleDoEixo("avatar", size), className), children: src && !falhou ? _jsx("img", { src: src, alt: alt, onError: () => setFalhou(true) }) : fallback });
12
+ return _jsx("span", { className: cx("avatar", peleDoEixo("avatar", size), tone && tone !== "neutral" && `avatar-tone-${tone}`, emphasis === "soft" && "avatar-soft", className), children: src && !falhou ? _jsx("img", { src: src, alt: alt, onError: () => setFalhou(true) }) : fallback });
16
13
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStateFromParams, screenStateToParams, screenStateFromParams, universalStates, stateSeverity, type AureaStrings, type AureaTheme, type AureaDensity, type GridState, type ScreenState, type UniversalState } from "./pure.js";
1
+ export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStateFromParams, screenStateToParams, screenStateFromParams, universalStates, stateSeverity, type AureaStrings, type AureaTheme, type AureaThemePreference, type AureaDensity, type GridState, type ScreenState, type UniversalState } from "./pure.js";
2
2
  export { useAureaStrings, useSpriteUrl, useTheme, useDensity } from "./internal.js";
3
3
  export { type Responsive, type Breakpoint, type ContainerBreakpoint, type Orientation, ESCALA, ESCALA_CONTAINER } from "./pure.js";
4
4
  export { ContainerScope, useValorResponsivo } from "./responsivo-runtime.js";
@@ -1,7 +1,7 @@
1
1
  import React, { type ButtonHTMLAttributes, type HTMLAttributes, type InputHTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
2
  import { type Responsive } from "./pure.js";
3
3
  import { type ColorChannel, type ColorSpace } from "./color.js";
4
- import { type ColorSwatchOption, type FieldSize, type FieldVariant } from "./markup.js";
4
+ import { Radio, type ColorSwatchOption, type FieldSize, type FieldVariant } from "./markup.js";
5
5
  import { type IconName } from "./system.js";
6
6
  export { FileInput, matchesAccept, type FileRejection, type UploadContext, type UploadFn } from "./file-input.js";
7
7
  export type FieldOrientation = "vertical" | "horizontal";
@@ -76,6 +76,29 @@ export interface CheckboxGroupProps extends Omit<HTMLAttributes<HTMLDivElement>,
76
76
  required?: boolean;
77
77
  }
78
78
  export declare function CheckboxGroup({ label, description, error, value, defaultValue, onValueChange, name, disabled, required, children, className, ...props }: CheckboxGroupProps): React.JSX.Element;
79
+ export interface RadioGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "defaultValue" | "onChange">, RefAttributes<HTMLDivElement> {
80
+ label: ReactNode;
81
+ /** O nome sai da tela e fica para o leitor de tela, como no `Checkbox` (a fileira de controles da vitrine da referência). */
82
+ labelHidden?: boolean;
83
+ description?: ReactNode;
84
+ error?: ReactNode;
85
+ value?: string;
86
+ defaultValue?: string;
87
+ onValueChange?: (value: string) => void;
88
+ /** O `name` de todas as opções, para o formulário. Sem ele, um nome próprio do grupo. */
89
+ name?: string;
90
+ disabled?: boolean;
91
+ /** Pinta todas as opções de erro, como o `invalid` do nativo. Com `error`, já vale. */
92
+ invalid?: boolean;
93
+ /** `vertical` (o padrão) ou `horizontal`, como a referência e o nativo. */
94
+ orientation?: "vertical" | "horizontal";
95
+ /** O asterisco no rótulo, como o `Field`. */
96
+ required?: boolean;
97
+ }
98
+ export declare function RadioGroup({ label, labelHidden, description, error, value, defaultValue, onValueChange, name, disabled, invalid, orientation, required, children, className, ...props }: RadioGroupProps): React.JSX.Element;
99
+ export declare namespace RadioGroup {
100
+ export { Radio as Item };
101
+ }
79
102
  export interface ListBoxItem {
80
103
  id: string;
81
104
  label: ReactNode;
@@ -86,6 +109,8 @@ export interface ListBoxItem {
86
109
  disabled?: boolean;
87
110
  /** O item de perigo ("Excluir"), na cor de perigo. */
88
111
  danger?: boolean;
112
+ /** O atalho do item ("⌘N"), à direita, como na lista da referência (15/10/2026). Só mostra: quem liga a tecla é o app. */
113
+ kbd?: string;
89
114
  }
90
115
  export interface ListBoxSection {
91
116
  title: ReactNode;
@@ -269,7 +294,7 @@ export interface ColorPickerProps {
269
294
  className?: string;
270
295
  }
271
296
  export declare function ColorPicker({ label, value, defaultValue, onValueChange, onValueCommitted, swatches, variant, children, disabled, name, id, className }: ColorPickerProps): React.JSX.Element;
272
- export declare function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className }: {
297
+ export declare function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className, groupSize, variant }: {
273
298
  length: number;
274
299
  value?: string;
275
300
  defaultValue?: string;
@@ -280,6 +305,10 @@ export declare function OTPField({ length, value, defaultValue, onValueChange, m
280
305
  label?: string;
281
306
  id?: string;
282
307
  className?: string;
308
+ /** O tracinho entre os grupos: a cada `groupSize` caixas (3 num código de 6). */
309
+ groupSize?: number;
310
+ /** `secondary`: o fundo neutro do `Input` secundário, para o campo dentro de um cartão. */
311
+ variant?: "primary" | "secondary";
283
312
  }): React.JSX.Element;
284
313
  export declare function SegmentedControl({ items, value, onChange, label, fullWidth }: {
285
314
  items: Array<{
@@ -22,7 +22,7 @@ import { Popover as BasePopover } from "@base-ui/react/popover";
22
22
  // diretiva — são marcação, e quem escuta `onChange` é o consumidor. Voltam IMPORTADOS e não só
23
23
  // reexportados porque o `ehControle` do `Field` compara por IDENTIDADE de referência: dois
24
24
  // objetos diferentes com o mesmo nome quebrariam o rótulo do campo em silêncio.
25
- import { Textarea, Checkbox, Radio, Switch, Range, InputGroup, InputGroupAddon, ColorSwatch } from "./markup.js";
25
+ import { Textarea, Checkbox, Radio, Switch, Range, InputGroup, InputGroupAddon, ColorSwatch, Kbd } from "./markup.js";
26
26
  import { Icon } from "./system.js";
27
27
  import { IconButton } from "./actions.js";
28
28
  // A FAMÍLIA DE COR (0.32.0, ADR-0069) — as peças INTERNAS moram aqui, no topo, antes do primeiro export (auxiliar
@@ -383,7 +383,8 @@ export const Input = forwardRef(function Input({ className, formatOnBlur, size,
383
383
  // `.input-wrap-end` para o botão do PasswordField —, e o grupo é a terceira vez virando uma. O
384
384
  // merge das duas linhagens tinha trazido de volta a versão com `.input-wrap`, contra um core
385
385
  // que já não a pinta.
386
- export function SearchField({ icon = "magnifying-glass", className, size, width, ...props }) { return _jsxs(InputGroup, { className: className, width: width, children: [_jsx(InputGroupAddon, { children: _jsx(Icon, { name: icon, size: valorBase(size) === "sm" ? "sm" : undefined }) }), _jsx(Input, { type: "search", size: size, ...props })] }); }
386
+ // `search-field` (15/10/2026): a busca tem a medida própria da referência — 12, a lupa, 8 (ver o core).
387
+ export function SearchField({ icon = "magnifying-glass", className, size, width, ...props }) { return _jsxs(InputGroup, { className: cx("search-field", className), width: width, children: [_jsx(InputGroupAddon, { children: _jsx(Icon, { name: icon, size: valorBase(size) === "sm" ? "sm" : undefined }) }), _jsx(Input, { type: "search", size: size, ...props })] }); }
387
388
  // labelHidden: coluna de seleção de tabela precisa do rótulo POR LINHA para o leitor de
388
389
  // tela ("Select Analyst"), mas mostrá-lo engorda a coluna. O rótulo continua no DOM, só
389
390
  // sai da tela (.sr-only) — nunca trocar por aria-label solto num <label> visível vazio.
@@ -432,6 +433,33 @@ export function CheckboxGroup({ label, description, error, value, defaultValue,
432
433
  });
433
434
  return _jsxs("div", { role: "group", "aria-labelledby": idRotulo, "aria-describedby": [idDescricao, idErro].filter(Boolean).join(" ") || undefined, className: cx("checkbox-group", className), ...props, children: [_jsx("span", { className: "label", id: idRotulo, children: _jsxs("span", { children: [label, required ? _jsx("span", { className: "label-required", "aria-hidden": "true", children: "*" }) : null] }) }), description && _jsx("span", { className: "field-description", id: idDescricao, children: description }), caixas, error && _jsx("span", { className: "field-error", id: idErro, children: error })] });
434
435
  }
436
+ export function RadioGroup({ label, labelHidden, description, error, value, defaultValue, onValueChange, name, disabled, invalid, orientation = "vertical", required, children, className, ...props }) {
437
+ const auto = React.useId();
438
+ const [proprio, setProprio] = React.useState(defaultValue);
439
+ const escolhido = value ?? proprio;
440
+ const idRotulo = `${auto}-label`, idDescricao = description ? `${auto}-description` : undefined, idErro = error ? `${auto}-error` : undefined;
441
+ const opcoes = React.Children.map(children, filho => {
442
+ if (!React.isValidElement(filho))
443
+ return filho;
444
+ const p = filho.props;
445
+ if (typeof p.value !== "string")
446
+ return filho;
447
+ const v = p.value;
448
+ return React.cloneElement(filho, { checked: escolhido === v, name: name ?? auto, required,
449
+ disabled: p.disabled ?? disabled, ...(error || invalid ? { "aria-invalid": true } : null),
450
+ onChange: (e) => {
451
+ p.onChange?.(e);
452
+ if (!e.currentTarget.checked)
453
+ return;
454
+ if (value === undefined)
455
+ setProprio(v);
456
+ onValueChange?.(v);
457
+ } });
458
+ });
459
+ return _jsxs("div", { role: "radiogroup", "aria-labelledby": idRotulo, "aria-describedby": [idDescricao, idErro].filter(Boolean).join(" ") || undefined, "aria-orientation": orientation, "aria-required": required || undefined, "aria-invalid": error || invalid ? true : undefined, className: cx("radio-group", orientation === "horizontal" && "radio-group-horizontal", labelHidden && "radio-group-bare", className), ...props, children: [_jsx("span", { className: cx("label", labelHidden && "sr-only"), id: idRotulo, children: _jsxs("span", { children: [label, required ? _jsx("span", { className: "label-required", "aria-hidden": "true", children: "*" }) : null] }) }), description && _jsx("span", { className: "field-description", id: idDescricao, children: description }), orientation === "horizontal" ? _jsx("div", { className: "radio-group-items", children: opcoes }) : opcoes, error && _jsx("span", { className: "field-error", id: idErro, children: error })] });
460
+ }
461
+ /** A opção do grupo: o `Radio`, como o `RadioGroup.Item` do nativo. */
462
+ RadioGroup.Item = Radio;
435
463
  const ehSecao = (x) => "items" in x;
436
464
  const textoDoItem = (it) => it.textValue ?? (typeof it.label === "string" ? it.label : "");
437
465
  export function ListBox({ label, items, selectionMode = "single", value, defaultValue, onValueChange, onAction, className, ...props }) {
@@ -502,7 +530,7 @@ export function ListBox({ label, items, selectionMode = "single", value, default
502
530
  refs.current.delete(it.id); }, role: "option", "aria-selected": selectionMode === "none" ? undefined : escolhido, "aria-disabled": it.disabled || undefined, "aria-describedby": idDesc, tabIndex: it.id === alvo ? 0 : -1, className: cx("list-box-item", it.danger && "list-box-item-danger"), onClick: () => { if (!it.disabled) {
503
531
  setAtivo(it.id);
504
532
  escolher(it);
505
- } }, onKeyDown: e => aoTeclar(e, it), onFocus: () => setAtivo(it.id), children: [it.icon && _jsx(Icon, { name: it.icon }), _jsxs("span", { className: "list-box-item-text", children: [_jsx("span", { className: "list-box-item-label", children: it.label }), it.description && _jsx("span", { className: "list-box-item-description", id: idDesc, children: it.description })] }), escolhido && _jsx(Icon, { name: "check", className: "list-box-item-indicator" })] }, it.id);
533
+ } }, onKeyDown: e => aoTeclar(e, it), onFocus: () => setAtivo(it.id), children: [it.icon && _jsx(Icon, { name: it.icon }), _jsxs("span", { className: "list-box-item-text", children: [_jsx("span", { className: "list-box-item-label", children: it.label }), it.description && _jsx("span", { className: "list-box-item-description", id: idDesc, children: it.description })] }), it.kbd && _jsx(Kbd, { className: "list-box-item-kbd", children: it.kbd }), escolhido && _jsx(Icon, { name: "check", className: "list-box-item-indicator" })] }, it.id);
506
534
  };
507
535
  return _jsx("div", { role: "listbox", "aria-label": label, "aria-multiselectable": selectionMode === "multiple" || undefined, className: cx("list-box", className), ...props, children: items.map((x, i) => ehSecao(x)
508
536
  ? _jsxs("div", { role: "group", "aria-labelledby": `${auto}-s${i}`, className: "list-box-section", children: [_jsx("span", { className: "list-box-section-title", id: `${auto}-s${i}`, role: "presentation", children: x.title }), x.items.map(item)] }, `s${i}`)
@@ -682,7 +710,7 @@ export function ColorPicker({ label, value, defaultValue, onValueChange, onValue
682
710
  // O foco fica PRESO na janela (`trap-focus`), como na referência: com quatro peças dentro, o Tab que saía depois
683
711
  // do código fechava a janela (medido no navegador). O motor pede um botão de fechar dentro, para o leitor de tela
684
712
  // por toque sair; ele fica invisível e fora do Tab — quem usa teclado tem o Esc, e o foco nunca some na tela.
685
- return _jsxs(BasePopover.Root, { modal: "trap-focus", children: [_jsxs(BasePopover.Trigger, { disabled: disabled, id: id, "aria-label": `${label}, ${nome}`, className: cx("color-picker-trigger", className), children: [_jsx(ColorSwatch, { color: hex, "aria-hidden": "true" }), _jsx("span", { children: label })] }), name && _jsx("input", { type: "hidden", name: name, value: hex }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { side: "bottom", align: "start", sideOffset: 6, children: _jsxs(BasePopover.Popup, { className: "popover color-picker-popover", "aria-label": label, children: [_jsx(CorDoSeletor.Provider, { value: contexto, children: children ?? _jsxs(_Fragment, { children: [variant !== "swatches" && _jsxs(_Fragment, { children: [_jsx(AreaDeCor, { cor: cor, onCor: mudar, space: "hsb", x: "saturation", y: "brightness", "aria-label": label }), _jsx(TrilhaDeCor, { cor: cor, onCor: mudar, channel: "hue", label: s.colorChannel.hue, labelHidden: true, showValue: false })] }), amostras && _jsx(ColorSwatch, { options: amostras, name: `${idGrupo}-amostras`, label: s.colorSwatches, value: escolhida, onChange: (e) => mudar(corDoTexto(e.target.value, cor), true) }), _jsx(ColorField, { "aria-label": s.colorHex })] }) }), _jsx(BasePopover.Close, { className: "sr-only", tabIndex: -1, children: s.close })] }) }) })] });
713
+ return _jsxs(BasePopover.Root, { modal: "trap-focus", children: [_jsxs(BasePopover.Trigger, { disabled: disabled, id: id, "aria-label": `${label}, ${nome}`, className: cx("color-picker-trigger", className), children: [_jsx(ColorSwatch, { color: hex, "aria-hidden": "true" }), _jsx("span", { children: label })] }), name && _jsx("input", { type: "hidden", name: name, value: hex }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { className: "positioner", side: "bottom", align: "start", sideOffset: 6, children: _jsxs(BasePopover.Popup, { className: "popover color-picker-popover", "aria-label": label, children: [_jsx(CorDoSeletor.Provider, { value: contexto, children: children ?? _jsxs(_Fragment, { children: [variant !== "swatches" && _jsxs(_Fragment, { children: [_jsx(AreaDeCor, { cor: cor, onCor: mudar, space: "hsb", x: "saturation", y: "brightness", "aria-label": label }), _jsx(TrilhaDeCor, { cor: cor, onCor: mudar, channel: "hue", label: s.colorChannel.hue, labelHidden: true, showValue: false })] }), amostras && _jsx(ColorSwatch, { options: amostras, name: `${idGrupo}-amostras`, label: s.colorSwatches, value: escolhida, onChange: (e) => mudar(corDoTexto(e.target.value, cor), true) }), _jsx(ColorField, { "aria-label": s.colorHex })] }) }), _jsx(BasePopover.Close, { className: "sr-only", tabIndex: -1, children: s.close })] }) }) })] });
686
714
  }
687
715
  // OTPField (Lote 1 do BUILDING.md). Um campo por dígito, com o comportamento que ninguém acerta
688
716
  // à mão: colar o código inteiro distribui pelos campos, Backspace volta um, e o
@@ -695,9 +723,12 @@ export function ColorPicker({ label, value, defaultValue, onValueChange, onValue
695
723
  // formulário anônimos. Um leitor de tela anunciaria "editar texto" seis vezes seguidas sem
696
724
  // dizer qual é qual. O grupo continua nomeado (`role="group"`), que é o que diz para que serve
697
725
  // o código; o nome de cada caixa é o que diz onde você está.
698
- export function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className }) {
726
+ // 15/10/2026 (o Victor: "A, B, C, pode"), como o campo de código da referência: `groupSize` põe o TRACINHO a cada N
727
+ // caixas (o 3 + 3 da vitrine dela; o `Separator` do motor, de 6 × 2), e `variant="secondary"` dá às caixas o fundo
728
+ // neutro do `Input` secundário. Sem os dois, a marcação é a de antes.
729
+ export function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className, groupSize, variant }) {
699
730
  const s = useAureaStrings();
700
- return _jsx(BaseOTPField.Root, { id: id, length: length, value: value, defaultValue: defaultValue, onValueChange: soOValor(onValueChange), mask: mask, disabled: disabled, required: required, role: "group", "aria-label": label, className: cx("otp-field", className), children: Array.from({ length }, (_, i) => _jsx(BaseOTPField.Input, { render: _jsx("input", { "aria-label": `${s.otpDigit} ${i + 1}` }), className: "input otp-slot" }, i)) });
731
+ return _jsx(BaseOTPField.Root, { id: id, length: length, value: value, defaultValue: defaultValue, onValueChange: soOValor(onValueChange), mask: mask, disabled: disabled, required: required, role: "group", "aria-label": label, className: cx("otp-field", className), children: Array.from({ length }, (_, i) => _jsxs(React.Fragment, { children: [_jsx(BaseOTPField.Input, { render: _jsx("input", { "aria-label": `${s.otpDigit} ${i + 1}` }), className: cx("input otp-slot", variant === "secondary" && "input-secondary") }), groupSize && groupSize > 0 && (i + 1) % groupSize === 0 && i < length - 1 ? _jsx(BaseOTPField.Separator, { className: "otp-separator" }) : null] }, i)) });
701
732
  }
702
733
  // SegmentedControl — achado M20, fechado na Parte C do PLANO-1.0 em 07/08/2026.
703
734
  //
@@ -759,7 +790,7 @@ export function Combobox({ items, value, onValueChange, placeholder, label, empt
759
790
  const portal = usePortalContainer();
760
791
  const autoId = React.useId();
761
792
  const inputId = id ?? autoId;
762
- return _jsxs(BaseCombobox.Root, { items: items, value: value, onValueChange: soOValor(onValueChange), disabled: disabled, itemToStringLabel: (i) => i.label, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-group", children: [_jsx(BaseCombobox.Input, { id: inputId, placeholder: placeholder, className: cx("input", peleDoEixo("input", size)), "aria-describedby": descrito, "aria-invalid": invalido, "aria-label": rotulo }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "caret-down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { container: portal, children: _jsx(BaseCombobox.Positioner, { sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: empty ?? s.comboboxEmpty }), listaCombobox(items)] }) }) })] });
793
+ return _jsxs(BaseCombobox.Root, { items: items, value: value, onValueChange: soOValor(onValueChange), disabled: disabled, itemToStringLabel: (i) => i.label, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-group", children: [_jsx(BaseCombobox.Input, { id: inputId, placeholder: placeholder, className: cx("input", peleDoEixo("input", size)), "aria-describedby": descrito, "aria-invalid": invalido, "aria-label": rotulo }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "caret-down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { container: portal, children: _jsx(BaseCombobox.Positioner, { className: "positioner", sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: empty ?? s.comboboxEmpty }), listaCombobox(items)] }) }) })] });
763
794
  }
764
795
  // MultiCombobox (Fase 4): multi-seleção com chips, opções agrupadas e filtro
765
796
  // externo (async). Reusa .combobox-popup/-item/-empty do básico.
@@ -774,7 +805,7 @@ export function MultiCombobox({ items, value, onValueChange, onInputChange, load
774
805
  const autoId = React.useId();
775
806
  const inputId = id ?? autoId;
776
807
  return _jsxs(BaseCombobox.Root, { multiple: true, items: items, value: value, onValueChange: soOValor(onValueChange), disabled: disabled, itemToStringLabel: (i) => i.label, filter: onInputChange ? null : undefined, onInputValueChange: onInputChange ? ((v, d) => { if (d.reason !== "item-press")
777
- onInputChange(v); }) : undefined, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-multi", children: [_jsx(BaseCombobox.Chips, { className: "combobox-chips", children: _jsx(BaseCombobox.Value, { children: (selected) => _jsxs(_Fragment, { children: [selected.map((item) => _jsxs(BaseCombobox.Chip, { className: "combobox-chip", children: [item.label, _jsx(BaseCombobox.ChipRemove, { className: "combobox-chip-remove", "aria-label": `${s.comboboxRemove} ${item.label}`, children: _jsx(Icon, { name: "x", size: "sm" }) })] }, item.value)), _jsx(BaseCombobox.Input, { id: inputId, placeholder: selected.length ? undefined : placeholder, className: "combobox-chip-input", "aria-describedby": descrito, "aria-invalid": invalido, "aria-label": rotulo })] }) }) }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "caret-down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { container: portal, children: _jsx(BaseCombobox.Positioner, { sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: loading ? s.comboboxLoading : (empty ?? s.comboboxEmpty) }), listaCombobox(items)] }) }) })] });
808
+ onInputChange(v); }) : undefined, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-multi", children: [_jsx(BaseCombobox.Chips, { className: "combobox-chips", children: _jsx(BaseCombobox.Value, { children: (selected) => _jsxs(_Fragment, { children: [selected.map((item) => _jsxs(BaseCombobox.Chip, { className: "combobox-chip", children: [item.label, _jsx(BaseCombobox.ChipRemove, { className: "combobox-chip-remove", "aria-label": `${s.comboboxRemove} ${item.label}`, children: _jsx(Icon, { name: "x", size: "sm" }) })] }, item.value)), _jsx(BaseCombobox.Input, { id: inputId, placeholder: selected.length ? undefined : placeholder, className: "combobox-chip-input", "aria-describedby": descrito, "aria-invalid": invalido, "aria-label": rotulo })] }) }) }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "caret-down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { container: portal, children: _jsx(BaseCombobox.Positioner, { className: "positioner", sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: loading ? s.comboboxLoading : (empty ?? s.comboboxEmpty) }), listaCombobox(items)] }) }) })] });
778
809
  }
779
810
  const ehGrupoDoSelect = (i) => typeof i === "object" && i != null && "items" in i;
780
811
  // Lê `<option>`/`<optgroup>` como o navegador lê: `value` ausente vale o TEXTO da opção.
@@ -804,7 +835,7 @@ export const Select = forwardRef(function Select({ items, children, value, defau
804
835
  onValueChange?.(valor);
805
836
  onChange?.({ target: { value: valor, name }, currentTarget: { value: valor, name } });
806
837
  };
807
- return _jsxs(BaseSelect.Root, { items: planas.map(o => ({ value: o.value, label: o.label })), ...(value !== undefined ? { value } : { defaultValue: inicial }), onValueChange: v => mudar(v), name: name, required: required, disabled: disabled, children: [_jsx(BaseSelect.Trigger, { ref: ref, className: cx("select", "select-trigger", peleDoEixo("select", size), className), ...props, children: _jsx(BaseSelect.Value, { placeholder: placeholder, className: "select-value" }) }), _jsx(BaseSelect.Portal, { container: portal, children: _jsx(BaseSelect.Positioner, { sideOffset: 6, alignItemWithTrigger: false, children: _jsx(BaseSelect.Popup, { className: "menu combobox-popup", children: _jsx(BaseSelect.List, { children: lista.map((i, n) => ehGrupoDoSelect(i)
838
+ return _jsxs(BaseSelect.Root, { items: planas.map(o => ({ value: o.value, label: o.label })), ...(value !== undefined ? { value } : { defaultValue: inicial }), onValueChange: v => mudar(v), name: name, required: required, disabled: disabled, children: [_jsx(BaseSelect.Trigger, { ref: ref, className: cx("select", "select-trigger", peleDoEixo("select", size), className), ...props, children: _jsx(BaseSelect.Value, { placeholder: placeholder, className: "select-value" }) }), _jsx(BaseSelect.Portal, { container: portal, children: _jsx(BaseSelect.Positioner, { className: "positioner", sideOffset: 6, alignItemWithTrigger: false, children: _jsx(BaseSelect.Popup, { className: "menu combobox-popup", children: _jsx(BaseSelect.List, { children: lista.map((i, n) => ehGrupoDoSelect(i)
808
839
  ? _jsxs(BaseSelect.Group, { className: "combobox-section", children: [_jsx(BaseSelect.GroupLabel, { className: "combobox-group-label", children: i.label }), i.items.map(itemDoSelect)] }, n)
809
840
  : itemDoSelect(i)) }) }) }) })] });
810
841
  });
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
- import { type AureaStrings, type AureaTheme, type AureaDensity } from "./pure.js";
3
- export { cx, fundirRender, defaultStrings, ptBR, defaultSpriteUrl, universalStates, stateSeverity, type AureaStrings, type UniversalState, type AureaTheme, type AureaDensity } from "./pure.js";
2
+ import { type AureaStrings, type AureaTheme, type AureaThemePreference, type AureaDensity } from "./pure.js";
3
+ export { cx, fundirRender, defaultStrings, ptBR, defaultSpriteUrl, universalStates, stateSeverity, type AureaStrings, type UniversalState, type AureaTheme, type AureaThemePreference, type AureaDensity } from "./pure.js";
4
4
  export declare const controlesRotulaveis: WeakSet<object>;
5
5
  export declare const StringsContext: React.Context<AureaStrings>;
6
6
  export declare const useAureaStrings: () => AureaStrings;
@@ -8,14 +8,16 @@ export declare const SpriteContext: React.Context<string>;
8
8
  export declare const useSpriteUrl: () => string;
9
9
  export declare const ThemeContext: React.Context<{
10
10
  theme?: AureaTheme;
11
- setTheme: (t: AureaTheme) => void;
11
+ preference?: AureaThemePreference;
12
+ setTheme: (t: AureaThemePreference) => void;
12
13
  toggleTheme: () => void;
13
14
  }>;
14
15
  /** Tema atual e como trocá-lo. Fora de um `AureaProvider`, devolve `undefined` e no-ops — não
15
16
  * lança: um componente isolado num teste não deve morrer por falta de provider. */
16
17
  export declare const useTheme: () => {
17
18
  theme?: AureaTheme;
18
- setTheme: (t: AureaTheme) => void;
19
+ preference?: AureaThemePreference;
20
+ setTheme: (t: AureaThemePreference) => void;
19
21
  toggleTheme: () => void;
20
22
  };
21
23
  export declare const DensityContext: React.Context<{
package/dist/internal.js CHANGED
@@ -35,7 +35,8 @@ export const useSpriteUrl = () => useContext(SpriteContext);
35
35
  // diferente de "escuro". Um default concreto aqui faria o provider sobrescrever, na primeira
36
36
  // pintura, o `data-theme` que o documento já traz no markup — e esse atributo existe justamente
37
37
  // para não haver piscada de tema ao carregar.
38
- export const ThemeContext = createContext({ theme: undefined, setTheme: () => { }, toggleTheme: () => { } });
38
+ // `preference` (15/10/2026): o que foi ESCOLHIDO, que pode ser `"system"`; `theme` é o que está na tela.
39
+ export const ThemeContext = createContext({ theme: undefined, preference: undefined, setTheme: () => { }, toggleTheme: () => { } });
39
40
  /** Tema atual e como trocá-lo. Fora de um `AureaProvider`, devolve `undefined` e no-ops — não
40
41
  * lança: um componente isolado num teste não deve morrer por falta de provider. */
41
42
  export const useTheme = () => useContext(ThemeContext);