@aurea-uds/react 0.1.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.
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
+ // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
4
+ // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
5
+ // SUBPATH PRÓPRIO (ver ./qrcode): é o único componente que precisa do @tanstack/react-table,
6
+ // que é peer OPCIONAL. Table, DataList, KPI e Timeline ficam em /data-display sem essa conta.
7
+ import React from "react";
8
+ import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, flexRender } from "@tanstack/react-table";
9
+ import { cx, useAureaStrings } from "./internal.js";
10
+ import { Icon } from "./system.js";
11
+ import { SearchField } from "./inputs.js";
12
+ import { Pagination } from "./navigation.js";
13
+ // DataGrid (Fase 4): TanStack Table v8 (8.21.3) headless + pele Aurea — sort,
14
+ // filtro global, paginação e seleção por checkbox. v8 porque o v9 ainda é beta
15
+ // (07/2026; decisão no ROADMAP). A pele reusa .table-wrap/th/td; o estado é
16
+ // interno (não controlado — controlar de fora só quando houver demanda real).
17
+ // A11y: aria-sort fica SÓ no th ordenado (padrão APG) e o cabeçalho ordenável é
18
+ // um <button> de verdade; a linha selecionada usa data-selected apenas para
19
+ // estilo — aria-selected é inválido em role=table, o estado acessível é o
20
+ // próprio checkbox. A paginação reusa o componente Pagination.
21
+ function GridCheck({ label, indeterminate, ...props }) {
22
+ return _jsxs("label", { className: "checkbox", children: [_jsx("input", { type: "checkbox", ref: el => { if (el)
23
+ el.indeterminate = !!indeterminate; }, ...props }), _jsx("span", { className: "control-mark" }), _jsx("span", { className: "sr-only", children: label })] });
24
+ }
25
+ export function DataGrid({ data, columns, label, filterable, pageSize, selectable, onSelectionChange, getRowId, className }) {
26
+ const s = useAureaStrings();
27
+ const [sorting, setSorting] = React.useState([]);
28
+ const [globalFilter, setGlobalFilter] = React.useState("");
29
+ const [rowSelection, setRowSelection] = React.useState({});
30
+ // Sem getRowId a seleção do TanStack é por ÍNDICE: trocar/reordenar data faria o
31
+ // checkbox "seguir" a posição e marcar OUTRO registro (auditoria 18/07/2026,
32
+ // ALTO 3). Sem id estável a seleção não sobrevive à mudança de dados — limpa.
33
+ // Com getRowId ela persiste corretamente; prefira passá-lo quando selectable.
34
+ const prevData = React.useRef(data);
35
+ React.useEffect(() => {
36
+ if (prevData.current === data)
37
+ return;
38
+ prevData.current = data;
39
+ if (!getRowId && Object.keys(rowSelection).length) {
40
+ setRowSelection({});
41
+ onSelectionChange?.([]);
42
+ }
43
+ }, [data]);
44
+ const allColumns = React.useMemo(() => selectable ? [{
45
+ id: "select", enableSorting: false,
46
+ header: ({ table }) => _jsx(GridCheck, { label: s.dataGridSelectAll, checked: table.getIsAllRowsSelected(), indeterminate: table.getIsSomeRowsSelected(), onChange: table.getToggleAllRowsSelectedHandler() }),
47
+ cell: ({ row }) => _jsx(GridCheck, { label: s.dataGridSelectRow, checked: row.getIsSelected(), disabled: !row.getCanSelect(), onChange: row.getToggleSelectedHandler() }),
48
+ }, ...columns] : columns, [selectable, columns, s]);
49
+ const table = useReactTable({
50
+ data, columns: allColumns, getRowId,
51
+ state: { sorting, globalFilter, rowSelection },
52
+ onSortingChange: setSorting,
53
+ onGlobalFilterChange: setGlobalFilter,
54
+ // emite as linhas ORIGINAIS já aqui (não em effect): getPreFilteredRowModel
55
+ // ignora filtro/página, então a seleção sobrevive a ambos.
56
+ onRowSelectionChange: updater => {
57
+ const next = typeof updater === "function" ? updater(rowSelection) : updater;
58
+ setRowSelection(next);
59
+ onSelectionChange?.(table.getPreFilteredRowModel().flatRows.filter(r => next[r.id]).map(r => r.original));
60
+ },
61
+ getCoreRowModel: getCoreRowModel(),
62
+ getSortedRowModel: getSortedRowModel(),
63
+ getFilteredRowModel: getFilteredRowModel(),
64
+ enableRowSelection: !!selectable,
65
+ ...(pageSize ? { getPaginationRowModel: getPaginationRowModel(), initialState: { pagination: { pageSize } } } : {}),
66
+ });
67
+ const rows = table.getRowModel().rows;
68
+ const sortIcon = (dir) => dir === "asc" ? "chevron--sort--up" : dir === "desc" ? "chevron--sort--down" : "chevron--sort";
69
+ return _jsxs("div", { className: cx("datagrid", className), children: [filterable && _jsx(SearchField, { value: globalFilter, onChange: e => setGlobalFilter(e.target.value), placeholder: s.dataGridFilter, "aria-label": s.dataGridFilter }), _jsx("div", { className: "table-wrap", role: "region", "aria-label": label ?? s.tableLabel, tabIndex: 0, children: _jsxs("table", { children: [_jsx("thead", { children: table.getHeaderGroups().map(hg => _jsx("tr", { children: hg.headers.map(h => {
70
+ const dir = h.column.getIsSorted();
71
+ return _jsx("th", { colSpan: h.colSpan, className: h.column.id === "select" ? "datagrid-selcol" : undefined, "aria-sort": dir === "asc" ? "ascending" : dir === "desc" ? "descending" : undefined, children: h.isPlaceholder ? null : h.column.getCanSort()
72
+ ? _jsxs("button", { type: "button", className: "datagrid-sort", onClick: h.column.getToggleSortingHandler(), children: [flexRender(h.column.columnDef.header, h.getContext()), _jsx(Icon, { name: sortIcon(dir), size: "sm", className: "datagrid-sort-icon" })] })
73
+ : flexRender(h.column.columnDef.header, h.getContext()) }, h.id);
74
+ }) }, hg.id)) }), _jsx("tbody", { children: rows.length ? rows.map(row => _jsx("tr", { "data-selected": row.getIsSelected() || undefined, children: row.getVisibleCells().map(cell => _jsx("td", { className: cell.column.id === "select" ? "datagrid-selcol" : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id)) }, row.id))
75
+ : _jsx("tr", { children: _jsx("td", { colSpan: table.getVisibleLeafColumns().length, className: "datagrid-empty", children: s.dataGridEmpty }) }) })] }) }), pageSize != null && table.getPageCount() > 1 && _jsx(Pagination, { page: table.getState().pagination.pageIndex + 1, total: table.getPageCount(), onPageChange: p => table.setPageIndex(p - 1) })] });
76
+ }
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from "react";
2
+ export declare function Accordion({ items }: {
3
+ items: Array<{
4
+ id: string;
5
+ title: ReactNode;
6
+ content: ReactNode;
7
+ }>;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function Accordion({ items }) { return _jsx("div", { className: "accordion", children: items.map(i => _jsxs("details", { children: [_jsx("summary", { children: i.title }), _jsx("div", { className: "accordion-content", children: i.content })] }, i.id)) }); }
@@ -0,0 +1,53 @@
1
+ import React, { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
+ import { type IconName } from "./system.js";
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;
@@ -0,0 +1,71 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
+ // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
4
+ // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
5
+ import React from "react";
6
+ import { Popover as BasePopover } from "@base-ui/react/popover";
7
+ import { cx, useAureaStrings } from "./internal.js";
8
+ import { Icon } from "./system.js";
9
+ import { Button, IconButton } from "./actions.js";
10
+ export function Badge({ variant = "neutral", className, ...props }) { return _jsx("span", { className: cx("badge", variant !== "neutral" && `badge-${variant}`, className), ...props }); }
11
+ // Status (DIRECTION §3.6): condição OPERACIONAL — ponto + rótulo. Não é Badge: Badge é
12
+ // metadado curto num pill; Status diz em que estado a coisa está. Reusa o .status-dot que
13
+ // já existia solto. A variante colore só o PONTO (currentColor); o rótulo fica legível em
14
+ // --foreground. Cor não é o único sinal — quem diz o estado é o texto (WCAG 1.4.1); o
15
+ // ponto é decorativo e sai do leitor de tela. ponytail: rótulo é do consumidor (sem i18n
16
+ // nova) — a variante é só a cor.
17
+ export function Status({ variant = "neutral", children, className, ...props }) { return _jsxs("span", { className: cx("status", variant !== "neutral" && `status-${variant}`, className), ...props, children: [_jsx("i", { className: "status-dot", "aria-hidden": "true" }), _jsx("span", { className: "status-label", children: children })] }); }
18
+ export function Alert({ variant = "info", title, children, className, ...props }) { return _jsxs("div", { className: cx("alert", `alert-${variant}`, className), role: variant === "danger" ? "alert" : "status", ...props, children: [title && _jsx("strong", { children: title }), children] }); }
19
+ export function Banner({ variant = "info", title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); return _jsxs("div", { className: cx("banner", `banner-${variant}`, className), role: variant === "danger" ? "alert" : "status", ...props, children: [icon ? _jsx(Icon, { name: icon }) : _jsx("span", {}), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
20
+ export function Progress({ value, label }) { return _jsx("div", { children: _jsx("div", { className: "progress", role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": value, children: _jsx("span", { style: { width: `${Math.max(0, Math.min(100, value))}%` } }) }) }); }
21
+ export function Skeleton({ className, ...props }) { return _jsx("div", { className: cx("skeleton", className), "aria-hidden": "true", ...props }); }
22
+ // titleAs: o nível do título é do DOCUMENTO, não do componente. Fixo em h3, um empty state
23
+ // no alto de uma página vira h1→h3 e a hierarquia quebra (axe heading-order). Default h3
24
+ // para não mexer em quem já consome; quem sabe o contexto passa o nível certo.
25
+ export function EmptyState({ icon = "document--blank", title, titleAs: TitleTag = "h3", description, action }) { return _jsxs("div", { className: "empty-state", children: [_jsx(Icon, { name: icon, size: "xl" }), _jsx(TitleTag, { className: "empty-title", children: title }), description && _jsx("p", { className: "muted", children: description }), action] }); }
26
+ function groupNotifications(items) {
27
+ const out = [];
28
+ for (const it of items) {
29
+ const last = out[out.length - 1];
30
+ if (last && last.label === it.group)
31
+ last.items.push(it);
32
+ else
33
+ out.push({ label: it.group, items: [it] });
34
+ }
35
+ return out;
36
+ }
37
+ export function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon = "notification", side = "bottom" }) {
38
+ const s = useAureaStrings();
39
+ const title = label ?? s.notificationsLabel;
40
+ const unread = items.filter(i => !i.read).length;
41
+ const groups = groupNotifications(items);
42
+ const baseId = React.useId();
43
+ const seen = React.useRef(undefined);
44
+ const [announce, setAnnounce] = React.useState("");
45
+ React.useEffect(() => {
46
+ const ids = new Set(items.map(i => i.id));
47
+ if (seen.current === undefined) {
48
+ seen.current = ids;
49
+ return;
50
+ }
51
+ const fresh = items.filter(i => !seen.current.has(i.id)).length;
52
+ seen.current = ids;
53
+ // Texto idêntico duas vezes seguidas não muta o DOM e o leitor silencia a 2ª
54
+ // chegada (auditoria 18/07/2026, MÉDIO 4). Um NBSP alternado no fim força a
55
+ // mutação sem mudar o que se ouve.
56
+ if (fresh)
57
+ setAnnounce(prev => { const text = `${fresh} ${s.notificationNew}`; return prev === text ? text + " " : text; });
58
+ }, [items, s.notificationNew]);
59
+ const renderRow = (it) => {
60
+ 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 })] });
61
+ return it.onClick || onItemClick
62
+ ? _jsx("button", { type: "button", className: "notification-item", "data-read": it.read || undefined, onClick: () => { it.onClick?.(); onItemClick?.(it); }, children: body })
63
+ : _jsx("div", { className: "notification-item", "data-read": it.read || undefined, children: body });
64
+ };
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
66
+ ? _jsx("div", { className: "notification-list", children: groups.map((g, gi) => {
67
+ const gid = baseId + gi;
68
+ 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);
69
+ }) })
70
+ : _jsx("p", { className: "notification-empty", children: s.notificationEmpty })] }) }) }), _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: announce })] });
71
+ }
@@ -0,0 +1,25 @@
1
+ import React, { type ReactNode } from "react";
2
+ export interface FileRejection {
3
+ file: File;
4
+ reason: string;
5
+ }
6
+ export interface UploadContext {
7
+ signal: AbortSignal;
8
+ onProgress: (fraction: number) => void;
9
+ }
10
+ export type UploadFn = (file: File, ctx: UploadContext) => Promise<void>;
11
+ export declare function matchesAccept(file: {
12
+ name: string;
13
+ type: string;
14
+ }, accept?: string): boolean;
15
+ export declare function FileInput({ accept, maxSize, multiple, onFilesChange, upload, label, hint, id, className }: {
16
+ accept?: string;
17
+ maxSize?: number;
18
+ multiple?: boolean;
19
+ onFilesChange?: (files: File[]) => void;
20
+ upload?: UploadFn;
21
+ label?: ReactNode;
22
+ hint?: ReactNode;
23
+ id?: string;
24
+ className?: string;
25
+ }): React.JSX.Element;
@@ -0,0 +1,106 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
+ // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
4
+ // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
5
+ import React from "react";
6
+ import { cx, useAureaStrings } from "./internal.js";
7
+ import { Icon } from "./system.js";
8
+ import { IconButton } from "./actions.js";
9
+ // matchesAccept espelha o algoritmo do atributo accept do HTML: extensão (.json),
10
+ // grupo de tipo (image/*) ou MIME exato (application/json). accept vazio = tudo.
11
+ export function matchesAccept(file, accept) {
12
+ const tokens = (accept ?? "").split(",").map(t => t.trim().toLowerCase()).filter(Boolean);
13
+ if (!tokens.length)
14
+ return true;
15
+ const name = file.name.toLowerCase(), type = file.type.toLowerCase();
16
+ return tokens.some(t => t.startsWith(".") ? name.endsWith(t) : t.endsWith("/*") ? type.startsWith(t.slice(0, -1)) : type === t);
17
+ }
18
+ function formatSize(bytes) {
19
+ if (bytes < 1024)
20
+ return `${bytes} B`;
21
+ const units = ["KB", "MB", "GB"];
22
+ let n = bytes / 1024, i = 0;
23
+ while (n >= 1024 && i < units.length - 1) {
24
+ n /= 1024;
25
+ i++;
26
+ }
27
+ return `${n < 10 ? n.toFixed(1) : Math.round(n)} ${units[i]}`;
28
+ }
29
+ export function FileInput({ accept, maxSize, multiple, onFilesChange, upload, label, hint, id, className }) {
30
+ const s = useAureaStrings();
31
+ const autoId = React.useId();
32
+ const inputId = id ?? autoId;
33
+ const nextId = React.useRef(0);
34
+ const [files, setFiles] = React.useState([]);
35
+ const [rejected, setRejected] = React.useState([]);
36
+ const [announce, setAnnounce] = React.useState("");
37
+ const [dragging, setDragging] = React.useState(false);
38
+ const controllers = React.useRef(new Map());
39
+ // Desmontar aborta tudo em voo — sem isso requisições e closures sobrevivem à
40
+ // tela (auditoria 18/07/2026, MÉDIO 5). Concorrência/agenda é do consumidor: o
41
+ // transporte é dele (UploadFn); o componente dispara um upload() por arquivo aceito.
42
+ React.useEffect(() => { const map = controllers.current; return () => map.forEach(c => c.abort()); }, []);
43
+ const emit = (next) => { setFiles(next); onFilesChange?.(next.map(f => f.file)); };
44
+ const patch = (fid, p) => setFiles(prev => prev.map(e => e.id === fid ? { ...e, ...p } : e));
45
+ const abortId = (fid) => controllers.current.get(fid)?.abort();
46
+ // Cada arquivo envia por conta própria (promessa independente + catch por arquivo):
47
+ // erro ou cancelamento de um não derruba a fila. Cancelar = abort() no controller;
48
+ // no catch, signal.aborted distingue "cancelado" de "falhou" seja qual for o erro
49
+ // que o consumidor lançou. patch é setState funcional → seguro sob closures velhas.
50
+ const startUpload = (fid, file) => {
51
+ if (!upload)
52
+ return;
53
+ const c = new AbortController();
54
+ controllers.current.set(fid, c);
55
+ patch(fid, { status: "uploading", progress: 0 });
56
+ (async () => {
57
+ try {
58
+ await upload(file, { signal: c.signal, onProgress: f => patch(fid, { progress: Math.max(0, Math.min(1, f)) }) });
59
+ patch(fid, { status: "done", progress: 1 });
60
+ setAnnounce(`${s.uploadComplete}: ${file.name}`);
61
+ }
62
+ catch {
63
+ const st = c.signal.aborted ? "canceled" : "error";
64
+ patch(fid, { status: st });
65
+ setAnnounce(`${st === "canceled" ? s.uploadCanceled : s.uploadError}: ${file.name}`);
66
+ }
67
+ finally {
68
+ controllers.current.delete(fid);
69
+ }
70
+ })();
71
+ };
72
+ const add = (list) => {
73
+ if (!list || !list.length)
74
+ return;
75
+ const ok = [], bad = [];
76
+ for (const file of Array.from(list)) {
77
+ const reason = !matchesAccept(file, accept) ? s.fileWrongType : (maxSize != null && file.size > maxSize) ? s.fileTooLarge : null;
78
+ if (reason)
79
+ bad.push({ file, reason });
80
+ else
81
+ ok.push({ id: nextId.current++, file });
82
+ }
83
+ // multiple acumula; single substitui (input nativo já entrega 1 arquivo).
84
+ // só emite se algo passou — drop 100% rejeitado não mexe na lista atual.
85
+ const added = multiple ? ok : ok.slice(-1);
86
+ if (added.length) {
87
+ if (!multiple)
88
+ files.forEach(f => abortId(f.id)); // single troca o arquivo: aborta o envio anterior
89
+ emit(multiple ? [...files, ...added] : added);
90
+ added.forEach(e => startUpload(e.id, e.file));
91
+ }
92
+ setRejected(bad);
93
+ setAnnounce([ok.length && `${s.fileAdded}: ${ok.map(f => f.file.name).join(", ")}`, ...bad.map(b => `${b.reason}: ${b.file.name}`)].filter(Boolean).join(". "));
94
+ };
95
+ const remove = (rid) => { const f = files.find(x => x.id === rid); abortId(rid); emit(files.filter(x => x.id !== rid)); setAnnounce(f ? `${s.fileRemoved}: ${f.file.name}` : ""); };
96
+ return _jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs("label", { className: "dropzone", "data-dragging": dragging || undefined, onDragOver: e => { e.preventDefault(); setDragging(true); }, onDragLeave: e => { if (!e.currentTarget.contains(e.relatedTarget))
97
+ setDragging(false); }, onDrop: e => { e.preventDefault(); setDragging(false); add(e.dataTransfer.files); }, children: [_jsx("input", { id: inputId, type: "file", className: "sr-only", accept: accept, multiple: multiple, onChange: e => { add(e.target.files); e.target.value = ""; } }), _jsx(Icon, { name: "cloud--upload", size: "xl", className: "dropzone-icon" }), _jsx("strong", { children: s.fileDropPrompt }), hint && _jsx("span", { className: "dropzone-hint", children: hint })] }), files.length > 0 && _jsx("ul", { className: "file-list", children: files.map(({ id, file, status, progress = 0 }) => {
98
+ // A barra reusa .progress + .file-progress (largura fixa em classe); só o
99
+ // preenchimento (width %) fica inline, por ser data-driven. Erro reusa
100
+ // .field-error. Como .file-progress não é usada nos docs, adicioná-la ao core
101
+ // não muda pixel de screenshot nenhum (auditoria 18/07, M14: CSS não usado
102
+ // pelos docs não altera baseline — a justificativa antiga estava invertida).
103
+ const pct = Math.round(progress * 100);
104
+ return _jsxs("li", { className: "file-item", children: [_jsx(Icon, { name: "document" }), _jsx("span", { className: "file-name", children: file.name }), status === "uploading" ? _jsxs(_Fragment, { children: [_jsx("div", { className: "progress file-progress", role: "progressbar", "aria-label": `${s.uploadSending} ${file.name}`, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": pct, children: _jsx("span", { style: { width: `${pct}%` } }) }), _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: `${s.uploadCancel} ${file.name}`, onClick: () => abortId(id) })] }) : (status === "error" || status === "canceled") ? _jsxs(_Fragment, { children: [_jsx("span", { className: "field-error", children: status === "error" ? s.uploadError : s.uploadCanceled }), _jsx(IconButton, { variant: "ghost", size: "sm", icon: "restart", label: `${s.uploadRetry} ${file.name}`, onClick: () => startUpload(id, file) }), _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: `${s.fileRemove} ${file.name}`, onClick: () => remove(id) })] }) : _jsxs(_Fragment, { children: [status === "done" && _jsx(Icon, { name: "checkmark--filled" }), _jsx("span", { className: "file-size", children: formatSize(file.size) }), _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: `${s.fileRemove} ${file.name}`, onClick: () => remove(id) })] })] }, id);
105
+ }) }), rejected.map((r, n) => _jsxs("span", { className: "field-error", children: [r.reason, ": ", r.file.name] }, n)), _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: announce })] });
106
+ }
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from "react";
2
+ export declare function Avatar({ src, alt, fallback, size, className }: {
3
+ src?: string;
4
+ alt?: string;
5
+ fallback?: ReactNode;
6
+ size?: number;
7
+ className?: string;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "./internal.js";
3
+ export function Avatar({ src, alt = "", fallback, size = 40, className }) { return _jsx("span", { className: cx("avatar", className), style: { width: size, height: size }, children: src ? _jsx("img", { src: src, alt: alt }) : fallback }); }
@@ -0,0 +1,14 @@
1
+ export { cx, defaultStrings, ptBR, useAureaStrings, defaultSpriteUrl, useSpriteUrl, type AureaStrings } from "./internal.js";
2
+ export * from "./system.js";
3
+ export * from "./actions.js";
4
+ export * from "./feedback.js";
5
+ export * from "./inputs.js";
6
+ export * from "./navigation.js";
7
+ export * from "./layout.js";
8
+ export * from "./data-display.js";
9
+ export * from "./identity.js";
10
+ export * from "./disclosure.js";
11
+ export * from "./overlays.js";
12
+ export * from "./media.js";
13
+ export * from "./communication.js";
14
+ export * from "./code.js";
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ // @aurea-uds/react — a entrada principal.
2
+ //
3
+ // Fase 9 (achado A5). Antes: UM arquivo de 970 linhas, UM ponto de entrada, e oito dependências
4
+ // de runtime — das quais seis serviam o CodeEditor, uma o DataGrid e uma o QRCode. Quem
5
+ // instalava a biblioteca para usar um Button baixava um editor de código inteiro.
6
+ //
7
+ // Agora cada categoria do registry é um módulo, e cada módulo é um subpath. Este arquivo é a
8
+ // porta de sempre: `import {Button} from "@aurea-uds/react"` continua funcionando e traz tudo
9
+ // que não custa dependência.
10
+ //
11
+ // TRÊS COMPONENTES NÃO ENTRAM AQUI, e é o ponto da fase:
12
+ // CodeEditor → "@aurea-uds/react/code-editor" (CodeMirror, 6 peers opcionais)
13
+ // DataGrid → "@aurea-uds/react/data-grid" (@tanstack/react-table)
14
+ // QRCode → "@aurea-uds/react/qrcode" (qr)
15
+ // Se estivessem neste barril, importar um Button carregaria os três — e a dependência opcional
16
+ // que não estivesse instalada quebraria a importação. O peso é real, então a fronteira é real.
17
+ export { cx, defaultStrings, ptBR, useAureaStrings, defaultSpriteUrl, useSpriteUrl } from "./internal.js";
18
+ export * from "./system.js";
19
+ export * from "./actions.js";
20
+ export * from "./feedback.js";
21
+ export * from "./inputs.js";
22
+ export * from "./navigation.js";
23
+ export * from "./layout.js";
24
+ export * from "./data-display.js";
25
+ export * from "./identity.js";
26
+ export * from "./disclosure.js";
27
+ export * from "./overlays.js";
28
+ export * from "./media.js";
29
+ export * from "./communication.js";
30
+ export * from "./code.js";
@@ -0,0 +1,65 @@
1
+ import React, { type HTMLAttributes, type InputHTMLAttributes, type SelectHTMLAttributes, type TextareaHTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
+ import { type IconName } from "./system.js";
3
+ export { FileInput, matchesAccept, type FileRejection, type UploadContext, type UploadFn } from "./file-input.js";
4
+ export interface FieldProps extends HTMLAttributes<HTMLLabelElement>, RefAttributes<HTMLLabelElement> {
5
+ label: string;
6
+ hint?: ReactNode;
7
+ error?: ReactNode;
8
+ htmlFor?: string;
9
+ }
10
+ export declare function Field({ label, hint, error, children, className, id, ...props }: FieldProps): React.JSX.Element;
11
+ export declare const Input: React.ForwardRefExoticComponent<Omit<InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;
12
+ export declare const Select: React.ForwardRefExoticComponent<Omit<SelectHTMLAttributes<HTMLSelectElement> & RefAttributes<HTMLSelectElement>, "ref"> & RefAttributes<HTMLSelectElement>>;
13
+ export declare const Textarea: React.ForwardRefExoticComponent<Omit<TextareaHTMLAttributes<HTMLTextAreaElement> & RefAttributes<HTMLTextAreaElement>, "ref"> & RefAttributes<HTMLTextAreaElement>>;
14
+ export declare function SearchField({ icon, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
15
+ icon?: IconName;
16
+ }): React.JSX.Element;
17
+ export declare function Checkbox({ label, labelHidden, description, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
18
+ label: ReactNode;
19
+ labelHidden?: boolean;
20
+ description?: ReactNode;
21
+ }): React.JSX.Element;
22
+ export declare function Radio({ label, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
23
+ label: ReactNode;
24
+ }): React.JSX.Element;
25
+ export declare function Switch({ label, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
26
+ label: ReactNode;
27
+ }): React.JSX.Element;
28
+ export declare function Range(props: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement>): React.JSX.Element;
29
+ export declare function SegmentedControl({ items, value, onChange, label }: {
30
+ items: Array<{
31
+ value: string;
32
+ label: ReactNode;
33
+ }>;
34
+ value: string;
35
+ onChange: (v: string) => void;
36
+ label?: string;
37
+ }): React.JSX.Element;
38
+ export interface ComboboxOption {
39
+ value: string;
40
+ label: string;
41
+ }
42
+ export declare function Combobox({ items, value, onValueChange, placeholder, label, id, className }: {
43
+ items: ComboboxOption[];
44
+ value?: ComboboxOption | null;
45
+ onValueChange?: (v: ComboboxOption | null) => void;
46
+ placeholder?: string;
47
+ label?: ReactNode;
48
+ id?: string;
49
+ className?: string;
50
+ }): React.JSX.Element;
51
+ export interface ComboboxOptGroup {
52
+ label: string;
53
+ items: ComboboxOption[];
54
+ }
55
+ export declare function MultiCombobox({ items, value, onValueChange, onInputChange, loading, placeholder, label, id, className }: {
56
+ items: ComboboxOption[] | ComboboxOptGroup[];
57
+ value?: ComboboxOption[];
58
+ onValueChange?: (v: ComboboxOption[]) => void;
59
+ onInputChange?: (query: string) => void;
60
+ loading?: boolean;
61
+ placeholder?: string;
62
+ label?: ReactNode;
63
+ id?: string;
64
+ className?: string;
65
+ }): React.JSX.Element;
package/dist/inputs.js ADDED
@@ -0,0 +1,61 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
+ // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
4
+ // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
5
+ import React, { forwardRef } from "react";
6
+ import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
7
+ import { cx, useAureaStrings } from "./internal.js";
8
+ import { Icon } from "./system.js";
9
+ import { IconButton } from "./actions.js";
10
+ // FileInput mora em arquivo próprio (132 linhas — upload real, aborto, progresso) e é público
11
+ // por aqui: a categoria dele é Inputs.
12
+ export { FileInput, matchesAccept } from "./file-input.js";
13
+ // hint e error entram como DESCRIÇÃO (aria-describedby) e não como parte do nome. Medido em
14
+ // 30/07/2026: por estarem dentro do <label>, o nome acessível do campo virava "E-mail Usamos
15
+ // para entrar Endereço inválido" — tudo grudado — e nada dizia ao leitor de tela que o valor
16
+ // era inválido. Nome é o que o campo É; hint e erro são sobre o valor.
17
+ // O controle vem como children, então a ligação é feita clonando-o: é a única forma de pôr
18
+ // aria-describedby/aria-invalid no elemento certo sem obrigar o consumidor a repetir ids.
19
+ export function Field({ label, hint, error, children, className, id, ...props }) {
20
+ const auto = React.useId();
21
+ const base = id ?? auto;
22
+ const idHint = hint ? `${base}-hint` : undefined;
23
+ const idErro = error ? `${base}-error` : undefined;
24
+ const descrito = [idHint, idErro].filter(Boolean).join(" ") || undefined;
25
+ const controle = React.isValidElement(children)
26
+ ? React.cloneElement(children, {
27
+ "aria-describedby": [children.props["aria-describedby"], descrito].filter(Boolean).join(" ") || undefined,
28
+ "aria-invalid": error ? true : children.props["aria-invalid"],
29
+ })
30
+ : children;
31
+ return _jsxs("label", { className: cx("field", className), id: id, ...props, children: [_jsxs("span", { className: "label", children: [label, hint && _jsx("span", { className: "hint", id: idHint, children: hint })] }), controle, error && _jsx("span", { className: "field-error", id: idErro, children: error })] });
32
+ }
33
+ export const Input = forwardRef(function Input({ className, ...props }, ref) { return _jsx("input", { ref: ref, className: cx("input", className), ...props }); });
34
+ export const Select = forwardRef(function Select({ className, ...props }, ref) { return _jsx("select", { ref: ref, className: cx("select", className), ...props }); });
35
+ export const Textarea = forwardRef(function Textarea({ className, ...props }, ref) { return _jsx("textarea", { ref: ref, className: cx("textarea", className), ...props }); });
36
+ export function SearchField({ icon = "search", className, ...props }) { return _jsxs("div", { className: cx("input-wrap", className), children: [_jsx(Icon, { name: icon }), _jsx(Input, { type: "search", ...props })] }); }
37
+ // labelHidden: coluna de seleção de tabela precisa do rótulo POR LINHA para o leitor de
38
+ // tela ("Select Analyst"), mas mostrá-lo engorda a coluna. O rótulo continua no DOM, só
39
+ // sai da tela (.sr-only) — nunca trocar por aria-label solto num <label> visível vazio.
40
+ export function Checkbox({ label, labelHidden, description, className, ...props }) { return _jsxs("label", { className: cx("checkbox", labelHidden && "checkbox-bare", className), children: [_jsx("input", { type: "checkbox", ...props }), _jsx("span", { className: "control-mark" }), _jsxs("span", { className: labelHidden ? "sr-only" : undefined, children: [_jsx("strong", { children: label }), description && _jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "muted", children: description })] })] })] }); }
41
+ export function Radio({ label, className, ...props }) { return _jsxs("label", { className: cx("radio", className), children: [_jsx("input", { type: "radio", ...props }), _jsx("span", { className: "control-mark" }), label] }); }
42
+ export function Switch({ label, className, ...props }) { return _jsxs("label", { className: cx("switch", className), children: [_jsx("input", { type: "checkbox", role: "switch", ...props }), _jsx("span", { className: "switch-track" }), _jsx("span", { children: label })] }); }
43
+ export function Range(props) { return _jsx("input", { className: cx("range", props.className), type: "range", ...props }); }
44
+ export function SegmentedControl({ items, value, onChange, label }) { const s = useAureaStrings(); return _jsx("div", { className: "segmented", role: "group", "aria-label": label ?? s.optionsLabel, children: items.map(i => _jsx("button", { className: i.value === value ? "active" : undefined, "aria-pressed": i.value === value, onClick: () => onChange(i.value), children: i.label }, i.value)) }); }
45
+ export function Combobox({ items, value, onValueChange, placeholder, label, id, className }) {
46
+ const s = useAureaStrings();
47
+ const autoId = React.useId();
48
+ const inputId = id ?? autoId;
49
+ return _jsxs(BaseCombobox.Root, { items: items, value: value, onValueChange: onValueChange, itemToStringLabel: (i) => i.label, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-group", children: [_jsx(BaseCombobox.Input, { id: inputId, placeholder: placeholder, className: "input" }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "chevron--down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { children: _jsx(BaseCombobox.Positioner, { sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: s.comboboxEmpty }), _jsx(BaseCombobox.List, { children: (item) => _jsxs(BaseCombobox.Item, { value: item, className: "menu-item combobox-item", children: [_jsx(BaseCombobox.ItemIndicator, { className: "combobox-check", children: _jsx(Icon, { name: "checkmark", size: "sm" }) }), _jsx("span", { children: item.label })] }, item.value) })] }) }) })] });
50
+ }
51
+ const isGrouped = (items) => items.length > 0 && "items" in items[0];
52
+ export function MultiCombobox({ items, value, onValueChange, onInputChange, loading, placeholder, label, id, className }) {
53
+ const s = useAureaStrings();
54
+ const autoId = React.useId();
55
+ const inputId = id ?? autoId;
56
+ const renderItem = (item) => _jsxs(BaseCombobox.Item, { value: item, className: "menu-item combobox-item", children: [_jsx(BaseCombobox.ItemIndicator, { className: "combobox-check", children: _jsx(Icon, { name: "checkmark", size: "sm" }) }), _jsx("span", { children: item.label })] }, item.value);
57
+ return _jsxs(BaseCombobox.Root, { multiple: true, items: items, value: value, onValueChange: onValueChange, itemToStringLabel: (i) => i.label, filter: onInputChange ? null : undefined, onInputValueChange: onInputChange ? ((v, d) => { if (d.reason !== "item-press")
58
+ onInputChange(v); }) : undefined, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-multi", children: [_jsx(BaseCombobox.Chips, { className: "combobox-chips", children: _jsx(BaseCombobox.Value, { children: (selected) => _jsxs(_Fragment, { children: [selected.map((item) => _jsxs(BaseCombobox.Chip, { className: "combobox-chip", children: [item.label, _jsx(BaseCombobox.ChipRemove, { className: "combobox-chip-remove", "aria-label": `${s.comboboxRemove} ${item.label}`, children: _jsx(Icon, { name: "close", size: "sm" }) })] }, item.value)), _jsx(BaseCombobox.Input, { id: inputId, placeholder: selected.length ? undefined : placeholder, className: "combobox-chip-input" })] }) }) }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "chevron--down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { children: _jsx(BaseCombobox.Positioner, { sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: loading ? s.comboboxLoading : s.comboboxEmpty }), _jsx(BaseCombobox.List, { children: isGrouped(items)
59
+ ? (group) => _jsxs(BaseCombobox.Group, { items: group.items, className: "combobox-section", children: [_jsx(BaseCombobox.GroupLabel, { className: "combobox-group-label", children: group.label }), _jsx(BaseCombobox.Collection, { children: renderItem })] }, group.label)
60
+ : renderItem })] }) }) })] });
61
+ }
@@ -0,0 +1,72 @@
1
+ import { type HTMLAttributes, type RefAttributes } from "react";
2
+ export declare const cx: (...v: Array<string | false | null | undefined>) => string;
3
+ export interface AureaStrings {
4
+ close: string;
5
+ paginationLabel: string;
6
+ previous: string;
7
+ next: string;
8
+ breadcrumbLabel: string;
9
+ tabsLabel: string;
10
+ optionsLabel: string;
11
+ tableLabel: string;
12
+ commandLabel: string;
13
+ commandPlaceholder: string;
14
+ dismissNotification: string;
15
+ toolbarLabel: string;
16
+ comboboxEmpty: string;
17
+ comboboxClear: string;
18
+ comboboxOpen: string;
19
+ comboboxRemove: string;
20
+ comboboxLoading: string;
21
+ fileDropPrompt: string;
22
+ fileAdded: string;
23
+ fileRemoved: string;
24
+ fileRemove: string;
25
+ fileTooLarge: string;
26
+ fileWrongType: string;
27
+ treeLabel: string;
28
+ notificationsLabel: string;
29
+ notificationMarkAll: string;
30
+ notificationEmpty: string;
31
+ notificationUnread: string;
32
+ notificationNew: string;
33
+ dataGridFilter: string;
34
+ dataGridEmpty: string;
35
+ dataGridSelectAll: string;
36
+ dataGridSelectRow: string;
37
+ mediaPlayer: string;
38
+ mediaPlay: string;
39
+ mediaPause: string;
40
+ mediaMute: string;
41
+ mediaUnmute: string;
42
+ mediaSeek: string;
43
+ mediaVolume: string;
44
+ mediaCaptionsShow: string;
45
+ mediaCaptionsHide: string;
46
+ mediaSkipBack: string;
47
+ mediaSkipForward: string;
48
+ mediaFullscreenEnter: string;
49
+ mediaFullscreenExit: string;
50
+ uploadSending: string;
51
+ uploadCancel: string;
52
+ uploadRetry: string;
53
+ uploadError: string;
54
+ uploadCanceled: string;
55
+ uploadComplete: string;
56
+ codeEditor: string;
57
+ chatLabel: string;
58
+ chatMessage: string;
59
+ chatSend: string;
60
+ qrCode: string;
61
+ copyCode: string;
62
+ tocLabel: string;
63
+ navigationToggle: string;
64
+ }
65
+ export declare const defaultStrings: AureaStrings;
66
+ export declare const ptBR: AureaStrings;
67
+ export declare const StringsContext: import("react").Context<AureaStrings>;
68
+ export declare const useAureaStrings: () => AureaStrings;
69
+ export declare const defaultSpriteUrl = "/aurea-icons.svg";
70
+ export declare const SpriteContext: import("react").Context<string>;
71
+ export declare const useSpriteUrl: () => string;
72
+ export declare function Kbd({ children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement>): import("react").JSX.Element;