@aurea-uds/react 0.1.0 → 0.2.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/chart.js ADDED
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Lote 3 do BUILDING.md (01/08/2026). SUBPATH PRÓPRIO, como ./data-grid e ./qrcode: o único
4
+ // componente que precisa do `recharts`, que é peer OPCIONAL. Se morasse em data-display.tsx,
5
+ // quem importa Table baixaria um motor de gráfico inteiro — que é o achado A5 visto de perto.
6
+ //
7
+ // NÃO ESCREVEMOS MOTOR DE GRÁFICO. Duas das quatro referências (shadcn/ui e Untitled UI React)
8
+ // envelopam o MESMO motor, o Recharts, e nenhuma das quatro desenha eixo à mão. O que é nosso
9
+ // aqui é só a pele: a caixa, o tooltip e a legenda. Escala, eixo, curva e interação são dele.
10
+ //
11
+ // O QUE FICOU DE FORA, e por quê (BUILDING.md passo 5 — escopo menor que o da referência):
12
+ // • o `ChartConfig` do shadcn (mapa dataKey → {label, color, theme}). MEDIDO em 01/08/2026: o
13
+ // payload que o Recharts entrega ao `content` já traz `name` (do prop `name` da série) e
14
+ // `color` resolvido. O objeto de configuração é indireção para reconstruir o que já chega.
15
+ // E o eixo `theme:{light,dark}` não existe aqui: `var(--chart-2)` já troca com o data-theme.
16
+ // • o par `ChartTooltip`/`ChartTooltipContent`. No Recharts 2 o filho tinha de ser o
17
+ // componente DELES, o que forçava o shadcn a reexportar o primitivo e pôr a pele no
18
+ // `content`. MEDIDO no 3.10.1: um componente NOSSO como filho é reconhecido. Então é uma
19
+ // peça só, não duas.
20
+ // • prop de altura. Dimensão não é número (check 23): a caixa mede pelo CSS (`.chart`), e
21
+ // quem quer outra altura escreve uma classe. Sem prop, sem pixel cru no consumidor.
22
+ import React from "react";
23
+ import { ResponsiveContainer, Tooltip, Legend } from "recharts";
24
+ import { cx, useAureaStrings } from "./internal.js";
25
+ export function Chart({ children, label, className }) {
26
+ const s = useAureaStrings();
27
+ const nome = label ?? s.chartLabel;
28
+ // O nome vai para os DOIS: o grupo (que também abriga legenda e tooltip, que ficam FORA do
29
+ // <svg>) e o desenho. Medido em 01/08/2026: o motor emite `<title></title>` vazio, e o <svg>
30
+ // é role="application" com tabindex=0 — quem chega nele pelo Tab ouviria "application" e mais
31
+ // nada. `title` é prop do gráfico do motor, não da caixa, então o repasse é aqui. Se o
32
+ // consumidor já passou o seu, o dele vence.
33
+ const desenho = React.isValidElement(children) && children.props.title == null
34
+ ? React.cloneElement(children, { title: nome }) : children;
35
+ return _jsx("div", { className: cx("chart", className), role: "group", "aria-label": nome, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: desenho }) });
36
+ }
37
+ // ChartTooltip: reusa `.tooltip`, a mesma superfície do Tooltip da Aurea — o valor sob o cursor
38
+ // é um rótulo, não um card. O quadradinho de cor vem do `color` do payload (é DADO: a cor da
39
+ // série), então é o único `style` inline do arquivo.
40
+ // ponytail: sem formatador de valor. `tickFormatter` do eixo já é do motor, e nenhum consumidor
41
+ // pediu o do tooltip — entra quando pedir.
42
+ export function ChartTooltip(props) {
43
+ return _jsx(Tooltip, { cursor: { className: "chart-cursor" }, ...props, content: ({ active, payload, label }) => {
44
+ if (!active || !payload?.length)
45
+ return null;
46
+ return _jsxs("div", { className: "tooltip chart-tooltip", children: [label != null && label !== "" && _jsx("strong", { children: String(label) }), payload.map((p, i) => _jsxs("span", { className: "chart-key", children: [_jsx("i", { style: { background: p.color } }), p.name, _jsx("b", { children: String(p.value) })] }, p.dataKey ?? i))] });
47
+ } });
48
+ }
49
+ // ChartLegend: lista de séries. `value` do payload é o nome legível da série (o prop `name`),
50
+ // não o valor do dado — é o nome do campo no Recharts e não dá para renomear.
51
+ export function ChartLegend(props) {
52
+ return _jsx(Legend, { verticalAlign: "bottom", ...props, content: ({ payload }) => _jsx("ul", { className: "chart-legend", children: (payload ?? []).map((p, i) => _jsxs("li", { className: "chart-key", children: [_jsx("i", { style: { background: p.color } }), p.value] }, p.value ?? i)) }) });
53
+ }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
package/dist/code.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
@@ -9,10 +10,25 @@ import { Icon } from "./system.js";
9
10
  // carrega o pacote React; ele para a propagação pro core não copiar duas vezes.
10
11
  export function CodeBlock({ children, language = "text", copyable, className }) {
11
12
  const s = useAureaStrings();
12
- const pre = _jsx("pre", { className: cx("code-block", !copyable && className), "data-language": language, children: _jsx("code", { children: children }) });
13
+ // tabIndex no <pre>: `.code-block` é `overflow:auto` por construção, então é REGIÃO ROLÁVEL
14
+ // e não tem nada focável dentro — quem navega por teclado não alcança o código que passa da
15
+ // largura. É a regra `scrollable-region-focusable` do axe, e o mesmo defeito que o painel de
16
+ // demo do catálogo já tinha corrigido em 30/07/2026 no lado dele; aqui, na biblioteca, ele
17
+ // seguia aberto e só não aparecia porque nenhuma linha era comprida o bastante. Quem o achou
18
+ // foi o `catalog-sweep` em 10/08/2026, quando o bloco do I1 fez a linha de import crescer.
19
+ const pre = _jsx("pre", { className: cx("code-block", !copyable && className), "data-language": language, tabIndex: 0, children: _jsx("code", { children: children }) });
13
20
  if (!copyable)
14
21
  return pre;
15
22
  const onCopy = (e) => { e.stopPropagation(); window.Aurea?.copy?.(e.currentTarget) ?? navigator.clipboard?.writeText(children); };
16
23
  return _jsxs("div", { className: cx("code-block-wrap", className), "data-aurea-copy-scope": true, children: [_jsxs("button", { type: "button", className: "btn btn-icon btn-ghost copy-code", "data-aurea-copy": true, "aria-label": s.copyCode, onClick: onCopy, children: [_jsx(Icon, { name: "copy", size: "sm", className: "c-copy" }), _jsx(Icon, { name: "checkmark", size: "sm", className: "c-done" })] }), pre] });
17
24
  }
18
- export function LogStream({ lines }) { return _jsx("div", { className: "log-stream", role: "log", "aria-live": "polite", children: lines.map((l, n) => _jsxs("div", { className: cx("log-line", l.level && `log-${l.level}`), children: [_jsx("time", { children: l.time }), _jsx("span", { children: l.text })] }, n)) }); }
25
+ // A pele do log é de TRÊS colunas hora, nível, texto e o componente emitia DUAS. Medido no
26
+ // item E13 (07/08/2026), com só o core: o texto caía na coluna do NÍVEL, 72px de largura, e a
27
+ // prop `level` não pintava nada, porque o core estiliza `.log-level.error` (um elemento) e o
28
+ // componente escrevia `log-error` (no container). Gate nenhum via: o check 18 só enxerga classe
29
+ // LITERAL, e `log-${level}` é template; o check 15 dava a classe por produzível pelo mesmo
30
+ // motivo, via prefixo. É o achado A6 outra vez — a regra do core servia a `apps/docs/index.html`,
31
+ // escrito à mão com as três partes, e a biblioteca pagava a conta.
32
+ // A célula do nível é SEMPRE renderizada, mesmo vazia: sem ela a linha sem `level` volta a ter
33
+ // dois filhos e o texto volta para a coluna estreita.
34
+ export function LogStream({ lines }) { return _jsx("div", { className: "log-stream", role: "log", "aria-live": "polite", children: lines.map((l, n) => _jsxs("div", { className: cx("log-line", l.level && `log-${l.level}`), children: [_jsx("time", { className: "log-time", children: l.time }), _jsx("span", { className: "log-level", children: l.level }), _jsx("span", { children: l.text })] }, n)) }); }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
@@ -1,7 +1,18 @@
1
1
  import React from "react";
2
- import { type ColumnDef } from "@tanstack/react-table";
3
- export type { ColumnDef } from "@tanstack/react-table";
4
- export declare function DataGrid<T>({ data, columns, label, filterable, pageSize, selectable, onSelectionChange, getRowId, className }: {
2
+ import { type ColumnDef, type SortingState, type RowSelectionState, type ColumnFiltersState, type VisibilityState, type ColumnSizingState } from "@tanstack/react-table";
3
+ import { type UniversalState } from "./internal.js";
4
+ import { type IconName } from "./system.js";
5
+ export type { ColumnDef, SortingState, RowSelectionState, ColumnFiltersState, VisibilityState, ColumnSizingState } from "@tanstack/react-table";
6
+ export interface GridFilterSpec {
7
+ column: string;
8
+ label: string;
9
+ facet?: boolean;
10
+ options?: Array<{
11
+ value: string;
12
+ label: string;
13
+ }>;
14
+ }
15
+ export declare function DataGrid<T>({ data, columns, label, filterable, pageSize, selectable, onSelectionChange, getRowId, className, sorting: sortingProp, onSortingChange, globalFilter: globalFilterProp, onGlobalFilterChange, page, onPageChange, rowSelection: rowSelectionProp, onRowSelectionChange, manualSorting, manualFiltering, manualPagination, rowCount, filters, columnFilters: columnFiltersProp, onColumnFiltersChange, bulkActions, stickyHeader, hideableColumns, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, resizableColumns, columnSizing: columnSizingProp, onColumnSizingChange, state, stateMessage, renderDetail, detailRowId: detailRowIdProp, onDetailRowIdChange, onExport }: {
5
16
  data: T[];
6
17
  columns: Array<ColumnDef<T, any>>;
7
18
  label?: string;
@@ -11,4 +22,42 @@ export declare function DataGrid<T>({ data, columns, label, filterable, pageSize
11
22
  onSelectionChange?: (rows: T[]) => void;
12
23
  getRowId?: (row: T, index: number) => string;
13
24
  className?: string;
25
+ sorting?: SortingState;
26
+ onSortingChange?: (sorting: SortingState) => void;
27
+ globalFilter?: string;
28
+ onGlobalFilterChange?: (filter: string) => void;
29
+ page?: number;
30
+ onPageChange?: (page: number) => void;
31
+ rowSelection?: RowSelectionState;
32
+ onRowSelectionChange?: (selection: RowSelectionState) => void;
33
+ manualSorting?: boolean;
34
+ manualFiltering?: boolean;
35
+ manualPagination?: boolean;
36
+ rowCount?: number;
37
+ filters?: GridFilterSpec[];
38
+ columnFilters?: ColumnFiltersState;
39
+ onColumnFiltersChange?: (filters: ColumnFiltersState) => void;
40
+ bulkActions?: Array<{
41
+ id: string;
42
+ label: string;
43
+ icon?: IconName;
44
+ onAction: (rows: T[], clear: () => void) => void;
45
+ }>;
46
+ stickyHeader?: boolean;
47
+ hideableColumns?: boolean;
48
+ columnVisibility?: VisibilityState;
49
+ onColumnVisibilityChange?: (v: VisibilityState) => void;
50
+ resizableColumns?: boolean;
51
+ columnSizing?: ColumnSizingState;
52
+ onColumnSizingChange?: (s: ColumnSizingState) => void;
53
+ state?: "loading" | "error" | UniversalState;
54
+ stateMessage?: string;
55
+ renderDetail?: (row: T) => React.ReactNode;
56
+ detailRowId?: string | null;
57
+ onDetailRowIdChange?: (id: string | null) => void;
58
+ onExport?: (rows: T[], scope: {
59
+ count: number;
60
+ filtered: boolean;
61
+ selected: boolean;
62
+ }) => void;
14
63
  }): React.JSX.Element;
package/dist/data-grid.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
@@ -5,15 +6,35 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
6
  // SUBPATH PRÓPRIO (ver ./qrcode): é o único componente que precisa do @tanstack/react-table,
6
7
  // que é peer OPCIONAL. Table, DataList, KPI e Timeline ficam em /data-display sem essa conta.
7
8
  import React from "react";
8
- import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, flexRender } from "@tanstack/react-table";
9
- import { cx, useAureaStrings } from "./internal.js";
9
+ import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, getFacetedRowModel, getFacetedUniqueValues, flexRender } from "@tanstack/react-table";
10
+ import { cx, useAureaStrings, stateSeverity } from "./internal.js";
10
11
  import { Icon } from "./system.js";
11
- import { SearchField } from "./inputs.js";
12
+ import { Button, Toolbar, ToolbarButton, ToolbarSeparator } from "./actions.js";
13
+ import { Input, SearchField, MultiCombobox } from "./inputs.js";
12
14
  import { Pagination } from "./navigation.js";
15
+ import { Cluster } from "./layout.js";
16
+ import { Alert, Skeleton } from "./feedback.js";
17
+ import { IconButton } from "./actions.js";
13
18
  // DataGrid (Fase 4): TanStack Table v8 (8.21.3) headless + pele Aurea — sort,
14
- // filtro global, paginação e seleção por checkbox. v8 porque o v9 ainda é beta
15
- // (07/2026; decisão no ROADMAP). A pele reusa .table-wrap/th/td; o estado é
16
- // interno (não controlado controlar de fora quando houver demanda real).
19
+ // filtro global, paginação e seleção por checkbox. A pele reusa .table-wrap/th/td.
20
+ //
21
+ // PLANO-1.0 Parte F, itens F1 e F2 (08/08/2026): o estado deixou de ser SÓ interno.
22
+ // Cada eixo aceita valor + callback de fora, e o interno segue sendo o default —
23
+ // nenhuma chamada existente muda. E os quatro `manual*` dizem ao motor que quem
24
+ // ordena/filtra/pagina é o servidor, não ele.
25
+ //
26
+ // Por que os nomes `manualSorting`/`manualFiltering`/`manualPagination` são os DO
27
+ // MOTOR e não inventados aqui: as três referências de tabela foram medidas em
28
+ // 08/08/2026 e NENHUMA expõe API controlada — o shadcn guarda tudo em useState
29
+ // dentro do exemplo, o Kibo põe a ordenação num átomo global (jotai) e o Untitled
30
+ // é apresentação. Não havia anatomia para copiar; o vocabulário veio do contrato
31
+ // do motor, que é o que o consumidor já lê na documentação dele.
32
+ //
33
+ // LIMITE de `manualPagination`: `data` passa a ser UMA página, então `rowCount`
34
+ // (total no servidor) é obrigatório — sem ele o motor devolve pageCount -1 e a
35
+ // paginação some da tela. E `onSelectionChange` emite as linhas da página atual,
36
+ // porque é só o que existe em memória; para seleção que atravessa páginas, use
37
+ // `rowSelection`/`onRowSelectionChange`, que são ids.
17
38
  // A11y: aria-sort fica SÓ no th ordenado (padrão APG) e o cabeçalho ordenável é
18
39
  // um <button> de verdade; a linha selecionada usa data-selected apenas para
19
40
  // estilo — aria-selected é inválido em role=table, o estado acessível é o
@@ -22,11 +43,55 @@ function GridCheck({ label, indeterminate, ...props }) {
22
43
  return _jsxs("label", { className: "checkbox", children: [_jsx("input", { type: "checkbox", ref: el => { if (el)
23
44
  el.indeterminate = !!indeterminate; }, ...props }), _jsx("span", { className: "control-mark" }), _jsx("span", { className: "sr-only", children: label })] });
24
45
  }
25
- export function DataGrid({ data, columns, label, filterable, pageSize, selectable, onSelectionChange, getRowId, className }) {
46
+ // A faceta guarda um ARRAY de valores e a célula é ESCALAR — e nenhum filterFn de
47
+ // fábrica faz "valor da célula ∈ selecionados": `arrIncludes`/`arrIncludesSome`
48
+ // esperam a célula array (medido em filterFns.ts:45,67). É uma linha, e mora aqui
49
+ // para o consumidor não repeti-la em cada coluna, que é o que a referência obriga.
50
+ const facetFilterFn = (row, columnId, value) => !Array.isArray(value) || !value.length || value.includes(String(row.getValue(columnId)));
51
+ function GridFilter({ col, spec, name }) {
52
+ if (!spec.facet)
53
+ return _jsx(Input, { value: col.getFilterValue() ?? "", onChange: e => col.setFilterValue(e.target.value || undefined), "aria-label": name });
54
+ const counts = new Map([...col.getFacetedUniqueValues()].map(([k, n]) => [String(k), n]));
55
+ // `options` explícitas mantêm na lista um valor que NENHUMA linha tem agora —
56
+ // é a diferença entre "não há nenhum cancelado" e "cancelado não existe".
57
+ const base = spec.options ?? [...counts.keys()].filter(v => v !== "" && v !== "null" && v !== "undefined").sort();
58
+ const items = (typeof base[0] === "string" ? base.map(v => ({ value: v, label: v })) : base)
59
+ .map(o => ({ value: o.value, label: counts.has(o.value) ? `${o.label} (${counts.get(o.value)})` : o.label }));
60
+ const picked = col.getFilterValue() ?? [];
61
+ return _jsx(MultiCombobox, { items: items, value: items.filter(i => picked.includes(i.value)), onValueChange: v => col.setFilterValue(v.length ? v.map(i => i.value) : undefined), label: _jsx("span", { className: "sr-only", children: name }) });
62
+ }
63
+ // F1: um só lugar decide entre "o valor vem de fora" e "eu guardo". Escrever o
64
+ // ternário em cada eixo é como se erra em um — e o erro típico (esquecer o
65
+ // onChange no modo controlado) trava a tela do consumidor, não a nossa.
66
+ function useMaybe(outer, onChange, initial) {
67
+ const [inner, setInner] = React.useState(initial);
68
+ const controlled = outer !== undefined;
69
+ const value = controlled ? outer : inner;
70
+ return [value, u => { const next = typeof u === "function" ? u(value) : u; if (!controlled)
71
+ setInner(next); onChange?.(next); }];
72
+ }
73
+ export function DataGrid({ data, columns, label, filterable, pageSize, selectable, onSelectionChange, getRowId, className, sorting: sortingProp, onSortingChange, globalFilter: globalFilterProp, onGlobalFilterChange, page, onPageChange, rowSelection: rowSelectionProp, onRowSelectionChange, manualSorting, manualFiltering, manualPagination, rowCount, filters, columnFilters: columnFiltersProp, onColumnFiltersChange, bulkActions, stickyHeader, hideableColumns, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, resizableColumns, columnSizing: columnSizingProp, onColumnSizingChange, state, stateMessage, renderDetail, detailRowId: detailRowIdProp, onDetailRowIdChange, onExport }) {
26
74
  const s = useAureaStrings();
27
- const [sorting, setSorting] = React.useState([]);
28
- const [globalFilter, setGlobalFilter] = React.useState("");
29
- const [rowSelection, setRowSelection] = React.useState({});
75
+ const [sorting, setSorting] = useMaybe(sortingProp, onSortingChange, []);
76
+ const [globalFilter, setGlobalFilter] = useMaybe(globalFilterProp, onGlobalFilterChange, "");
77
+ const [columnFilters, setColumnFilters] = useMaybe(columnFiltersProp, onColumnFiltersChange, []);
78
+ // F7: os dois são MAPAS serializáveis — é isso que torna a escolha persistível,
79
+ // e por isso a persistência não é nossa: quem decide entre localStorage, perfil
80
+ // do usuário ou URL é o consumidor, exatamente como no F4.
81
+ const [columnVisibility, setColumnVisibility] = useMaybe(columnVisibilityProp, onColumnVisibilityChange, {});
82
+ const [columnSizing, setColumnSizing] = useMaybe(columnSizingProp, onColumnSizingChange, {});
83
+ const [detailRowId, setDetailRowId] = useMaybe(detailRowIdProp, onDetailRowIdChange, null);
84
+ const [rowSelection, setRowSelection] = useMaybe(rowSelectionProp, onRowSelectionChange, {});
85
+ // `page` é 1-based porque o nosso Pagination é 1-based e é ele que aparece na
86
+ // tela; o motor é 0-based. A conversão fica AQUI, uma vez. Só o índice é
87
+ // estado: o tamanho vem sempre da prop, então mudar `pageSize` não deixa um
88
+ // valor velho preso em useState.
89
+ const paginated = pageSize != null;
90
+ const [innerIndex, setInnerIndex] = React.useState(0);
91
+ const pageIndex = page !== undefined ? page - 1 : innerIndex;
92
+ const pagination = React.useMemo(() => ({ pageIndex, pageSize: pageSize ?? 10 }), [pageIndex, pageSize]);
93
+ const setPagination = (u) => { const next = typeof u === "function" ? u(pagination) : u; if (page === undefined)
94
+ setInnerIndex(next.pageIndex); onPageChange?.(next.pageIndex + 1); };
30
95
  // Sem getRowId a seleção do TanStack é por ÍNDICE: trocar/reordenar data faria o
31
96
  // checkbox "seguir" a posição e marcar OUTRO registro (auditoria 18/07/2026,
32
97
  // ALTO 3). Sem id estável a seleção não sobrevive à mudança de dados — limpa.
@@ -41,16 +106,40 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
41
106
  onSelectionChange?.([]);
42
107
  }
43
108
  }, [data]);
44
- const allColumns = React.useMemo(() => selectable ? [{
45
- id: "select", enableSorting: false,
46
- header: ({ table }) => _jsx(GridCheck, { label: s.dataGridSelectAll, checked: table.getIsAllRowsSelected(), indeterminate: table.getIsSomeRowsSelected(), onChange: table.getToggleAllRowsSelectedHandler() }),
47
- cell: ({ row }) => _jsx(GridCheck, { label: s.dataGridSelectRow, checked: row.getIsSelected(), disabled: !row.getCanSelect(), onChange: row.getToggleSelectedHandler() }),
48
- }, ...columns] : columns, [selectable, columns, s]);
109
+ const facetIds = React.useMemo(() => new Set((filters ?? []).filter(f => f.facet).map(f => f.column)), [filters]);
110
+ const allColumns = React.useMemo(() => {
111
+ const comFn = columns.map(c => {
112
+ const id = c.id ?? c.accessorKey;
113
+ return id && facetIds.has(id) ? { ...c, filterFn: facetFilterFn } : c;
114
+ });
115
+ return selectable ? [{
116
+ id: "select", enableSorting: false,
117
+ header: ({ table }) => _jsx(GridCheck, { label: s.dataGridSelectAll, checked: table.getIsAllRowsSelected(), indeterminate: table.getIsSomeRowsSelected(), onChange: table.getToggleAllRowsSelectedHandler() }),
118
+ cell: ({ row }) => _jsx(GridCheck, { label: s.dataGridSelectRow, checked: row.getIsSelected(), disabled: !row.getCanSelect(), onChange: row.getToggleSelectedHandler() }),
119
+ }, ...comFn] : comFn;
120
+ }, [selectable, columns, s, facetIds]);
121
+ // F9: o gatilho do detalhe é uma COLUNA com botão por linha, no molde da coluna de
122
+ // seleção — e não um <tr> clicável. Linha não é foco de teclado, e transformá-la em
123
+ // alvo exige inventar papel, tabindex e tecla; um <button> já é tudo isso.
124
+ const colunas = React.useMemo(() => renderDetail ? [...allColumns, {
125
+ id: "detail", enableSorting: false, enableHiding: false, header: () => null,
126
+ cell: ({ row }) => _jsx(IconButton, { icon: "chevron--right", label: s.dataGridDetails, size: "sm", variant: "ghost", "aria-expanded": detailRowId === row.id, onClick: () => setDetailRowId(detailRowId === row.id ? null : row.id) }),
127
+ }] : allColumns, [allColumns, renderDetail, detailRowId, s]);
49
128
  const table = useReactTable({
50
- data, columns: allColumns, getRowId,
51
- state: { sorting, globalFilter, rowSelection },
129
+ data, columns: colunas, getRowId,
130
+ state: { sorting, globalFilter, rowSelection, columnFilters, columnVisibility, columnSizing, ...(paginated ? { pagination } : {}) },
52
131
  onSortingChange: setSorting,
53
132
  onGlobalFilterChange: setGlobalFilter,
133
+ onColumnFiltersChange: setColumnFilters,
134
+ onColumnVisibilityChange: setColumnVisibility,
135
+ onColumnSizingChange: setColumnSizing,
136
+ enableColumnResizing: !!resizableColumns, columnResizeMode: "onChange",
137
+ onPaginationChange: setPagination,
138
+ // F2: os três curto-circuitam o modelo de linha correspondente dentro do
139
+ // motor — medido no fonte instalado (RowSorting.ts:535, ColumnFiltering.ts:408,
140
+ // RowPagination.ts:376). Por isso os getters abaixo seguem ligados sem
141
+ // condição: sob `manual` o motor simplesmente não os chama.
142
+ manualSorting, manualFiltering, manualPagination, rowCount,
54
143
  // emite as linhas ORIGINAIS já aqui (não em effect): getPreFilteredRowModel
55
144
  // ignora filtro/página, então a seleção sobrevive a ambos.
56
145
  onRowSelectionChange: updater => {
@@ -62,15 +151,106 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
62
151
  getSortedRowModel: getSortedRowModel(),
63
152
  getFilteredRowModel: getFilteredRowModel(),
64
153
  enableRowSelection: !!selectable,
65
- ...(pageSize ? { getPaginationRowModel: getPaginationRowModel(), initialState: { pagination: { pageSize } } } : {}),
154
+ ...(paginated ? { getPaginationRowModel: getPaginationRowModel() } : {}),
155
+ // só quando há faceta: os dois varrem os dados para montar o mapa de valores
156
+ // únicos, e ninguém paga por isso sem ter pedido.
157
+ // <T> explícito: chamados sem argumento de tipo, os dois fixam TData em
158
+ // `unknown` e derrubam a inferência do useReactTable inteiro — o erro sai
159
+ // longe daqui, em `columns` e em `row.original`.
160
+ ...(facetIds.size ? { getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues() } : {}),
66
161
  });
67
162
  const rows = table.getRowModel().rows;
68
163
  const sortIcon = (dir) => dir === "asc" ? "chevron--sort--up" : dir === "desc" ? "chevron--sort--down" : "chevron--sort";
69
- return _jsxs("div", { className: cx("datagrid", className), children: [filterable && _jsx(SearchField, { value: globalFilter, onChange: e => setGlobalFilter(e.target.value), placeholder: s.dataGridFilter, "aria-label": s.dataGridFilter }), _jsx("div", { className: "table-wrap", role: "region", "aria-label": label ?? s.tableLabel, tabIndex: 0, children: _jsxs("table", { children: [_jsx("thead", { children: table.getHeaderGroups().map(hg => _jsx("tr", { children: hg.headers.map(h => {
70
- const dir = h.column.getIsSorted();
71
- return _jsx("th", { colSpan: h.colSpan, className: h.column.id === "select" ? "datagrid-selcol" : undefined, "aria-sort": dir === "asc" ? "ascending" : dir === "desc" ? "descending" : undefined, children: h.isPlaceholder ? null : h.column.getCanSort()
72
- ? _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" })] })
73
- : flexRender(h.column.columnDef.header, h.getContext()) }, h.id);
74
- }) }, hg.id)) }), _jsx("tbody", { children: 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))
75
- : _jsx("tr", { children: _jsx("td", { colSpan: table.getVisibleLeafColumns().length, className: "datagrid-empty", children: s.dataGridEmpty }) }) })] }) }), pageSize != null && table.getPageCount() > 1 && _jsx(Pagination, { page: table.getState().pagination.pageIndex + 1, total: table.getPageCount(), onPageChange: p => table.setPageIndex(p - 1) })] });
164
+ // F5: a barra de lote. Duas referências independentes (Activepieces e Kaneo) chegam
165
+ // à MESMA anatomia — contagem, divisória, ações, e um jeito de limpar —, então é
166
+ // ela que entra. O que não entra é o resto das duas: barra `position:fixed` sobre a
167
+ // janela inteira (decisão da APLICAÇÃO, não de um componente que o consumidor põe
168
+ // onde quer, e briga de z-index de graça) e animação por biblioteca de movimento.
169
+ //
170
+ // E ela não tem CSS NENHUM: é `Toolbar` + `ToolbarButton` + `ToolbarSeparator`, que
171
+ // já são superfície flutuante com pele e teclado de setas do Base UI, mais `.hint`
172
+ // para a contagem. A referência desenha a mesma barra à mão em 57 linhas.
173
+ //
174
+ // As linhas vão como as ORIGINAIS e de antes do filtro, igual ao onSelectionChange —
175
+ // agir em lote sobre o que o filtro escondeu é o que o consumidor pediu ao marcar.
176
+ const emLote = bulkActions?.length ? table.getPreFilteredRowModel().flatRows.filter(r => rowSelection[r.id]).map(r => r.original) : [];
177
+ const limparSelecao = () => table.resetRowSelection();
178
+ // F10: o botão DIZ o escopo, e é essa a exigência do item — "exportar" sozinho
179
+ // esconde a pergunta que importa: exportar o quê, tudo ou o que está na tela?
180
+ // A precedência é a que a pessoa acabou de fazer: se marcou, é o marcado; se
181
+ // filtrou, é o filtrado; senão é tudo.
182
+ //
183
+ // FRONTEIRA, e ela não se move: a grade não gera arquivo nem baixa nada. Formato,
184
+ // codificação e transporte são do consumidor — é a mesma linha que a Parte G traça
185
+ // para o envio de arquivo. O que entra é a peça de interface e o escopo certo.
186
+ const selecionadasExp = onExport ? table.getPreFilteredRowModel().flatRows.filter(r => rowSelection[r.id]).map(r => r.original) : [];
187
+ const filtradas = onExport ? table.getFilteredRowModel().rows.map(r => r.original) : [];
188
+ const escopo = selecionadasExp.length ? { linhas: selecionadasExp, filtered: false, selected: true, sufixo: s.dataGridExportSelected }
189
+ : filtradas.length !== data.length ? { linhas: filtradas, filtered: true, selected: false, sufixo: s.dataGridExportFiltered }
190
+ : { linhas: filtradas, filtered: false, selected: false, sufixo: s.dataGridExportRows };
191
+ const exportar = onExport ? _jsx(Button, { variant: "secondary", leadingIcon: "download", onClick: () => onExport(escopo.linhas, { count: escopo.linhas.length, filtered: escopo.filtered, selected: escopo.selected }), children: `${s.dataGridExport} ${escopo.linhas.length} ${escopo.sufixo}` }) : null;
192
+ const busca = filterable ? _jsx(SearchField, { value: globalFilter, onChange: e => setGlobalFilter(e.target.value), placeholder: s.dataGridFilter, "aria-label": s.dataGridFilter }) : null;
193
+ // F7: esconder coluna é escolher várias de uma lista — o mesmo `MultiCombobox` da
194
+ // faceta do F3. Terceira peça desta parte que entra por reuso em vez de construção.
195
+ const ocultaveis = hideableColumns ? table.getAllLeafColumns().filter(c => c.getCanHide() && c.id !== "select") : [];
196
+ const itensColuna = ocultaveis.map(c => ({ value: c.id, label: String(c.columnDef.header ?? c.id) }));
197
+ const seletorColunasEl = hideableColumns ? _jsx(MultiCombobox, { items: itensColuna, value: itensColuna.filter(i => table.getColumn(i.value)?.getIsVisible()), onValueChange: v => setColumnVisibility(Object.fromEntries(ocultaveis.map(c => [c.id, v.some(i => i.value === c.id)]))), label: _jsx("span", { className: "sr-only", children: s.dataGridColumns }), placeholder: s.dataGridColumns }) : null;
198
+ // O Cluster só entra com DOIS ou mais controles: sozinho, cada um continua item de
199
+ // grade e ocupa a largura toda, como antes desta parte. Medido no F7.
200
+ const controles = [busca, seletorColunasEl, exportar].filter(Boolean).map((c, i) => _jsx(React.Fragment, { children: c }, i));
201
+ // F8: dado velho sem aviso é pior que tela vazia — então o aviso é TEXTO, e não uma
202
+ // cor. `Alert` já resolve os dois lados: `danger` vira role="alert" (interrompe),
203
+ // os outros viram role="status" (entra na próxima pausa do leitor de tela).
204
+ //
205
+ // `loading` NÃO apaga o que já está na tela: recarregar não é motivo para o
206
+ // consumidor perder o que estava lendo. Só quando não há linha nenhuma é que
207
+ // entram os esqueletos. O `aria-busy` diz o resto.
208
+ //
209
+ // Os nomes daqui são os DESTA grade. A Parte J é que vai nomear os estados
210
+ // universais uma vez só — antecipá-la aqui criaria o segundo vocabulário que ela
211
+ // existe para impedir.
212
+ //
213
+ // A Parte J (09/08/2026) nomeou os estados universais, e este `state` passou a ACEITÁ-LOS —
214
+ // era a previsão escrita no parágrafo acima e ela se cumpriu sem renomear nada: `stale` e
215
+ // `partial` já se chamavam assim. `loading` e `error` continuam sendo DESTA grade e de
216
+ // propósito: nos sete universais a tela AINDA SERVE, e sem linha nenhuma ela não serve.
217
+ //
218
+ // As três frases da grade ficam, e não é apego — elas falam de LINHAS ("algumas linhas não
219
+ // puderam ser carregadas") onde a universal fala do genérico ("parte disto"). Mais específico
220
+ // ganha de mais geral; o universal entra para os quatro estados que a grade não tinha.
221
+ const carregando = state === "loading";
222
+ const universal = state && state !== "loading" && state !== "error" ? state : null;
223
+ const recado = state && !carregando ? (stateMessage ?? (state === "stale" ? s.dataGridStale : state === "partial" ? s.dataGridPartial : state === "error" ? s.dataGridError : s.universalState[state])) : null;
224
+ // O marcador vai na GRADE, não no recado: quem está obsoleto é a tabela, e o `Alert` é só
225
+ // como ela conta isso. Achado pelo check 30 na primeira execução dele — eu tinha marcado o
226
+ // recado, e com `state="loading"` não há recado nenhum, então o estado não chegava ao DOM.
227
+ return _jsxs("div", { className: cx("datagrid", stickyHeader && "datagrid-sticky", className), "data-state": state, children: [emLote.length > 0 && _jsxs(Toolbar, { label: s.dataGridBulkLabel, children: [_jsxs("span", { className: "hint", children: [emLote.length, " ", s.dataGridSelected] }), _jsx(ToolbarSeparator, {}), bulkActions?.map(a => _jsx(ToolbarButton, { size: "sm", leadingIcon: a.icon, onClick: () => a.onAction(emLote, limparSelecao), children: a.label }, a.id)), _jsx(ToolbarButton, { size: "sm", onClick: limparSelecao, children: s.dataGridClearSelection })] }), controles.length > 1 ? _jsx(Cluster, { children: controles }) : controles[0] ?? null, recado && _jsx(Alert, { variant: state === "error" ? "danger" : universal ? stateSeverity(universal) : "warning", children: recado }), (() => {
228
+ // F9: o embrulho de duas colunas só existe COM o painel aberto — fechado, a
229
+ // marcação é a de sempre. É a lição do F7: não mudar a estrutura de quem não
230
+ // pediu nada. A largura do painel é `--datagrid-detail-w`, e quem decide se
231
+ // ele cabe é a largura da GRADE, não a da janela: por isso `@container`, no
232
+ // precedente do MediaPlayer.
233
+ 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 => {
235
+ 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" })] })
238
+ : 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
+ const d = e.key === "ArrowLeft" ? -16 : e.key === "ArrowRight" ? 16 : 0;
240
+ if (!d)
241
+ return;
242
+ e.preventDefault();
243
+ setColumnSizing({ ...columnSizing, [h.column.id]: Math.max(40, h.column.getSize() + d) });
244
+ } })] }, h.id);
245
+ }) }, hg.id)), !!filters?.length && _jsx("tr", { className: "datagrid-filters", children: table.getVisibleLeafColumns().map(col => {
246
+ const spec = filters.find(f => f.column === col.id);
247
+ 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
+ }) })] }), _jsx("tbody", { children: carregando && !rows.length
249
+ ? 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 }) }) })] }) }));
252
+ if (!renderDetail || !linhaAberta)
253
+ return tabela;
254
+ 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)] })] });
255
+ })(), paginated && table.getPageCount() > 1 && _jsx(Pagination, { page: pageIndex + 1, total: table.getPageCount(), onPageChange: p => table.setPageIndex(p - 1) })] });
76
256
  }
@@ -1,4 +1,5 @@
1
1
  import React, { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
+ import { type UniversalState } from "./internal.js";
2
3
  import { type IconName } from "./system.js";
3
4
  import { type OverlaySide } from "./overlays.js";
4
5
  export type BadgeVariant = "neutral" | "primary" | "info" | "success" | "warning" | "danger" | "running" | "paused" | "offline" | "review";
@@ -6,17 +7,20 @@ export declare function Badge({ variant, className, ...props }: HTMLAttributes<H
6
7
  variant?: BadgeVariant;
7
8
  }): React.JSX.Element;
8
9
  export type StatusVariant = "neutral" | "online" | "offline" | "busy" | "away" | "running" | "success" | "warning" | "danger" | "info";
9
- export declare function Status({ variant, children, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
10
+ export declare function Status({ variant, state, children, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
10
11
  variant?: StatusVariant;
12
+ state?: UniversalState;
11
13
  }): React.JSX.Element;
12
14
  export type AlertVariant = "info" | "success" | "warning" | "danger";
13
- export declare function Alert({ variant, title, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
15
+ export declare function Alert({ variant, state, title, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
14
16
  variant?: AlertVariant;
17
+ state?: UniversalState;
15
18
  title?: ReactNode;
16
19
  }): React.JSX.Element;
17
20
  export type BannerVariant = AlertVariant;
18
- export declare function Banner({ variant, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
21
+ export declare function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
19
22
  variant?: BannerVariant;
23
+ state?: UniversalState;
20
24
  title?: ReactNode;
21
25
  icon?: IconName;
22
26
  onDismiss?: () => void;
@@ -26,12 +30,18 @@ export declare function Progress({ value, label }: {
26
30
  label?: string;
27
31
  }): React.JSX.Element;
28
32
  export declare function Skeleton({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): React.JSX.Element;
29
- export declare function EmptyState({ icon, title, titleAs: TitleTag, description, action }: {
33
+ export declare function Spinner({ size, label, decorative, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
34
+ size?: "sm" | "md" | "lg";
35
+ label?: string;
36
+ decorative?: boolean;
37
+ }): React.JSX.Element;
38
+ export declare function EmptyState({ icon, title, titleAs: TitleTag, description, action, state }: {
30
39
  icon?: IconName;
31
40
  title: ReactNode;
32
41
  titleAs?: "h2" | "h3" | "h4" | "p";
33
42
  description?: ReactNode;
34
43
  action?: ReactNode;
44
+ state?: UniversalState;
35
45
  }): React.JSX.Element;
36
46
  export interface NotificationItem {
37
47
  id: string;
package/dist/feedback.js CHANGED
@@ -1,10 +1,11 @@
1
+ "use client";
1
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
4
5
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
5
6
  import React from "react";
6
7
  import { Popover as BasePopover } from "@base-ui/react/popover";
7
- import { cx, useAureaStrings } from "./internal.js";
8
+ import { cx, useAureaStrings, usePortalContainer, stateSeverity } from "./internal.js";
8
9
  import { Icon } from "./system.js";
9
10
  import { Button, IconButton } from "./actions.js";
10
11
  export function Badge({ variant = "neutral", className, ...props }) { return _jsx("span", { className: cx("badge", variant !== "neutral" && `badge-${variant}`, className), ...props }); }
@@ -14,15 +15,43 @@ export function Badge({ variant = "neutral", className, ...props }) { return _js
14
15
  // --foreground. Cor não é o único sinal — quem diz o estado é o texto (WCAG 1.4.1); o
15
16
  // ponto é decorativo e sai do leitor de tela. ponytail: rótulo é do consumidor (sem i18n
16
17
  // nova) — a variante é só a cor.
17
- export function Status({ variant = "neutral", children, className, ...props }) { return _jsxs("span", { className: cx("status", variant !== "neutral" && `status-${variant}`, className), ...props, children: [_jsx("i", { className: "status-dot", "aria-hidden": "true" }), _jsx("span", { className: "status-label", children: children })] }); }
18
- export function Alert({ variant = "info", title, children, className, ...props }) { return _jsxs("div", { className: cx("alert", `alert-${variant}`, className), role: variant === "danger" ? "alert" : "status", ...props, children: [title && _jsx("strong", { children: title }), children] }); }
19
- export function Banner({ variant = "info", title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); return _jsxs("div", { className: cx("banner", `banner-${variant}`, className), role: variant === "danger" ? "alert" : "status", ...props, children: [icon ? _jsx(Icon, { name: icon }) : _jsx("span", {}), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
20
- export function Progress({ value, label }) { return _jsx("div", { children: _jsx("div", { className: "progress", role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": value, children: _jsx("span", { style: { width: `${Math.max(0, Math.min(100, value))}%` } }) }) }); }
18
+ // `state` (Parte J) é EIXO À PARTE de `variant`, e os dois convivem: a variante é a cor do
19
+ // ponto, o estado é a condição universal. Quem passa `state` e não passa `variant` recebe a
20
+ // cor derivada `offline` fica com o ponto vazado que ele tinha desde sempre, os outros
21
+ // seis caem na gravidade. Quem passa os dois manda, porque o consumidor sabe se aquele
22
+ // "esperando" dele é grave. E o rótulo é o do consumidor, como sempre foi: a string universal
23
+ // entra só quando não há filho, para o componente não passar a inventar texto.
24
+ 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) })] }); }
25
+ 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)] }); }
26
+ 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", {})] }); }
27
+ // A barra grampeava 0..100 e o `aria-valuenow` NÃO — medido ao publicar o contrato de API na
28
+ // Parte E: com value=150 o desenho parava em 100% e o leitor de tela anunciava "150 de 100".
29
+ // A causa é a de sempre: o grampo existia num lugar só. Agora é UMA expressão que serve os dois,
30
+ // então não há como divergirem de novo.
31
+ // SÓ DETERMINADA, de propósito: não há modo indeterminado nem `.progress` para ele no core. A
32
+ // ficha dizia que havia e era falso — corrigido junto, porque contrato que promete o que não
33
+ // existe custa mais que ausência.
34
+ export function Progress({ value, label }) { const pct = Math.max(0, Math.min(100, value)); return _jsx("div", { children: _jsx("div", { className: "progress", role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": pct, children: _jsx("span", { style: { width: `${pct}%` } }) }) }); }
21
35
  export function Skeleton({ className, ...props }) { return _jsx("div", { className: cx("skeleton", className), "aria-hidden": "true", ...props }); }
36
+ // Spinner (Lote 1 do BUILDING.md). O glifo `.spinner` existia no core desde sempre e era usado
37
+ // SÓ por dentro do Button — nunca teve componente, então nunca teve papel acessível: quem
38
+ // esperava carregando não ouvia nada. As três referências que o têm concordam em `role="status"`
39
+ // com rótulo, e é o que faltava.
40
+ // Sem escolha de "estilo" de spinner (ponto, barra, pinwheel): é UM glifo, e um sistema com
41
+ // cinco spinners é um sistema sem spinner.
42
+ // `decorative` existe para o caso em que ele vive DENTRO de um controle que já anuncia o estado
43
+ // (o `Button loading` faz isso com aria-busy) — dois anúncios para o mesmo fato é ruído.
44
+ // O default é `sm` e não `md` de propósito: o lugar natural do spinner é DENTRO de um controle,
45
+ // ao lado de texto, e é o tamanho que ele sempre teve aqui. Um default maior mudaria a
46
+ // aparência de todo botão em carregamento sem ninguém ter pedido.
47
+ 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 }); }
22
48
  // titleAs: o nível do título é do DOCUMENTO, não do componente. Fixo em h3, um empty state
23
49
  // no alto de uma página vira h1→h3 e a hierarquia quebra (axe heading-order). Default h3
24
50
  // para não mexer em quem já consome; quem sabe o contexto passa o nível certo.
25
- export function EmptyState({ icon = "document--blank", title, titleAs: TitleTag = "h3", description, action }) { return _jsxs("div", { className: "empty-state", children: [_jsx(Icon, { name: icon, size: "xl" }), _jsx(TitleTag, { className: "empty-title", children: title }), description && _jsx("p", { className: "muted", children: description }), action] }); }
51
+ // `state` aqui NÃO deriva cor nenhuma o vazio é neutro e um empty state colorido seria
52
+ // alarme onde há ausência. O que ele faz é o marcador no DOM e a descrição padrão, para
53
+ // "sem conexão" e "resultado parcial" não serem sete frases diferentes em sete telas.
54
+ export function EmptyState({ icon = "document--blank", title, titleAs: TitleTag = "h3", description, action, state }) { const s = useAureaStrings(); const desc = description ?? (state ? s.universalState[state] : null); return _jsxs("div", { className: "empty-state", "data-state": state, children: [_jsx(Icon, { name: icon, size: "xl" }), _jsx(TitleTag, { className: "empty-title", children: title }), desc && _jsx("p", { className: "muted", children: desc }), action] }); }
26
55
  function groupNotifications(items) {
27
56
  const out = [];
28
57
  for (const it of items) {
@@ -36,6 +65,7 @@ function groupNotifications(items) {
36
65
  }
37
66
  export function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon = "notification", side = "bottom" }) {
38
67
  const s = useAureaStrings();
68
+ const portal = usePortalContainer();
39
69
  const title = label ?? s.notificationsLabel;
40
70
  const unread = items.filter(i => !i.read).length;
41
71
  const groups = groupNotifications(items);
@@ -62,7 +92,7 @@ export function NotificationCenter({ items, onItemClick, onMarkAllRead, label, i
62
92
  ? _jsx("button", { type: "button", className: "notification-item", "data-read": it.read || undefined, onClick: () => { it.onClick?.(); onItemClick?.(it); }, children: body })
63
93
  : _jsx("div", { className: "notification-item", "data-read": it.read || undefined, children: body });
64
94
  };
65
- 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, { 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
95
+ 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
66
96
  ? _jsx("div", { className: "notification-list", children: groups.map((g, gi) => {
67
97
  const gid = baseId + gi;
68
98
  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);