@aurea-uds/react 0.1.0 → 0.3.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/README.md +34 -2
- package/dist/actions.d.ts +20 -0
- package/dist/actions.js +59 -5
- 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-client.d.ts +6 -0
- package/dist/code-client.js +24 -0
- package/dist/code-editor.js +1 -0
- package/dist/code.d.ts +2 -13
- package/dist/code.js +5 -18
- package/dist/communication.js +1 -0
- package/dist/data-display-client.d.ts +14 -0
- package/dist/data-display-client.js +22 -0
- package/dist/data-display.d.ts +2 -23
- package/dist/data-display.js +5 -13
- package/dist/data-grid.d.ts +52 -3
- package/dist/data-grid.js +205 -25
- package/dist/feedback-client.d.ts +64 -0
- package/dist/feedback-client.js +110 -0
- package/dist/feedback.d.ts +2 -53
- package/dist/feedback.js +5 -71
- package/dist/file-input.d.ts +21 -2
- package/dist/file-input.js +202 -21
- package/dist/graph.d.ts +33 -0
- package/dist/graph.js +178 -0
- package/dist/identity-client.d.ts +9 -0
- package/dist/identity-client.js +12 -0
- package/dist/identity.d.ts +2 -8
- package/dist/identity.js +5 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +33 -3
- package/dist/inputs-client.d.ts +105 -0
- package/dist/inputs-client.js +264 -0
- package/dist/inputs.d.ts +2 -65
- package/dist/inputs.js +9 -61
- package/dist/internal.d.ts +23 -70
- package/dist/internal.js +115 -23
- package/dist/layout-client.d.ts +9 -0
- package/dist/layout-client.js +72 -0
- package/dist/layout.d.ts +2 -14
- package/dist/layout.js +5 -22
- package/dist/markup.d.ts +72 -0
- package/dist/markup.js +87 -0
- package/dist/media-client.d.ts +36 -0
- package/dist/media-client.js +239 -0
- package/dist/media.d.ts +2 -9
- package/dist/media.js +5 -98
- package/dist/navigation-client.d.ts +94 -0
- package/dist/navigation-client.js +154 -0
- package/dist/navigation.d.ts +2 -59
- package/dist/navigation.js +5 -113
- package/dist/overlays.d.ts +28 -0
- package/dist/overlays.js +66 -10
- package/dist/pure.d.ts +196 -0
- package/dist/pure.js +111 -0
- package/dist/qrcode.d.ts +2 -1
- package/dist/qrcode.js +3 -2
- package/dist/system.d.ts +11 -1
- package/dist/system.js +59 -4
- package/package.json +40 -4
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
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
|
|
2
|
+
import { type UniversalState } from "./internal.js";
|
|
3
|
+
import { type IconName } from "./system.js";
|
|
4
|
+
import { type OverlaySide } from "./overlays.js";
|
|
5
|
+
export type StatusVariant = "neutral" | "online" | "offline" | "busy" | "away" | "running" | "success" | "warning" | "danger" | "info";
|
|
6
|
+
export declare function Status({ variant, state, children, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
|
|
7
|
+
variant?: StatusVariant;
|
|
8
|
+
state?: UniversalState;
|
|
9
|
+
}): React.JSX.Element;
|
|
10
|
+
export type AlertVariant = "info" | "success" | "warning" | "danger";
|
|
11
|
+
export declare function Alert({ variant, state, title, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
12
|
+
variant?: AlertVariant;
|
|
13
|
+
state?: UniversalState;
|
|
14
|
+
title?: ReactNode;
|
|
15
|
+
}): React.JSX.Element;
|
|
16
|
+
export type BannerVariant = AlertVariant;
|
|
17
|
+
export declare function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
18
|
+
variant?: BannerVariant;
|
|
19
|
+
state?: UniversalState;
|
|
20
|
+
title?: ReactNode;
|
|
21
|
+
icon?: IconName;
|
|
22
|
+
onDismiss?: () => void;
|
|
23
|
+
}): React.JSX.Element;
|
|
24
|
+
export declare function Spinner({ size, label, decorative, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
|
|
25
|
+
size?: "sm" | "md" | "lg";
|
|
26
|
+
label?: string;
|
|
27
|
+
decorative?: boolean;
|
|
28
|
+
}): React.JSX.Element;
|
|
29
|
+
export type DataStateValue = "loading" | "error" | "empty" | UniversalState;
|
|
30
|
+
export declare function DataState({ state, message, skeleton, emptyTitle, emptyIcon, action, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
31
|
+
state?: DataStateValue;
|
|
32
|
+
message?: ReactNode;
|
|
33
|
+
skeleton?: ReactNode;
|
|
34
|
+
emptyTitle?: ReactNode;
|
|
35
|
+
emptyIcon?: IconName;
|
|
36
|
+
action?: ReactNode;
|
|
37
|
+
children: ReactNode | (() => ReactNode);
|
|
38
|
+
}): React.JSX.Element;
|
|
39
|
+
export declare function EmptyState({ icon, title, titleAs: TitleTag, description, action, state }: {
|
|
40
|
+
icon?: IconName;
|
|
41
|
+
title: ReactNode;
|
|
42
|
+
titleAs?: "h2" | "h3" | "h4" | "p";
|
|
43
|
+
description?: ReactNode;
|
|
44
|
+
action?: ReactNode;
|
|
45
|
+
state?: UniversalState;
|
|
46
|
+
}): React.JSX.Element;
|
|
47
|
+
export interface NotificationItem {
|
|
48
|
+
id: string;
|
|
49
|
+
title: ReactNode;
|
|
50
|
+
description?: ReactNode;
|
|
51
|
+
time?: ReactNode;
|
|
52
|
+
icon?: IconName;
|
|
53
|
+
read?: boolean;
|
|
54
|
+
group?: string;
|
|
55
|
+
onClick?: () => void;
|
|
56
|
+
}
|
|
57
|
+
export declare function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon, side }: {
|
|
58
|
+
items: NotificationItem[];
|
|
59
|
+
onItemClick?: (item: NotificationItem) => void;
|
|
60
|
+
onMarkAllRead?: () => void;
|
|
61
|
+
label?: string;
|
|
62
|
+
icon?: IconName;
|
|
63
|
+
side?: OverlaySide;
|
|
64
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
|
|
4
|
+
// do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
|
|
5
|
+
// internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
8
|
+
import { cx, useAureaStrings, usePortalContainer, stateSeverity } from "./internal.js";
|
|
9
|
+
import { Icon } from "./system.js";
|
|
10
|
+
import { Button, IconButton } from "./actions.js";
|
|
11
|
+
// `oracle` saiu daqui (achado M9, 26/07/2026): estava no tipo e no CSS, e NÃO estava na
|
|
12
|
+
// ficha — ou seja, era superfície pública que o contrato não declarava. E é vocabulário do
|
|
13
|
+
// app de origem (papel de agente), não do sistema, como as classes de domínio que o achado
|
|
14
|
+
// A6 mapeou. As classes `.badge-oracle`/`.btn-oracle` seguem no core porque o
|
|
15
|
+
// `apps/docs/index.html` escrito à mão as usa; saem junto com ele, na Fase 4 do plano.
|
|
16
|
+
// `.btn-oracle` nunca foi alcançável pelo React — `ButtonVariant` não tem `oracle`.
|
|
17
|
+
import { Skeleton } from "./markup.js";
|
|
18
|
+
// Status (DIRECTION §3.6): condição OPERACIONAL — ponto + rótulo. Não é Badge: Badge é
|
|
19
|
+
// metadado curto num pill; Status diz em que estado a coisa está. Reusa o .status-dot que
|
|
20
|
+
// já existia solto. A variante colore só o PONTO (currentColor); o rótulo fica legível em
|
|
21
|
+
// --foreground. Cor não é o único sinal — quem diz o estado é o texto (WCAG 1.4.1); o
|
|
22
|
+
// ponto é decorativo e sai do leitor de tela. ponytail: rótulo é do consumidor (sem i18n
|
|
23
|
+
// nova) — a variante é só a cor.
|
|
24
|
+
// `state` (Parte J) é EIXO À PARTE de `variant`, e os dois convivem: a variante é a cor do
|
|
25
|
+
// ponto, o estado é a condição universal. Quem passa `state` e não passa `variant` recebe a
|
|
26
|
+
// cor derivada — `offline` fica com o ponto vazado que ele já tinha desde sempre, os outros
|
|
27
|
+
// seis caem na gravidade. Quem passa os dois manda, porque só o consumidor sabe se aquele
|
|
28
|
+
// "esperando" dele é grave. E o rótulo é o do consumidor, como sempre foi: a string universal
|
|
29
|
+
// entra só quando não há filho, para o componente não passar a inventar texto.
|
|
30
|
+
export function Status({ variant, state, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? (state === "offline" ? "offline" : stateSeverity(state)) : "neutral"); return _jsxs("span", { className: cx("status", v !== "neutral" && `status-${v}`, className), "data-state": state, ...props, children: [_jsx("i", { className: "status-dot", "aria-hidden": "true" }), _jsx("span", { className: "status-label", children: children ?? (state ? s.universalState[state] : null) })] }); }
|
|
31
|
+
export function Alert({ variant, state, title, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("alert", `alert-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }); }
|
|
32
|
+
export function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("banner", `banner-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [icon ? _jsx(Icon, { name: icon }) : _jsx("span", {}), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
|
|
33
|
+
// A barra grampeava 0..100 e o `aria-valuenow` NÃO — medido ao publicar o contrato de API na
|
|
34
|
+
// Parte E: com value=150 o desenho parava em 100% e o leitor de tela anunciava "150 de 100".
|
|
35
|
+
// A causa é a de sempre: o grampo existia num lugar só. Agora é UMA expressão que serve os dois,
|
|
36
|
+
// então não há como divergirem de novo.
|
|
37
|
+
// SÓ DETERMINADA, de propósito: não há modo indeterminado nem `.progress` para ele no core. A
|
|
38
|
+
// ficha dizia que havia e era falso — corrigido junto, porque contrato que promete o que não
|
|
39
|
+
// existe custa mais que ausência.
|
|
40
|
+
export function Spinner({ size = "sm", label, decorative, className, ...props }) { const s = useAureaStrings(); return _jsx("span", { className: cx("spinner", size !== "sm" && `spinner-${size}`, className), ...(decorative ? { "aria-hidden": true } : { role: "status", "aria-label": label ?? s.loading }), ...props }); }
|
|
41
|
+
// `children` como FUNÇÃO é de propósito para o caso `loading`: assim o consumidor não paga o
|
|
42
|
+
// render do conteúdo enquanto ele não existe. Aceita nó também, porque a maioria das telas já
|
|
43
|
+
// tem o conteúdo pronto e obrigar função seria cerimônia.
|
|
44
|
+
export function DataState({ state, message, skeleton, emptyTitle, emptyIcon, action, children, className, ...props }) {
|
|
45
|
+
const s = useAureaStrings();
|
|
46
|
+
const conteudo = () => typeof children === "function" ? children() : children;
|
|
47
|
+
const caixa = (inner, ocupado) => _jsx("div", { className: cx("data-state", className), "aria-busy": ocupado || undefined, "data-state": state, ...props, children: inner });
|
|
48
|
+
if (state === "loading")
|
|
49
|
+
return caixa(skeleton ?? _jsx(Skeleton, { style: { height: "var(--space-8)" } }), true);
|
|
50
|
+
if (state === "error")
|
|
51
|
+
return caixa(_jsx(Alert, { variant: "danger", children: message ?? s.dataError }));
|
|
52
|
+
// `titleAs="p"` e não o `h3` padrão do EmptyState: aqui o vazio é estado de uma REGIÃO, não
|
|
53
|
+
// seção do documento. Injetar um h3 no meio do conteúdo do consumidor salta nível de título —
|
|
54
|
+
// o gate de hierarquia pegou (`salto h1 → h3`) e o axe repetiu como `heading-order`.
|
|
55
|
+
if (state === "empty")
|
|
56
|
+
return caixa(_jsx(EmptyState, { icon: emptyIcon, titleAs: "p", title: emptyTitle ?? s.dataEmpty, description: message, action: action }));
|
|
57
|
+
// Os universais NÃO substituem o conteúdo: eles o acompanham. É a regra do DataGrid, e o
|
|
58
|
+
// motivo é o mesmo — a pessoa precisa do dado E do aviso, não de um no lugar do outro.
|
|
59
|
+
if (state)
|
|
60
|
+
return caixa(_jsxs(_Fragment, { children: [_jsx(Alert, { variant: stateSeverity(state), state: state, children: message }), conteudo()] }));
|
|
61
|
+
return caixa(conteudo());
|
|
62
|
+
}
|
|
63
|
+
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] }); }
|
|
64
|
+
function groupNotifications(items) {
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const it of items) {
|
|
67
|
+
const last = out[out.length - 1];
|
|
68
|
+
if (last && last.label === it.group)
|
|
69
|
+
last.items.push(it);
|
|
70
|
+
else
|
|
71
|
+
out.push({ label: it.group, items: [it] });
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
export function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon = "notification", side = "bottom" }) {
|
|
76
|
+
const s = useAureaStrings();
|
|
77
|
+
const portal = usePortalContainer();
|
|
78
|
+
const title = label ?? s.notificationsLabel;
|
|
79
|
+
const unread = items.filter(i => !i.read).length;
|
|
80
|
+
const groups = groupNotifications(items);
|
|
81
|
+
const baseId = React.useId();
|
|
82
|
+
const seen = React.useRef(undefined);
|
|
83
|
+
const [announce, setAnnounce] = React.useState("");
|
|
84
|
+
React.useEffect(() => {
|
|
85
|
+
const ids = new Set(items.map(i => i.id));
|
|
86
|
+
if (seen.current === undefined) {
|
|
87
|
+
seen.current = ids;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const fresh = items.filter(i => !seen.current.has(i.id)).length;
|
|
91
|
+
seen.current = ids;
|
|
92
|
+
// Texto idêntico duas vezes seguidas não muta o DOM e o leitor silencia a 2ª
|
|
93
|
+
// chegada (auditoria 18/07/2026, MÉDIO 4). Um NBSP alternado no fim força a
|
|
94
|
+
// mutação sem mudar o que se ouve.
|
|
95
|
+
if (fresh)
|
|
96
|
+
setAnnounce(prev => { const text = `${fresh} ${s.notificationNew}`; return prev === text ? text + " " : text; });
|
|
97
|
+
}, [items, s.notificationNew]);
|
|
98
|
+
const renderRow = (it) => {
|
|
99
|
+
const body = _jsxs(_Fragment, { children: [_jsx("span", { className: "notification-dot", "aria-hidden": "true" }), _jsxs("span", { className: "notification-item-title", children: [it.icon && _jsx(Icon, { name: it.icon, size: "sm" }), !it.read && _jsxs("span", { className: "sr-only", children: [s.notificationUnread, " "] }), it.title] }), it.time && _jsx("span", { className: "notification-time", children: it.time }), it.description && _jsx("span", { className: "notification-item-desc", children: it.description })] });
|
|
100
|
+
return it.onClick || onItemClick
|
|
101
|
+
? _jsx("button", { type: "button", className: "notification-item", "data-read": it.read || undefined, onClick: () => { it.onClick?.(); onItemClick?.(it); }, children: body })
|
|
102
|
+
: _jsx("div", { className: "notification-item", "data-read": it.read || undefined, children: body });
|
|
103
|
+
};
|
|
104
|
+
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
|
|
105
|
+
? _jsx("div", { className: "notification-list", children: groups.map((g, gi) => {
|
|
106
|
+
const gid = baseId + gi;
|
|
107
|
+
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);
|
|
108
|
+
}) })
|
|
109
|
+
: _jsx("p", { className: "notification-empty", children: s.notificationEmpty })] }) }) }), _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: announce })] });
|
|
110
|
+
}
|
package/dist/feedback.d.ts
CHANGED
|
@@ -1,53 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { type OverlaySide } from "./overlays.js";
|
|
4
|
-
export type BadgeVariant = "neutral" | "primary" | "info" | "success" | "warning" | "danger" | "running" | "paused" | "offline" | "review";
|
|
5
|
-
export declare function Badge({ variant, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
|
|
6
|
-
variant?: BadgeVariant;
|
|
7
|
-
}): React.JSX.Element;
|
|
8
|
-
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
|
-
variant?: StatusVariant;
|
|
11
|
-
}): React.JSX.Element;
|
|
12
|
-
export type AlertVariant = "info" | "success" | "warning" | "danger";
|
|
13
|
-
export declare function Alert({ variant, title, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
14
|
-
variant?: AlertVariant;
|
|
15
|
-
title?: ReactNode;
|
|
16
|
-
}): React.JSX.Element;
|
|
17
|
-
export type BannerVariant = AlertVariant;
|
|
18
|
-
export declare function Banner({ variant, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
19
|
-
variant?: BannerVariant;
|
|
20
|
-
title?: ReactNode;
|
|
21
|
-
icon?: IconName;
|
|
22
|
-
onDismiss?: () => void;
|
|
23
|
-
}): React.JSX.Element;
|
|
24
|
-
export declare function Progress({ value, label }: {
|
|
25
|
-
value: number;
|
|
26
|
-
label?: string;
|
|
27
|
-
}): React.JSX.Element;
|
|
28
|
-
export declare function Skeleton({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): React.JSX.Element;
|
|
29
|
-
export declare function EmptyState({ icon, title, titleAs: TitleTag, description, action }: {
|
|
30
|
-
icon?: IconName;
|
|
31
|
-
title: ReactNode;
|
|
32
|
-
titleAs?: "h2" | "h3" | "h4" | "p";
|
|
33
|
-
description?: ReactNode;
|
|
34
|
-
action?: ReactNode;
|
|
35
|
-
}): React.JSX.Element;
|
|
36
|
-
export interface NotificationItem {
|
|
37
|
-
id: string;
|
|
38
|
-
title: ReactNode;
|
|
39
|
-
description?: ReactNode;
|
|
40
|
-
time?: ReactNode;
|
|
41
|
-
icon?: IconName;
|
|
42
|
-
read?: boolean;
|
|
43
|
-
group?: string;
|
|
44
|
-
onClick?: () => void;
|
|
45
|
-
}
|
|
46
|
-
export declare function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon, side }: {
|
|
47
|
-
items: NotificationItem[];
|
|
48
|
-
onItemClick?: (item: NotificationItem) => void;
|
|
49
|
-
onMarkAllRead?: () => void;
|
|
50
|
-
label?: string;
|
|
51
|
-
icon?: IconName;
|
|
52
|
-
side?: OverlaySide;
|
|
53
|
-
}): React.JSX.Element;
|
|
1
|
+
export * from "./feedback-client.js";
|
|
2
|
+
export { Badge, Progress, Skeleton, type BadgeVariant } from "./markup.js";
|