@aurea-uds/react 0.5.0 → 0.7.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,10 +1,24 @@
1
1
  import React, { type ButtonHTMLAttributes, type HTMLAttributes, type RefAttributes } from "react";
2
+ import { type Orientation, type Responsive } from "./pure.js";
2
3
  import { type IconName } from "./system.js";
3
- export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "danger" | "danger-outline" | "danger-ghost" | "link" | "link-primary" | "link-danger" | "nav";
4
+ export type ButtonVariant = "primary" | "secondary" | "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";
6
+ export type ButtonTone = "neutral" | "brand" | "danger" | "success" | "warning" | "info";
4
7
  export type ComponentSize = "xs" | "sm" | "md" | "lg" | "xl";
8
+ /** Resolve os dois eixos (ou o atalho) na classe que o core pinta. Exportada porque o teste
9
+ * cobra a matriz inteira, e porque quem monta um botão à mão precisa da mesma conta.
10
+ *
11
+ * Não há degradação: toda célula dos dois eixos tem regra própria no core. A versão de
12
+ * 21/08 rebaixava `solid` de success/info para `outline` porque faltava o par de token — o
13
+ * Victor recusou a degradação silenciosa e mandou consertar a infraestrutura, que é o que
14
+ * `--success`/`--success-foreground` e os irmãos são. `outline` voltou a ser só uma aparência
15
+ * que se pede, nunca o que sobra quando a nossa paleta não dá conta. */
16
+ export declare function buttonSkin(variant?: ButtonVariant, appearance?: ButtonAppearance, tone?: ButtonTone): string;
5
17
  export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, RefAttributes<HTMLButtonElement> {
6
18
  variant?: ButtonVariant;
7
- size?: ComponentSize;
19
+ appearance?: ButtonAppearance;
20
+ tone?: ButtonTone;
21
+ size?: Responsive<ComponentSize>;
8
22
  loading?: boolean;
9
23
  leadingIcon?: IconName;
10
24
  trailingIcon?: IconName;
@@ -29,9 +43,11 @@ export declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "
29
43
  interface ToggleBase {
30
44
  pressed?: boolean;
31
45
  defaultPressed?: boolean;
32
- onPressedChange?: (pressed: boolean) => void;
46
+ onPressedChange?: (pressed: boolean) => void; /** O que identifica este toggle DENTRO de um ToggleGroup — sem ele o grupo não sabe qual botão
47
+ * mudou, e a composição fica bonita e morta. Solto, não faz diferença nenhuma. */
48
+ value?: string;
33
49
  icon?: IconName;
34
- size?: Extract<ComponentSize, "sm" | "md" | "lg">;
50
+ size?: Responsive<Extract<ComponentSize, "sm" | "md" | "lg">>;
35
51
  disabled?: boolean;
36
52
  id?: string;
37
53
  className?: string;
@@ -44,17 +60,18 @@ export type ToggleProps = ToggleBase & ({
44
60
  children?: never;
45
61
  label: string;
46
62
  });
47
- export declare function Toggle({ pressed, defaultPressed, onPressedChange, icon, label, size, disabled, id, className, children }: ToggleProps): React.JSX.Element;
63
+ export declare function Toggle({ pressed, defaultPressed, onPressedChange, value, icon, label, size, disabled, id, className, children }: ToggleProps): React.JSX.Element;
48
64
  export interface IconButtonProps extends Omit<ButtonProps, "children"> {
49
65
  label: string;
50
66
  icon: IconName;
51
67
  }
52
68
  export declare const IconButton: React.ForwardRefExoticComponent<Omit<IconButtonProps, "ref"> & RefAttributes<HTMLButtonElement>>;
53
- export declare function ButtonGroup({ label, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
69
+ export declare function ButtonGroup({ label, orientation, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
54
70
  label?: string;
71
+ orientation?: Responsive<Orientation>;
55
72
  }): React.JSX.Element;
56
73
  export declare function Toolbar({ orientation, label, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
57
- orientation?: "horizontal" | "vertical";
74
+ orientation?: Responsive<Orientation>;
58
75
  label?: string;
59
76
  }): React.JSX.Element;
60
77
  export declare function ToolbarGroup({ label, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
@@ -62,4 +79,15 @@ export declare function ToolbarGroup({ label, className, ...props }: HTMLAttribu
62
79
  }): React.JSX.Element;
63
80
  export declare function ToolbarSeparator({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): React.JSX.Element;
64
81
  export declare const ToolbarButton: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & RefAttributes<HTMLButtonElement>>;
82
+ export declare function ToggleGroup({ value, defaultValue, onValueChange, multiple, orientation, disabled, label, className, children }: {
83
+ value?: string[];
84
+ defaultValue?: string[];
85
+ onValueChange?: (value: string[]) => void;
86
+ multiple?: boolean;
87
+ orientation?: Responsive<Orientation>;
88
+ disabled?: boolean;
89
+ label?: string;
90
+ className?: string;
91
+ children: React.ReactNode;
92
+ }): React.JSX.Element;
65
93
  export {};
package/dist/actions.js CHANGED
@@ -3,17 +3,57 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
3
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
4
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
5
5
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
6
- import React, { forwardRef } from "react";
6
+ import React, { useRef, forwardRef } from "react";
7
+ import { classesResponsivas, ehResponsivo, peleDoEixo, soOValor, valorBase } from "./pure.js";
8
+ import { useValorResponsivo } from "./responsivo-runtime.js";
7
9
  import { Toolbar as BaseToolbar } from "@base-ui/react/toolbar";
8
10
  import { Toggle as BaseToggle } from "@base-ui/react/toggle";
11
+ import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group";
9
12
  import { cx, useAureaStrings } from "./internal.js";
10
13
  import { Kbd } from "./markup.js";
11
14
  import { Icon } from "./system.js";
15
+ // O dicionário que desachata o enum. É a ÚNICA fonte da correspondência: o gate do
16
+ // `validate.py` lê daqui para provar que todo par ou tem regra no core ou está declarado
17
+ // ausente com motivo, e a ficha do registry declara os mesmos dois eixos.
18
+ const EIXOS = {
19
+ primary: ["solid", "brand"], secondary: ["solid", "neutral"],
20
+ outline: ["outline", "neutral"], ghost: ["ghost", "neutral"],
21
+ "primary-outline": ["outline", "brand"], "primary-ghost": ["ghost", "brand"],
22
+ danger: ["solid", "danger"], "danger-outline": ["outline", "danger"], "danger-ghost": ["ghost", "danger"],
23
+ link: ["link", "neutral"], "link-primary": ["link", "brand"], "link-danger": ["link", "danger"],
24
+ nav: ["nav", "neutral"]
25
+ };
26
+ // caminho de volta: par → nome antigo. Existir aqui significa "o core já pinta esta célula com
27
+ // uma classe própria", e é o que garante que NENHUMA baseline se mexe: `appearance="outline"`
28
+ // + `tone="danger"` emite `.btn-danger-outline`, o mesmo DOM de sempre.
29
+ const CLASSE_ANTIGA = new Map(Object.entries(EIXOS)
30
+ .map(([v, [a, t]]) => [`${a}/${t}`, v]));
31
+ /** Resolve os dois eixos (ou o atalho) na classe que o core pinta. Exportada porque o teste
32
+ * cobra a matriz inteira, e porque quem monta um botão à mão precisa da mesma conta.
33
+ *
34
+ * Não há degradação: toda célula dos dois eixos tem regra própria no core. A versão de
35
+ * 21/08 rebaixava `solid` de success/info para `outline` porque faltava o par de token — o
36
+ * Victor recusou a degradação silenciosa e mandou consertar a infraestrutura, que é o que
37
+ * `--success`/`--success-foreground` e os irmãos são. `outline` voltou a ser só uma aparência
38
+ * que se pede, nunca o que sobra quando a nossa paleta não dá conta. */
39
+ export function buttonSkin(variant, appearance, tone) {
40
+ const [a0, t0] = EIXOS[variant ?? "secondary"];
41
+ const a = appearance ?? a0, t = tone ?? t0;
42
+ const antiga = CLASSE_ANTIGA.get(`${a}/${t}`);
43
+ return antiga ? `btn-${antiga}` : cx(`btn-${a}`, `btn-tone-${t}`);
44
+ }
12
45
  // type="button" por default: o default do HTML é submit, e um "Cancelar"/"Remover"
13
46
  // dentro de <form> dispararia a ação principal (auditoria 18/07/2026, ALTO 1).
14
47
  // Quem quer submeter passa type="submit" explícito (como o MessageComposer faz).
15
- export const Button = forwardRef(function Button({ variant = "secondary", size = "md", loading, leadingIcon, trailingIcon, className, children, disabled, type = "button", href, fullWidth, pressed, kbd, onClick, onClickCapture, "aria-disabled": ariaDisabled, ...props }, ref) {
16
- const cls = cx("btn", `btn-${variant}`, size !== "md" && `btn-${size}`, fullWidth && "btn-block", className);
48
+ export const Button = forwardRef(function Button({ variant, appearance, tone, size = "md", loading, leadingIcon, trailingIcon, className, children, disabled, type = "button", href, fullWidth, pressed, kbd, onClick, onClickCapture, "aria-disabled": ariaDisabled, ...props }, ref) {
49
+ // G-AXIS-04 `size` aceita valor simples, responsivo por viewport ou adaptativo por container.
50
+ // SOMATIVO por construção: valor simples continua emitindo `btn-sm`, byte por byte a mesma classe
51
+ // de antes, e por isso nenhuma baseline se mexe. Só o valor responsivo entra pela camada genérica
52
+ // (`size-*` + `vp-*:`/`ct-*:`), que existe uma vez no core para todo o sistema.
53
+ // Quem decide é o CSS: nada aqui observa largura, e o HTML do servidor já sai correto.
54
+ const responsivo = ehResponsivo(size);
55
+ const base = valorBase(size) ?? "md";
56
+ const cls = cx("btn", buttonSkin(variant, appearance, tone), responsivo ? classesResponsivas("size", size) : (base !== "md" && `btn-${base}`), fullWidth && "btn-block", className);
17
57
  // kbd dentro do botão: mostra o atalho E o anuncia (aria-keyshortcuts), senão é enfeite.
18
58
  const inner = _jsxs(_Fragment, { children: [loading && _jsx("span", { className: "spinner" }), leadingIcon && _jsx(Icon, { name: leadingIcon }), _jsx("span", { children: children }), kbd && _jsx(Kbd, { children: kbd }), trailingIcon && _jsx(Icon, { name: trailingIcon })] });
19
59
  // INERTE ≠ DESABILITADO, e a diferença é medida (M4, 13/08/2026): `disabled` tira o botão da
@@ -67,7 +107,7 @@ function temConteudoVisivel(children) {
67
107
  // entre os estados**. Se o texto vira "Mute"/"Unmute" ou "Play"/"Pause", não é toggle — é
68
108
  // botão de ação, porque quem lê tela ouve o rótulo NOVO e o estado ao mesmo tempo e não sabe
69
109
  // se o botão descreve o que é ou o que fará.
70
- export function Toggle({ pressed, defaultPressed, onPressedChange, icon, label, size = "md", disabled, id, className, children }) {
110
+ export function Toggle({ pressed, defaultPressed, onPressedChange, value, icon, label, size, disabled, id, className, children }) {
71
111
  // O tipo barra o consumidor TypeScript; este aviso barra o de JavaScript, que não tem tipo nenhum.
72
112
  // Sem gate por NODE_ENV de propósito: o `dist` é saída de `tsc`, então `process` não existe no
73
113
  // navegador e a referência quebraria o render — e um controle sem nome merece aparecer em produção
@@ -78,16 +118,45 @@ export function Toggle({ pressed, defaultPressed, onPressedChange, icon, label,
78
118
  const rotuloVisivel = typeof label === "string" && label.trim().length > 0;
79
119
  if (!textoVisivel && !rotuloVisivel)
80
120
  console.error("Aurea: <Toggle> sem `children` visível e sem `label` não tem nome acessível — quem usa leitor de tela encontra um botão anônimo. Passe `label` quando o toggle for só ícone.");
81
- return _jsxs(BaseToggle, { id: id, disabled: disabled, pressed: pressed, defaultPressed: defaultPressed, onPressedChange: onPressedChange, "aria-label": textoVisivel ? undefined : (rotuloVisivel ? label : undefined), className: cx("btn", "btn-ghost", size !== "md" && `btn-${size}`, !textoVisivel && "btn-icon", "toggle", className), children: [icon && _jsx(Icon, { name: icon }), children] });
121
+ return _jsxs(BaseToggle, { id: id, value: value, disabled: disabled, pressed: pressed, defaultPressed: defaultPressed, onPressedChange: soOValor(onPressedChange), "aria-label": textoVisivel ? undefined : (rotuloVisivel ? label : undefined), className: cx("btn", "btn-ghost", size !== "md" && `btn-${size}`, !textoVisivel && "btn-icon", "toggle", className), children: [icon && _jsx(Icon, { name: icon }), children] });
82
122
  }
83
123
  // default ghost (não secondary): um ícone-ação solto — hambúrguer, tema, fechar — é sem
84
124
  // caixa por convenção (pedido do Victor: hambúrguer sem borda). Quem quer a caixa passa
85
125
  // variant. Alinha o React ao HTML dos docs, onde .btn-icon já é transparente.
86
126
  export const IconButton = forwardRef(function IconButton({ label, icon, variant = "ghost", className, ...props }, ref) { return _jsx(Button, { ref: ref, variant: variant, className: cx("btn-icon", className), "aria-label": label, ...props, children: _jsx(Icon, { name: icon }) }); });
87
127
  // ButtonGroup: agrupamento semântico. Sem roving tabindex — cada botão continua tabulável (use Toolbar para roving).
88
- export function ButtonGroup({ label, className, ...props }) { const s = useAureaStrings(); return _jsx("div", { role: "group", "aria-label": label ?? s.optionsLabel, className: cx("btn-group", className), ...props }); }
128
+ // `orientation` (G-AXIS-01) muda o EIXO do layout, não a semântica: `role="group"` não tem
129
+ // noção de direção, e por isso — ao contrário do `Toolbar` e do `ToggleGroup`, que navegam por
130
+ // seta e precisam saber para que lado a seta anda — aqui não há `aria-orientation`. Anunciar
131
+ // orientação num grupo que não navega seria prometer teclado que não existe.
132
+ export function ButtonGroup({ label, orientation, className, ...props }) { const s = useAureaStrings(); return _jsx("div", { role: "group", "aria-label": label ?? s.optionsLabel, className: cx("btn-group", peleDoEixo("btn-group", orientation, "horizontal", "btn-group"), className), ...props }); }
89
133
  // Toolbar: roving tabindex (setas navegam, Tab entra/sai) via Base UI.
90
- export function Toolbar({ orientation = "horizontal", label, className, ...props }) { const s = useAureaStrings(); return _jsx(BaseToolbar.Root, { orientation: orientation, "aria-label": label ?? s.toolbarLabel, className: cx("toolbar", className), ...props }); }
134
+ // G-AXIS-06 o valor responsivo RESOLVIDO é a única fonte de verdade: ele vai para o motor, o
135
+ // motor publica `data-orientation`/`aria-orientation` e ajusta o teclado, e a pele reage ao
136
+ // atributo publicado. Nada de o CSS decidir a orientação por breakpoint enquanto o atributo
137
+ // diz outra coisa — seriam duas fontes de verdade e uma janela em que visual e comportamento
138
+ // divergem. Ver `decisions/0019-css-first-para-apresentacao-runtime-para-semantica.md`.
139
+ export function Toolbar({ orientation, label, className, ...props }) {
140
+ const s = useAureaStrings();
141
+ const ancora = useRef(null);
142
+ const resolvida = useValorResponsivo(orientation, "horizontal", ancora);
143
+ return _jsx(BaseToolbar.Root, { ref: ancora, orientation: resolvida, "aria-label": label ?? s.toolbarLabel, className: cx("toolbar", className), ...props });
144
+ }
91
145
  export function ToolbarGroup({ label, className, ...props }) { return _jsx(BaseToolbar.Group, { "aria-label": label, className: cx("toolbar-group", className), ...props }); }
92
146
  export function ToolbarSeparator({ className, ...props }) { return _jsx(BaseToolbar.Separator, { className: cx("toolbar-sep", className), ...props }); }
93
147
  export const ToolbarButton = forwardRef(function ToolbarButton({ variant = "ghost", ...props }, ref) { return _jsx(BaseToolbar.Button, { ref: ref, disabled: props.disabled, render: _jsx(Button, { variant: variant, ...props }) }); });
148
+ // TOGGLE GROUP — vários Toggle com estado compartilhado e navegação por seta entre eles.
149
+ //
150
+ // Não é o SegmentedControl, e confundir os dois constrói a coisa errada com o nome certo: o
151
+ // `SegmentedControl` é escolha ÚNICA e obrigatória entre alternativas de uma mesma dimensão
152
+ // ("dia / semana / mês") e se parece com uma cápsula; aqui cada botão liga e desliga por conta
153
+ // própria, `multiple` permite vários ao mesmo tempo, e zero selecionado é estado legítimo. É a
154
+ // barra de formatação de um editor, não um filtro de período.
155
+ //
156
+ // O motor dá o roving tabindex, o `loopFocus` e o contrato de pressionado; a pele é a do
157
+ // `.btn-group`, porque um grupo de botões é o que isto é.
158
+ export function ToggleGroup({ value, defaultValue, onValueChange, multiple, orientation, disabled, label, className, children }) {
159
+ const ancora = useRef(null);
160
+ const resolvida = useValorResponsivo(orientation, "horizontal", ancora);
161
+ return _jsx(BaseToggleGroup, { ref: ancora, value: value, defaultValue: defaultValue, onValueChange: soOValor(onValueChange), multiple: multiple, orientation: resolvida, disabled: disabled, "aria-label": label, className: cx("toggle-group", className), children: children });
162
+ }
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
6
6
  import React from "react";
7
7
  import { cx, useAureaStrings } from "./internal.js";
8
+ import { InputGroup, InputGroupAddon } from "./inputs.js";
8
9
  import { Icon } from "./system.js";
9
10
  import { IconButton } from "./actions.js";
10
11
  import { Avatar } from "./identity.js";
@@ -13,7 +14,7 @@ export function MessageList({ messages, label, className }) {
13
14
  const s = useAureaStrings();
14
15
  return _jsx("div", { className: cx("stack", className), role: "log", "aria-live": "polite", "aria-label": label ?? s.chatLabel, children: messages.map(m => _jsxs("div", { className: "message", children: [m.avatar ? _jsx(Avatar, { src: m.avatar.src, fallback: m.avatar.fallback }) : _jsx("span", {}), _jsxs("div", { className: "message-bubble", children: [(m.author || m.time) && _jsxs("div", { className: "label", children: [_jsx("span", { children: m.author }), m.time && _jsx("span", { className: "hint", children: m.time })] }), m.body, m.status && _jsx("div", { className: "message-status", children: _jsxs(Badge, { variant: m.status.variant, children: [_jsx("i", { className: "status-dot" }), m.status.label] }) })] })] }, m.id)) });
15
16
  }
16
- // Composer: <form> sobre .input-wrap + .input; Enter envia (submit nativo) e limpa.
17
+ // Composer: <form> sobre InputGroup + .input; Enter envia (submit nativo) e limpa.
17
18
  // Texto é estado interno (não controlado — controlar de fora só com demanda real,
18
19
  // precedente MediaPlayer/FileInput). Não envia texto vazio/só-espaço; o botão de
19
20
  // envio desabilita enquanto vazio. O input sempre tem nome acessível (aria-label),
@@ -24,5 +25,5 @@ export function MessageComposer({ onSend, placeholder, label, icon, sendLabel, d
24
25
  const [text, setText] = React.useState("");
25
26
  const submit = (e) => { e.preventDefault(); const t = text.trim(); if (!t)
26
27
  return; onSend(t); setText(""); };
27
- return _jsxs("form", { className: cx("message-composer", className), onSubmit: submit, children: [_jsxs("div", { className: "input-wrap", children: [icon && _jsx(Icon, { name: icon }), _jsx("input", { className: "input", value: text, disabled: disabled, placeholder: placeholder, "aria-label": label ?? s.chatMessage, onChange: e => setText(e.target.value) })] }), _jsx(IconButton, { type: "submit", variant: "primary", icon: "send", label: sendLabel ?? s.chatSend, disabled: disabled || !text.trim() })] });
28
+ return _jsxs("form", { className: cx("message-composer", className), onSubmit: submit, children: [_jsxs(InputGroup, { children: [icon && _jsx(InputGroupAddon, { children: _jsx(Icon, { name: icon }) }), _jsx("input", { className: "input", value: text, disabled: disabled, placeholder: placeholder, "aria-label": label ?? s.chatMessage, onChange: e => setText(e.target.value) })] }), _jsx(IconButton, { type: "submit", variant: "primary", icon: "send", label: sendLabel ?? s.chatSend, disabled: disabled || !text.trim() })] });
28
29
  }
package/dist/data-grid.js CHANGED
@@ -36,11 +36,35 @@ import { IconButton } from "./actions.js";
36
36
  // porque é só o que existe em memória; para seleção que atravessa páginas, use
37
37
  // `rowSelection`/`onRowSelectionChange`, que são ids.
38
38
  // A11y: aria-sort fica SÓ no th ordenado (padrão APG) e o cabeçalho ordenável é
39
- // um <button> de verdade; a linha selecionada usa data-selected apenas para
40
- // estilo — aria-selected é inválido em role=table, o estado acessível é o
41
- // próprio checkbox. A paginação reusa o componente Pagination.
39
+ // um <button> de verdade. A paginação reusa o componente Pagination.
40
+ //
41
+ // G-A11Y-07 ELE É UM `grid`, E ATÉ 28/08/2026 SÓ DIZIA QUE ERA.
42
+ //
43
+ // A ficha declarava `role: "grid"`, `apg: "grid"` e quatro setas. Medido no navegador em
44
+ // 27/08, as quatro eram INERTES: `<table>` puro, nenhum `onKeyDown`, nenhum `tabIndex` em
45
+ // célula. Prometer acessibilidade que não existe é pior que não ter — quem lê o contrato para
46
+ // decidir se adota confia nele. A ficha foi corrigida naquele dia para o que se media
47
+ // (`role: "table"`); hoje o componente passa a merecer o que prometia.
48
+ //
49
+ // POR QUE `grid` E NÃO `table`, agora de verdade: a APG manda usar `grid` quando as células
50
+ // contêm widgets operáveis, e as deste contêm — cabeçalho ordenável é `<button>`, seleção é
51
+ // `<input type=checkbox>`. E vem de brinde a correção de uma limitação que o comentário antigo
52
+ // deste arquivo registrava como fato da vida: **`aria-selected` é inválido em `role=table`, e é
53
+ // VÁLIDO em `role=grid`**. A seleção deixou de ser só `data-selected` (estilo) e passou a ser
54
+ // estado ACESSÍVEL na linha, que é o que o leitor de tela anuncia.
55
+ //
56
+ // O MODELO DE FOCO é o da APG: um tab stop para a grade inteira (roving tabindex pelas
57
+ // CÉLULAS, cabeçalho incluído), setas navegam, `Enter`/`F2` entram no widget da célula e
58
+ // `Escape` volta para a célula. Duas teclas para marcar um checkbox é o preço de a grade ter
59
+ // navegação — e é exatamente o que o exemplo "Data Grid" da APG faz.
60
+ //
61
+ // A conta de posição é feita contra o DOM (`table.rows[r].cells[c]`), não contra os dados: a
62
+ // linha de "vazio" tem uma célula só com `colSpan`, e qualquer aritmética baseada no número de
63
+ // colunas dos dados sairia da caixa ali. Quem sabe quantas células a linha tem é a linha.
42
64
  function GridCheck({ label, indeterminate, ...props }) {
43
- return _jsxs("label", { className: "checkbox", children: [_jsx("input", { type: "checkbox", ref: el => { if (el)
65
+ // `tabIndex={-1}`: numa grade APG a parada de Tab é UMA, a grade inteira. O checkbox continua
66
+ // focável por programa — é o que o `Enter`/`F2` da célula faz —, e o `Space` marca dali.
67
+ return _jsxs("label", { className: "checkbox", children: [_jsx("input", { type: "checkbox", tabIndex: -1, ref: el => { if (el)
44
68
  el.indeterminate = !!indeterminate; }, ...props }), _jsx("span", { className: "control-mark" }), _jsx("span", { className: "sr-only", children: label })] });
45
69
  }
46
70
  // A faceta guarda um ARRAY de valores e a célula é ESCALAR — e nenhum filterFn de
@@ -160,6 +184,117 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
160
184
  ...(facetIds.size ? { getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues() } : {}),
161
185
  });
162
186
  const rows = table.getRowModel().rows;
187
+ // O TAB STOP ROVING, e o `clamp` não é zelo: filtrar, ordenar ou paginar troca as linhas sob
188
+ // o foco. Sem ele a grade inteira ficaria `tabIndex=-1` e sumiria da ordem do Tab — é o mesmo
189
+ // defeito que o `TreeView` pagou (MÉDIO 1 da auditoria) e resolve com `effectiveActive`.
190
+ const grade = React.useRef(null);
191
+ const [foco, setFoco] = React.useState({ r: 0, c: 0 });
192
+ const nColunas = table.getVisibleLeafColumns().length;
193
+ // A LINHA DE "VAZIO" CONTA, e conta com UMA coluna só.
194
+ //
195
+ // O defeito que isto corrige foi MEDIDO, e não é o que eu tinha escrito aqui na primeira
196
+ // versão. Eu havia afirmado que sem o clamp a grade "sumia da ordem do Tab"; a medição
197
+ // desmentiu — ela continua alcançável. O que quebra é mais sutil e mais chato: com o `clamp`
198
+ // de linha dizendo "só existe o cabeçalho" enquanto o DOM tem a linha do vazio, **a parada de
199
+ // Tab fica numa célula e o foco em outra**. Filtrando até não sobrar nada e descendo para a
200
+ // linha do vazio, o DOM ficava assim:
201
+ //
202
+ // sem o clamp TH:0 TH:-1 TD:-1 ← foco no TD, parada de Tab no TH
203
+ // com o clamp TH:-1 TH:-1 TD:0 ← a parada acompanha o foco
204
+ //
205
+ // Ou seja: sair da grade e voltar devolvia a pessoa ao cabeçalho, e não a onde ela estava. É
206
+ // a invariante do roving tabindex — a célula focada É a parada — e o teste que a cobra assere
207
+ // exatamente isso, porque contar quantas células têm `tabindex=0` passava verde com o defeito.
208
+ const nLinhas = (rows.length || 1) + 1; // +1: a linha de cabeçalho é navegável
209
+ const r = Math.min(foco.r, nLinhas - 1);
210
+ const colunasNaLinha = rows.length === 0 && r === 1 ? 1 : nColunas;
211
+ const eff = { r, c: Math.min(foco.c, colunasNaLinha - 1) };
212
+ // Mover é reposicionar NO DOM e focar. `setFoco` só existe para o `tabIndex` da próxima
213
+ // renderização; quem devolve o foco de verdade é o `.focus()` aqui.
214
+ const irPara = (r, c) => {
215
+ const linhas = grade.current?.rows;
216
+ if (!linhas?.length)
217
+ return;
218
+ const nr = Math.max(0, Math.min(r, linhas.length - 1));
219
+ const nc = Math.max(0, Math.min(c, linhas[nr].cells.length - 1));
220
+ setFoco({ r: nr, c: nc });
221
+ linhas[nr].cells[nc].focus();
222
+ };
223
+ const teclado = (e) => {
224
+ const alvo = e.target;
225
+ const celula = alvo.closest("th,td");
226
+ if (!celula || !grade.current?.contains(celula))
227
+ return;
228
+ const r = celula.parentElement.rowIndex, c = celula.cellIndex;
229
+ // DENTRO de um widget da célula, o teclado é DELE. Interceptar as setas aqui quebraria o
230
+ // campo de texto que um dia entre numa célula, e é o defeito que o probe de teclado pegou
231
+ // no `Space` dos bancos com <input> (`digitaTexto`).
232
+ const dentro = alvo !== celula;
233
+ if (dentro) {
234
+ if (e.key === "Escape") {
235
+ e.preventDefault();
236
+ irPara(r, c);
237
+ }
238
+ return;
239
+ }
240
+ // O SALTO DE PÁGINA é de 10 linhas e é ARBITRÁRIO com aval: a APG diz "um número de linhas
241
+ // definido pelo autor". 10 é o que cabe numa tela sem passar do fim na maioria das grades.
242
+ const salto = 10;
243
+ switch (e.key) {
244
+ case "ArrowRight":
245
+ e.preventDefault();
246
+ irPara(r, c + 1);
247
+ break;
248
+ case "ArrowLeft":
249
+ e.preventDefault();
250
+ irPara(r, c - 1);
251
+ break;
252
+ case "ArrowDown":
253
+ e.preventDefault();
254
+ irPara(r + 1, c);
255
+ break;
256
+ case "ArrowUp":
257
+ e.preventDefault();
258
+ irPara(r - 1, c);
259
+ break;
260
+ case "PageDown":
261
+ e.preventDefault();
262
+ irPara(r + salto, c);
263
+ break;
264
+ case "PageUp":
265
+ e.preventDefault();
266
+ irPara(r - salto, c);
267
+ break;
268
+ case "Home":
269
+ e.preventDefault();
270
+ irPara(e.ctrlKey ? 0 : r, 0);
271
+ break;
272
+ case "End":
273
+ e.preventDefault();
274
+ irPara(e.ctrlKey ? (grade.current.rows.length - 1) : r, nColunas - 1);
275
+ break;
276
+ // ENTRAR NA CÉLULA. Se ela não tem widget, não há o que fazer e a tecla segue seu caminho
277
+ // (um `Enter` numa célula de texto não deve engolir nada).
278
+ case "Enter":
279
+ case "F2": {
280
+ const w = celula.querySelector("button:not([disabled]),a[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled])");
281
+ if (w) {
282
+ e.preventDefault();
283
+ w.focus();
284
+ }
285
+ break;
286
+ }
287
+ }
288
+ };
289
+ // O FOCO QUE CHEGA POR CLIQUE OU POR TAB também reposiciona o roving. Sem isto, sair da grade
290
+ // e voltar devolveria o foco à célula ANTIGA, e clicar numa célula deixaria o tab stop noutra.
291
+ const focoEntrou = (e) => {
292
+ const celula = e.target.closest("th,td");
293
+ if (celula && grade.current?.contains(celula)) {
294
+ setFoco({ r: celula.parentElement.rowIndex, c: celula.cellIndex });
295
+ }
296
+ };
297
+ const rove = (r, c) => eff.r === r && eff.c === c ? 0 : -1;
163
298
  const sortIcon = (dir) => dir === "asc" ? "chevron--sort--up" : dir === "desc" ? "chevron--sort--down" : "chevron--sort";
164
299
  // F5: a barra de lote. Duas referências independentes (Activepieces e Kaneo) chegam
165
300
  // à MESMA anatomia — contagem, divisória, ações, e um jeito de limpar —, então é
@@ -231,10 +366,15 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
231
366
  // ele cabe é a largura da GRADE, não a da janela: por isso `@container`, no
232
367
  // precedente do MediaPlayer.
233
368
  const linhaAberta = detailRowId != null ? table.getRowModel().rows.find(r => r.id === detailRowId) : undefined;
234
- const tabela = (_jsx("div", { className: "table-wrap", role: "region", "aria-label": label ?? s.tableLabel, tabIndex: 0, "aria-busy": carregando || undefined, children: _jsxs("table", { children: [_jsxs("thead", { children: [table.getHeaderGroups().map(hg => _jsx("tr", { children: hg.headers.map(h => {
369
+ // A REGIÃO DE ROLAGEM PERDEU O `tabIndex={0}` que o `Table` mantém, e a diferença é o
370
+ // motivo de a regra existir: `scrollable-region-focusable` do axe cobra que um contêiner
371
+ // rolável tenha COMO ser alcançado pelo teclado, e a grade agora tem células focáveis
372
+ // dentro — inclusive a de "vazio". Manter o `tabIndex` daria uma parada de Tab a mais
373
+ // ANTES da grade, e a APG pede que a grade inteira seja UMA parada.
374
+ const tabela = (_jsx("div", { className: "table-wrap", role: "region", "aria-label": label ?? s.tableLabel, "aria-busy": carregando || undefined, children: _jsxs("table", { ref: grade, role: "grid", "aria-label": label ?? s.tableLabel, onKeyDown: teclado, onFocus: focoEntrou, "aria-rowcount": pageSize != null ? table.getPreFilteredRowModel().rows.length + 1 : undefined, "aria-colcount": pageSize != null ? nColunas : undefined, children: [_jsxs("thead", { children: [table.getHeaderGroups().map(hg => _jsx("tr", { children: hg.headers.map((h, ci) => {
235
375
  const dir = h.column.getIsSorted();
236
- return _jsxs("th", { colSpan: h.colSpan, className: h.column.id === "select" ? "datagrid-selcol" : undefined, "aria-sort": dir === "asc" ? "ascending" : dir === "desc" ? "descending" : undefined, style: resizableColumns ? { width: h.getSize() } : undefined, children: [h.isPlaceholder ? null : h.column.getCanSort()
237
- ? _jsxs("button", { type: "button", className: "datagrid-sort", onClick: h.column.getToggleSortingHandler(), children: [flexRender(h.column.columnDef.header, h.getContext()), _jsx(Icon, { name: sortIcon(dir), size: "sm", className: "datagrid-sort-icon" })] })
376
+ return _jsxs("th", { colSpan: h.colSpan, tabIndex: rove(0, ci), className: h.column.id === "select" ? "datagrid-selcol" : undefined, "aria-sort": dir === "asc" ? "ascending" : dir === "desc" ? "descending" : undefined, style: resizableColumns ? { width: h.getSize() } : undefined, children: [h.isPlaceholder ? null : h.column.getCanSort()
377
+ ? _jsxs("button", { type: "button", tabIndex: -1, className: "datagrid-sort", onClick: h.column.getToggleSortingHandler(), children: [flexRender(h.column.columnDef.header, h.getContext()), _jsx(Icon, { name: sortIcon(dir), size: "sm", className: "datagrid-sort-icon" })] })
238
378
  : flexRender(h.column.columnDef.header, h.getContext()), resizableColumns && h.column.getCanResize() && _jsx("button", { type: "button", className: "datagrid-resizer", "aria-label": `${s.dataGridResize}: ${String(h.column.columnDef.header ?? h.column.id)}`, onMouseDown: h.getResizeHandler(), onTouchStart: h.getResizeHandler(), onKeyDown: e => {
239
379
  const d = e.key === "ArrowLeft" ? -16 : e.key === "ArrowRight" ? 16 : 0;
240
380
  if (!d)
@@ -247,8 +387,8 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
247
387
  return _jsx("th", { className: col.id === "select" ? "datagrid-selcol" : undefined, children: spec ? _jsx(GridFilter, { col: col, spec: spec, name: `${s.dataGridFilter} ${spec.label}` }) : null }, col.id);
248
388
  }) })] }), _jsx("tbody", { children: carregando && !rows.length
249
389
  ? Array.from({ length: pageSize ?? 3 }, (_, i) => _jsx("tr", { children: table.getVisibleLeafColumns().map(c => _jsx("td", { children: _jsx(Skeleton, { className: "datagrid-skeleton" }) }, c.id)) }, `sk${i}`))
250
- : rows.length ? rows.map(row => _jsx("tr", { "data-selected": row.getIsSelected() || undefined, children: row.getVisibleCells().map(cell => _jsx("td", { className: cell.column.id === "select" ? "datagrid-selcol" : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id)) }, row.id))
251
- : _jsx("tr", { children: _jsx("td", { colSpan: table.getVisibleLeafColumns().length, className: "datagrid-empty", children: s.dataGridEmpty }) }) })] }) }));
390
+ : rows.length ? rows.map((row, ri) => _jsx("tr", { "aria-selected": selectable ? row.getIsSelected() : undefined, "data-selected": row.getIsSelected() || undefined, children: row.getVisibleCells().map((cell, ci) => _jsx("td", { tabIndex: rove(ri + 1, ci), className: cell.column.id === "select" ? "datagrid-selcol" : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id)) }, row.id))
391
+ : _jsx("tr", { children: _jsx("td", { colSpan: nColunas, tabIndex: rove(1, 0), className: "datagrid-empty", children: s.dataGridEmpty }) }) })] }) }));
252
392
  if (!renderDetail || !linhaAberta)
253
393
  return tabela;
254
394
  return _jsxs("div", { className: "datagrid-split", children: [tabela, _jsxs("aside", { className: "datagrid-detail", "aria-label": s.dataGridDetailPanel, children: [_jsx("div", { className: "datagrid-detail-head", children: _jsx(IconButton, { icon: "close", label: s.close, size: "sm", variant: "ghost", onClick: () => setDetailRowId(null) }) }), renderDetail(linhaAberta.original)] })] });
@@ -6,3 +6,12 @@ export declare function Accordion({ items }: {
6
6
  content: ReactNode;
7
7
  }>;
8
8
  }): import("react").JSX.Element;
9
+ export declare function Collapsible({ trigger, children, open, defaultOpen, onOpenChange, disabled, className }: {
10
+ trigger: ReactNode;
11
+ children: ReactNode;
12
+ open?: boolean;
13
+ defaultOpen?: boolean;
14
+ onOpenChange?: (open: boolean) => void;
15
+ disabled?: boolean;
16
+ className?: string;
17
+ }): import("react").JSX.Element;
@@ -1,2 +1,21 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Collapsible as BaseCollapsible } from "@base-ui/react/collapsible";
4
+ import { cx } from "./internal.js";
5
+ import { soOValor } from "./pure.js";
6
+ import { Icon } from "./system.js";
2
7
  export function Accordion({ items }) { return _jsx("div", { className: "accordion", children: items.map(i => _jsxs("details", { children: [_jsx("summary", { children: i.title }), _jsx("div", { className: "accordion-content", children: i.content })] }, i.id)) }); }
8
+ // COLLAPSIBLE — uma seção que abre e fecha, com o estado do lado de quem usa.
9
+ //
10
+ // Não é um Accordion de um item só, e a diferença é o motivo de existir: o `Accordion` é um
11
+ // CONJUNTO de seções sobre `<details>` nativo, que abre sem JS e guarda o próprio estado. Aqui a
12
+ // aplicação é a dona do estado — um grupo da lateral que lembra se estava aberto, um painel de
13
+ // filtros que abre por ação de outro lugar, um "mostrar mais" que fecha ao trocar de página.
14
+ // `<details>` controlado é possível e desconfortável; e cinco das nove referências tratam os dois
15
+ // como componentes distintos.
16
+ //
17
+ // A API é a daqui: `trigger` é dado, o conteúdo são os filhos. Não é a API composta da referência
18
+ // (Root/Trigger/Panel) — quem quiser compor esse nível usa o motor direto.
19
+ export function Collapsible({ trigger, children, open, defaultOpen, onOpenChange, disabled, className }) {
20
+ return _jsxs(BaseCollapsible.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: soOValor(onOpenChange), disabled: disabled, className: cx("collapsible", className), children: [_jsxs(BaseCollapsible.Trigger, { className: "collapsible-trigger", children: [_jsx(Icon, { name: "chevron--down", size: "sm", className: "collapsible-chevron" }), trigger] }), _jsx(BaseCollapsible.Panel, { className: "collapsible-panel", children: children })] });
21
+ }
@@ -0,0 +1,26 @@
1
+ /** Os pontos da escala, em pixels. UMA escala para os dois mensuráveis — o prefixo da classe
2
+ * (`vp-` / `ct-`) é que diz de quê são os pixels. */
3
+ export declare const ESCALA: {
4
+ readonly "2xs": 360;
5
+ readonly xs: 480;
6
+ readonly sm: 640;
7
+ readonly md: 768;
8
+ readonly lg: 1024;
9
+ readonly xl: 1280;
10
+ readonly "2xl": 1536;
11
+ };
12
+ /** O subconjunto que o CONTAINER consulta. Contêiner de 1280px é raro o bastante para não valer
13
+ * regra, e ponto que ninguém usa é peso morto na folha. Cresce por medição. */
14
+ export declare const ESCALA_CONTAINER: {
15
+ readonly "2xs": 360;
16
+ readonly xs: 480;
17
+ readonly sm: 640;
18
+ readonly md: 768;
19
+ readonly lg: 1024;
20
+ };
21
+ export type Breakpoint = "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
22
+ export type ContainerBreakpoint = "2xs" | "xs" | "sm" | "md" | "lg";
23
+ /** Os pontos do maior para o menor. A resolução percorre nesta ordem e para no primeiro que
24
+ * couber, que é a semântica de `min-width`: o último ponto atingido vence. */
25
+ export declare const PONTOS_DESC: ReadonlyArray<Breakpoint>;
26
+ export declare const PONTOS_CONTAINER_DESC: ReadonlyArray<ContainerBreakpoint>;
package/dist/escala.js ADDED
@@ -0,0 +1,17 @@
1
+ // GERADO por scripts/build-responsive-layer.mjs — não editar à mão.
2
+ //
3
+ // A escala canônica, lida de `packages/tokens/dist/aurea.tokens.css`. É a MESMA leitura que
4
+ // gera as `@media`/`@container` do core, e é por isso que o resolvedor de runtime e o CSS
5
+ // não podem discordar: não há dois números, há um.
6
+ //
7
+ // Sem `"use client"`: é dado, e um componente de servidor precisa dos tipos.
8
+ /** Os pontos da escala, em pixels. UMA escala para os dois mensuráveis — o prefixo da classe
9
+ * (`vp-` / `ct-`) é que diz de quê são os pixels. */
10
+ export const ESCALA = { "2xs": 360, "xs": 480, "sm": 640, "md": 768, "lg": 1024, "xl": 1280, "2xl": 1536 };
11
+ /** O subconjunto que o CONTAINER consulta. Contêiner de 1280px é raro o bastante para não valer
12
+ * regra, e ponto que ninguém usa é peso morto na folha. Cresce por medição. */
13
+ export const ESCALA_CONTAINER = { "2xs": 360, "xs": 480, "sm": 640, "md": 768, "lg": 1024 };
14
+ /** Os pontos do maior para o menor. A resolução percorre nesta ordem e para no primeiro que
15
+ * couber, que é a semântica de `min-width`: o último ponto atingido vence. */
16
+ export const PONTOS_DESC = ["2xl", "xl", "lg", "md", "sm", "xs", "2xs"];
17
+ export const PONTOS_CONTAINER_DESC = ["lg", "md", "sm", "xs", "2xs"];
@@ -1,3 +1,4 @@
1
+ import { type Responsive } from "./pure.js";
1
2
  import React, { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
3
  import { type UniversalState } from "./internal.js";
3
4
  import { type IconName } from "./system.js";
@@ -8,10 +9,12 @@ export declare function Status({ variant, state, children, className, ...props }
8
9
  state?: UniversalState;
9
10
  }): React.JSX.Element;
10
11
  export type AlertVariant = "info" | "success" | "warning" | "danger";
11
- export declare function Alert({ variant, state, title, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
12
+ export declare function Alert({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
12
13
  variant?: AlertVariant;
13
14
  state?: UniversalState;
14
15
  title?: ReactNode;
16
+ icon?: IconName;
17
+ onDismiss?: () => void;
15
18
  }): React.JSX.Element;
16
19
  export type BannerVariant = AlertVariant;
17
20
  export declare function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
@@ -22,7 +25,7 @@ export declare function Banner({ variant, state, title, icon, onDismiss, childre
22
25
  onDismiss?: () => void;
23
26
  }): React.JSX.Element;
24
27
  export declare function Spinner({ size, label, decorative, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
25
- size?: "sm" | "md" | "lg";
28
+ size?: Responsive<"sm" | "md" | "lg">;
26
29
  label?: string;
27
30
  decorative?: boolean;
28
31
  }): React.JSX.Element;
@@ -2,6 +2,7 @@
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
4
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
5
+ import { peleDoEixo } from "./pure.js";
5
6
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
6
7
  import React from "react";
7
8
  import { Popover as BasePopover } from "@base-ui/react/popover";
@@ -28,7 +29,29 @@ import { Skeleton } from "./markup.js";
28
29
  // "esperando" dele é grave. E o rótulo é o do consumidor, como sempre foi: a string universal
29
30
  // entra só quando não há filho, para o componente não passar a inventar texto.
30
31
  export function Status({ variant, state, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? (state === "offline" ? "offline" : stateSeverity(state)) : "neutral"); return _jsxs("span", { className: cx("status", v !== "neutral" && `status-${v}`, className), "data-state": state, ...props, children: [_jsx("i", { className: "status-dot", "aria-hidden": "true" }), _jsx("span", { className: "status-label", children: children ?? (state ? s.universalState[state] : null) })] }); }
31
- export function Alert({ variant, state, title, 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: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }); }
32
+ // O ícone é FUNÇÃO da variante, como o `role` era. A referência congelada desenha o alerta
33
+ // com ele em toda variante, e um sistema que faz o consumidor lembrar qual glifo significa
34
+ // "perigo" acaba com quatro respostas diferentes para a mesma pergunta. `icon` sobrepõe quando o
35
+ // caso é específico. O Banner NÃO ganha o mesmo padrão de propósito: ele não existe na
36
+ // referência e o aviso de largura de página nem sempre quer glifo — a assimetria é decidida,
37
+ // não herdada.
38
+ const ICONE_DA_VARIANTE = { info: "information--filled",
39
+ success: "checkmark--filled", warning: "warning--alt--filled", danger: "error--filled" };
40
+ // A grade do `.alert` tem TRÊS trilhas (auto 1fr auto): ícone, corpo, ação. Uma versão anterior
41
+ // emitia o título solto na trilha 1 — a do ícone — e o corpo como item anônimo. Medido no
42
+ // navegador em 21/08/2026: o título ocupava a coluna do ícone e crescia com o próprio texto,
43
+ // enquanto a referência dá 300px de trilha ao ícone e 544px ao corpo. Título e corpo vão JUNTOS
44
+ // na trilha 2, dentro de um <div>, que é o que a referência faz.
45
+ // `<strong>` e não `<h4>`: um alerta aparece em qualquer profundidade da página e um h4 fixo
46
+ // quebra a ordem de cabeçalhos onde ele cair (é a razão de o EmptyState ter `titleAs`).
47
+ //
48
+ // A anatomia das três trilhas voltou em 29/08/2026: o merge das duas linhagens ficou com a
49
+ // versão sem ícone, e os oito testes do `grade-do-core.test.tsx` foram o que pegou. `state` é da
50
+ // outra linhagem e fica: é EIXO À PARTE de `variant` — a variante é a cor, o estado é a condição
51
+ // universal. Quem passa só `state` recebe a cor derivada da gravidade e o rótulo universal
52
+ // quando não há filho; quem passa os dois manda, porque só o consumidor sabe se aquele
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] }), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
32
55
  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: "close", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
33
56
  // A barra grampeava 0..100 e o `aria-valuenow` NÃO — medido ao publicar o contrato de API na
34
57
  // Parte E: com value=150 o desenho parava em 100% e o leitor de tela anunciava "150 de 100".
@@ -37,7 +60,10 @@ export function Banner({ variant, state, title, icon, onDismiss, children, class
37
60
  // SÓ DETERMINADA, de propósito: não há modo indeterminado nem `.progress` para ele no core. A
38
61
  // ficha dizia que havia e era falso — corrigido junto, porque contrato que promete o que não
39
62
  // existe custa mais que ausência.
40
- export function Spinner({ size = "sm", label, decorative, className, ...props }) { const s = useAureaStrings(); return _jsx("span", { className: cx("spinner", size !== "sm" && `spinner-${size}`, className), ...(decorative ? { "aria-hidden": true } : { role: "status", "aria-label": label ?? s.loading }), ...props }); }
63
+ // O DEGRAU RESPONSIVO volta (merge de 28/08/2026): a ficha declara `responsive.size` e o
64
+ // componente tinha perdido a capacidade quando este arquivo entrou da `main`. Mesma perda
65
+ // silenciosa do `Input`, do `Tabs` e do `NumberField` — a ficha documentava, o código não fazia.
66
+ export function Spinner({ size, 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 }); }
41
67
  // `children` como FUNÇÃO é de propósito para o caso `loading`: assim o consumidor não paga o
42
68
  // render do conteúdo enquanto ele não existe. Aceita nó também, porque a maioria das telas já
43
69
  // tem o conteúdo pronto e obrigar função seria cerimônia.