@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/actions.d.ts +13 -0
- package/dist/actions.js +5 -0
- package/dist/agents.d.ts +209 -0
- package/dist/agents.js +302 -0
- package/dist/calendar.d.ts +6 -0
- package/dist/calendar.js +27 -0
- package/dist/chart.d.ts +10 -0
- package/dist/chart.js +53 -0
- package/dist/code-editor.js +1 -0
- package/dist/code.js +18 -2
- package/dist/communication.js +1 -0
- package/dist/data-display.js +1 -0
- package/dist/data-grid.d.ts +52 -3
- package/dist/data-grid.js +205 -25
- package/dist/feedback.d.ts +14 -4
- package/dist/feedback.js +37 -7
- package/dist/file-input.d.ts +21 -2
- package/dist/file-input.js +154 -21
- package/dist/graph.d.ts +33 -0
- package/dist/graph.js +178 -0
- package/dist/identity.d.ts +12 -3
- package/dist/identity.js +24 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +15 -1
- package/dist/inputs.d.ts +26 -0
- package/dist/inputs.js +69 -4
- package/dist/internal.d.ts +4 -66
- package/dist/internal.js +25 -14
- package/dist/layout.d.ts +2 -1
- package/dist/layout.js +61 -7
- package/dist/media.js +1 -0
- package/dist/navigation.d.ts +27 -1
- package/dist/navigation.js +32 -7
- package/dist/overlays.d.ts +5 -0
- package/dist/overlays.js +21 -9
- package/dist/pure.d.ts +166 -0
- package/dist/pure.js +87 -0
- package/dist/qrcode.d.ts +2 -1
- package/dist/qrcode.js +3 -2
- package/dist/system.d.ts +2 -1
- package/dist/system.js +13 -4
- package/package.json +176 -144
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
|
+
}
|
package/dist/code-editor.js
CHANGED
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
|
-
|
|
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
|
-
|
|
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)) }); }
|
package/dist/communication.js
CHANGED
package/dist/data-display.js
CHANGED
package/dist/data-grid.d.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { type ColumnDef } from "@tanstack/react-table";
|
|
3
|
-
|
|
4
|
-
|
|
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 {
|
|
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.
|
|
15
|
-
//
|
|
16
|
-
//
|
|
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
|
-
|
|
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] =
|
|
28
|
-
const [globalFilter, setGlobalFilter] =
|
|
29
|
-
const [
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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:
|
|
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
|
-
...(
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
}
|
package/dist/feedback.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 já tinha desde sempre, os outros
|
|
21
|
+
// seis caem na gravidade. Quem passa os dois manda, porque só 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
|
-
|
|
51
|
+
// `state` aqui NÃO deriva cor nenhuma — o vazio já é 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);
|