@aurea-uds/react 0.2.0 → 0.4.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 +22 -3
- package/dist/actions.js +62 -6
- package/dist/code-client.d.ts +6 -0
- package/dist/code-client.js +24 -0
- package/dist/code.d.ts +2 -13
- package/dist/code.js +5 -34
- 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 -14
- package/dist/feedback-client.d.ts +64 -0
- package/dist/feedback-client.js +110 -0
- package/dist/feedback.d.ts +2 -63
- package/dist/feedback.js +5 -101
- package/dist/file-input.js +57 -9
- package/dist/identity-client.d.ts +9 -0
- package/dist/identity-client.js +12 -0
- package/dist/identity.d.ts +2 -17
- package/dist/identity.js +5 -25
- package/dist/index.d.ts +2 -1
- package/dist/index.js +19 -3
- package/dist/inputs-client.d.ts +105 -0
- package/dist/inputs-client.js +264 -0
- package/dist/inputs.d.ts +2 -91
- package/dist/inputs.js +9 -126
- package/dist/internal.d.ts +20 -5
- package/dist/internal.js +91 -10
- package/dist/layout-client.d.ts +10 -0
- package/dist/layout-client.js +77 -0
- package/dist/layout.d.ts +2 -15
- package/dist/layout.js +5 -76
- package/dist/markup.d.ts +103 -0
- package/dist/markup.js +124 -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 -99
- package/dist/navigation-client.d.ts +120 -0
- package/dist/navigation-client.js +196 -0
- package/dist/navigation.d.ts +2 -85
- package/dist/navigation.js +5 -138
- package/dist/overlays.d.ts +23 -0
- package/dist/overlays.js +46 -2
- package/dist/pure.d.ts +31 -0
- package/dist/pure.js +26 -2
- package/dist/system.d.ts +9 -0
- package/dist/system.js +46 -0
- package/package.json +180 -176
|
@@ -0,0 +1,196 @@
|
|
|
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 { Tabs as BaseTabs } from "@base-ui/react/tabs";
|
|
8
|
+
import { Autocomplete as BaseAutocomplete } from "@base-ui/react/autocomplete";
|
|
9
|
+
import { cx, useAureaStrings, usePortalContainer } from "./internal.js";
|
|
10
|
+
import { Kbd } from "./markup.js";
|
|
11
|
+
import { Icon } from "./system.js";
|
|
12
|
+
import { Button } from "./actions.js";
|
|
13
|
+
import { Badge } from "./feedback.js";
|
|
14
|
+
import { SearchField } from "./inputs.js";
|
|
15
|
+
// `overlays` PASSOU A VIR ANTES de `navigation` no DAG (18/08/2026, MAP.md §DAG). Ele só
|
|
16
|
+
// importa `internal`, `system` e `actions` — todos anteriores —, então não há ciclo: o que
|
|
17
|
+
// mudou foi a ORDEM documentada, que colocava overlays no "resto" sem motivo medido.
|
|
18
|
+
import { Tooltip } from "./overlays.js";
|
|
19
|
+
export function Stepper({ items, label, className }) {
|
|
20
|
+
const s = useAureaStrings();
|
|
21
|
+
return _jsx("div", { role: "list", "aria-label": label ?? s.stepperLabel, className: cx("stepper", className), children: items.map((it, n) => {
|
|
22
|
+
const st = it.state ?? "default";
|
|
23
|
+
const marca = st === "done" ? _jsx(Icon, { name: "checkmark" }) : st === "error" ? _jsx(Icon, { name: "error" }) : n + 1;
|
|
24
|
+
const miolo = _jsxs(_Fragment, { children: [_jsx("span", { className: "step-dot", children: marca }), _jsx("strong", { children: it.label }), it.optional && _jsx("small", { className: "step-optional", children: it.optional })] });
|
|
25
|
+
return _jsx("div", { role: "listitem", className: cx("step", st !== "default" && `step-${st}`), "aria-current": st === "active" ? "step" : undefined, children: it.onClick ? _jsx("button", { type: "button", className: "step-trigger", onClick: it.onClick, children: miolo }) : miolo }, n);
|
|
26
|
+
}) });
|
|
27
|
+
}
|
|
28
|
+
export function Breadcrumb({ items, label }) { const s = useAureaStrings(); return _jsx("nav", { className: "breadcrumb", "aria-label": label ?? s.breadcrumbLabel, children: items.map((i, n) => _jsxs(React.Fragment, { children: [n > 0 && _jsx(Icon, { name: "chevron--right", size: "sm" }), " ", i.href ? _jsx("a", { href: i.href, children: i.label }) : _jsx("strong", { "aria-current": "page", children: i.label })] }, n)) }); }
|
|
29
|
+
export function Tabs({ tabs, value, onChange, label }) { const s = useAureaStrings(); return _jsxs(BaseTabs.Root, { value: value, onValueChange: v => onChange(String(v)), children: [_jsx(BaseTabs.List, { className: "tabs", "aria-label": label ?? s.tabsLabel, activateOnFocus: true, children: tabs.map(t => _jsx(BaseTabs.Tab, { value: t.id, className: "tab", children: t.label }, t.id)) }), tabs.map(t => _jsx(BaseTabs.Panel, { value: t.id, className: "card card-inset", tabIndex: 0, children: t.content }, t.id))] }); }
|
|
30
|
+
export function Pagination({ page, total, onPageChange }) { const s = useAureaStrings(); return _jsxs("nav", { className: "pagination", "aria-label": s.paginationLabel, children: [_jsx(Button, { variant: "ghost", size: "sm", disabled: page <= 1, onClick: () => onPageChange(page - 1), children: s.previous }), _jsxs(Badge, { variant: "primary", children: [page, " / ", total] }), _jsx(Button, { variant: "ghost", size: "sm", disabled: page >= total, onClick: () => onPageChange(page + 1), children: s.next })] }); }
|
|
31
|
+
export function TableOfContents({ items, current, label, className, ...props }) {
|
|
32
|
+
const s = useAureaStrings();
|
|
33
|
+
return _jsxs("nav", { className: cx("toc", className), "aria-label": label ?? s.tocLabel, ...props, children: [_jsx("p", { className: "toc-label", children: label ?? s.tocLabel }), items.map(i => _jsx("a", { href: `#${i.id}`, className: cx(i.sub && "toc-sub"), ...(i.id === current ? { "aria-current": "true" } : {}), children: i.label }, i.id))] });
|
|
34
|
+
}
|
|
35
|
+
function flattenVisible(nodes, expanded, level = 1, parentId, acc = []) {
|
|
36
|
+
for (const node of nodes) {
|
|
37
|
+
acc.push({ node, level, parentId });
|
|
38
|
+
if (node.children?.length && expanded.has(node.id))
|
|
39
|
+
flattenVisible(node.children, expanded, level + 1, node.id, acc);
|
|
40
|
+
}
|
|
41
|
+
return acc;
|
|
42
|
+
}
|
|
43
|
+
export function TreeView({ items, defaultExpandedIds, onSelect, label, className }) {
|
|
44
|
+
const s = useAureaStrings();
|
|
45
|
+
const baseId = React.useId();
|
|
46
|
+
const [expanded, setExpanded] = React.useState(() => new Set(defaultExpandedIds));
|
|
47
|
+
const [selected, setSelected] = React.useState();
|
|
48
|
+
const [active, setActive] = React.useState(() => items[0]?.id);
|
|
49
|
+
const rootRef = React.useRef(null);
|
|
50
|
+
const visible = flattenVisible(items, expanded);
|
|
51
|
+
// Roving tab stop derivado: se o nó ativo saiu do conjunto visível (dados
|
|
52
|
+
// trocados, nó removido), o primeiro visível volta a ser tabulável — senão a
|
|
53
|
+
// árvore inteira fica tabIndex=-1 e some da ordem do Tab (auditoria, MÉDIO 1).
|
|
54
|
+
const effectiveActive = active !== undefined && visible.some(v => v.node.id === active) ? active : visible[0]?.node.id;
|
|
55
|
+
const focusId = (id) => { setActive(id); rootRef.current?.querySelector(`[data-tree-id="${CSS.escape(id)}"]`)?.focus(); };
|
|
56
|
+
const toggle = (id, open) => setExpanded(prev => { const n = new Set(prev); if (open)
|
|
57
|
+
n.add(id);
|
|
58
|
+
else
|
|
59
|
+
n.delete(id); return n; });
|
|
60
|
+
const select = (node) => { setSelected(node.id); onSelect?.(node); };
|
|
61
|
+
const onKeyDown = (e) => {
|
|
62
|
+
const idx = visible.findIndex(v => v.node.id === effectiveActive);
|
|
63
|
+
if (idx < 0)
|
|
64
|
+
return;
|
|
65
|
+
const cur = visible[idx], hasChildren = !!cur.node.children?.length, isOpen = expanded.has(cur.node.id);
|
|
66
|
+
const rtl = getComputedStyle(e.currentTarget).direction === "rtl";
|
|
67
|
+
const expandKey = rtl ? "ArrowLeft" : "ArrowRight", collapseKey = rtl ? "ArrowRight" : "ArrowLeft";
|
|
68
|
+
switch (e.key) {
|
|
69
|
+
case "ArrowDown":
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
if (idx < visible.length - 1)
|
|
72
|
+
focusId(visible[idx + 1].node.id);
|
|
73
|
+
break;
|
|
74
|
+
case "ArrowUp":
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
if (idx > 0)
|
|
77
|
+
focusId(visible[idx - 1].node.id);
|
|
78
|
+
break;
|
|
79
|
+
case expandKey:
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
if (hasChildren && !isOpen)
|
|
82
|
+
toggle(cur.node.id, true);
|
|
83
|
+
else if (hasChildren && isOpen)
|
|
84
|
+
focusId(cur.node.children[0].id);
|
|
85
|
+
break;
|
|
86
|
+
case collapseKey:
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
if (hasChildren && isOpen)
|
|
89
|
+
toggle(cur.node.id, false);
|
|
90
|
+
else if (cur.parentId)
|
|
91
|
+
focusId(cur.parentId);
|
|
92
|
+
break;
|
|
93
|
+
case "Home":
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
focusId(visible[0].node.id);
|
|
96
|
+
break;
|
|
97
|
+
case "End":
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
focusId(visible[visible.length - 1].node.id);
|
|
100
|
+
break;
|
|
101
|
+
case "Enter":
|
|
102
|
+
case " ":
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
select(cur.node);
|
|
105
|
+
if (hasChildren)
|
|
106
|
+
toggle(cur.node.id, !isOpen);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const renderNodes = (nodes, level) => (_jsx("ul", { ref: level === 1 ? rootRef : undefined, className: cx(level === 1 ? "tree" : "tree-group", level === 1 && className), role: level === 1 ? "tree" : "group", "aria-label": level === 1 ? (label ?? s.treeLabel) : undefined, onKeyDown: level === 1 ? onKeyDown : undefined, children: nodes.map(node => {
|
|
111
|
+
const hasChildren = !!node.children?.length, isOpen = expanded.has(node.id), isSelected = selected === node.id, labelId = baseId + node.id;
|
|
112
|
+
return _jsxs("li", { className: "tree-item", role: "treeitem", "data-tree-id": node.id, "aria-level": level, "aria-expanded": hasChildren ? isOpen : undefined, "aria-selected": isSelected, "aria-labelledby": labelId, tabIndex: node.id === effectiveActive ? 0 : -1, children: [_jsxs("span", { className: "tree-node", "data-selected": isSelected || undefined, style: { paddingInlineStart: `calc(var(--space-3) + ${level - 1} * var(--space-4))` }, onClick: () => { focusId(node.id); select(node); if (hasChildren)
|
|
113
|
+
toggle(node.id, !isOpen); }, children: [hasChildren ? _jsx(Icon, { name: "chevron--right", size: "sm", className: "tree-twist" }) : _jsx("span", { className: "tree-indent", "aria-hidden": "true" }), node.icon && _jsx(Icon, { name: node.icon, size: "sm" }), _jsx("span", { id: labelId, className: "tree-label", children: node.label })] }), hasChildren && isOpen && renderNodes(node.children, level + 1)] }, node.id);
|
|
114
|
+
}) }));
|
|
115
|
+
return renderNodes(items, 1);
|
|
116
|
+
}
|
|
117
|
+
function sidebarList(items, ctx, sub, labelledBy) {
|
|
118
|
+
return _jsx("ul", { className: cx("sidebar-list", sub && "sidebar-sub"), "aria-labelledby": labelledBy, children: items.map(it => {
|
|
119
|
+
const lid = ctx.baseId + it.id;
|
|
120
|
+
const filhos = it.items?.length ? it.items : undefined;
|
|
121
|
+
// Rótulo escondido vira `.sr-only` em vez de sumir do DOM: na lateral recolhida o item
|
|
122
|
+
// continua tendo nome para quem usa leitor de tela. Ícone sozinho não nomeia nada.
|
|
123
|
+
const oculto = (no) => ctx.collapsed ? _jsx("span", { className: "sr-only", children: no }) : no;
|
|
124
|
+
if (filhos && !it.href && !it.onClick)
|
|
125
|
+
return _jsxs("li", { children: [_jsx("p", { id: lid, className: cx("sidebar-group-label", ctx.collapsed && "sr-only"), children: it.label }), sidebarList(filhos, ctx, false, lid)] }, it.id);
|
|
126
|
+
const ativo = it.id === ctx.current;
|
|
127
|
+
const miolo = _jsxs(_Fragment, { children: [it.icon && _jsx(Icon, { name: it.icon }), _jsx("span", { className: cx("sidebar-label", ctx.collapsed && "sr-only"), children: it.label }), it.badge != null && oculto(it.badge)] });
|
|
128
|
+
const alvo = it.href
|
|
129
|
+
? _jsx("a", { id: lid, href: it.href, className: "sidebar-item", "aria-current": ativo ? "page" : undefined, onClick: it.onClick, children: miolo })
|
|
130
|
+
: _jsx("button", { id: lid, type: "button", className: "sidebar-item", "aria-current": ativo ? "page" : undefined, onClick: it.onClick, children: miolo });
|
|
131
|
+
// NO TRILHO O NOME SÓ EXISTE NO TOOLTIP. Recolhida, a lateral manda o rótulo para `.sr-only`: quem
|
|
132
|
+
// usa leitor de tela continua ouvindo, e quem ENXERGA fica com um ícone mudo. A referência resolve
|
|
133
|
+
// isso com tooltip no `NavButton`, e é o que falta para um trilho de ícone não virar adivinhação.
|
|
134
|
+
// Só quando recolhida: com o rótulo visível ao lado, o tooltip repetiria o que já está na tela.
|
|
135
|
+
// `side="right"` porque a lateral encosta na borda esquerda — para cima o balão sairia do trilho.
|
|
136
|
+
return _jsxs("li", { children: [ctx.collapsed ? _jsx(Tooltip, { content: it.label, side: "right", children: alvo }) : alvo, filhos && sidebarList(filhos, ctx, true, lid)] }, it.id);
|
|
137
|
+
}) });
|
|
138
|
+
}
|
|
139
|
+
export function Sidebar({ items, current, collapsed, variant = "floating", label, children, className, ...props }) {
|
|
140
|
+
const s = useAureaStrings();
|
|
141
|
+
const baseId = React.useId();
|
|
142
|
+
return _jsxs("aside", { className: cx("sidebar", variant !== "floating" && `sidebar-${variant}`, collapsed && "sidebar-collapsed", className), ...props, children: [items && items.length > 0 && _jsx("nav", { className: "sidebar-nav", "aria-label": label ?? s.sidebarLabel, children: sidebarList(items, { baseId, current, collapsed }) }), children] });
|
|
143
|
+
}
|
|
144
|
+
const LEGADO = {
|
|
145
|
+
flat: ["edge", "none"], surface: ["floating", "circle"], pill: ["floating", "pill"], dock: ["floating", "circle"]
|
|
146
|
+
};
|
|
147
|
+
export function BottomNav({ items, current, variant = "floating", indicator = "none", label, className, ...props }) {
|
|
148
|
+
const s = useAureaStrings();
|
|
149
|
+
const [layout, marca0] = variant in LEGADO ? LEGADO[variant] : [variant, indicator];
|
|
150
|
+
// O indicador explícito ganha do que o nome legado implica: quem escreveu os dois quis os dois.
|
|
151
|
+
const ind = indicator !== "none" ? indicator : marca0;
|
|
152
|
+
return _jsx("nav", { className: cx("bottom-nav", layout === "edge" && "bottom-nav-edge", `bottom-nav-ind-${ind}`, className), "aria-label": label ?? s.bottomNavLabel, ...props, children: items.map(it => {
|
|
153
|
+
const ativo = it.id === current;
|
|
154
|
+
// O CONTADOR PENDURA NO ÍCONE, e é por isso que existe esta caixa. Pendurado no ITEM (a
|
|
155
|
+
// primeira versão), `50%` cai no meio do RÓTULO quando o item é linha — o Victor viu o número
|
|
156
|
+
// cobrir o nome inteiro em 17/08/2026. A pesquisa (Material 3 e os guias de barra de abas do
|
|
157
|
+
// iOS) diz a mesma coisa: canto superior do ÍCONE, encostando na borda dele, nunca sobre o
|
|
158
|
+
// texto. A caixa é o que dá ao contador um canto para se ancorar — e nos quatro indicadores
|
|
159
|
+
// redondos é ela que VIRA o círculo, com o rótulo embaixo, fora dele.
|
|
160
|
+
// `size="lg"` porque a proporção contra o contador foi medida: 24 para 16, razão 0,67.
|
|
161
|
+
const marca = _jsxs("span", { className: "bottom-nav-mark", children: [it.icon && _jsx(Icon, { name: it.icon, size: "lg" }), it.badge != null && _jsx("span", { className: "bottom-nav-badge", children: it.badge })] });
|
|
162
|
+
const miolo = _jsxs(_Fragment, { children: [marca, _jsx("span", { className: "bottom-nav-label", children: it.label })] });
|
|
163
|
+
return it.href
|
|
164
|
+
? _jsx("a", { href: it.href, className: "bottom-nav-item", "aria-current": ativo ? "page" : undefined, onClick: it.onClick, children: miolo }, it.id)
|
|
165
|
+
: _jsx("button", { type: "button", className: "bottom-nav-item", "aria-current": ativo ? "page" : undefined, onClick: it.onClick, children: miolo }, it.id);
|
|
166
|
+
}) });
|
|
167
|
+
}
|
|
168
|
+
export function NavList({ items, className, ...props }) {
|
|
169
|
+
return _jsx("ul", { className: cx("nav-list", className), ...props, children: items.map(it => {
|
|
170
|
+
const miolo = _jsxs(_Fragment, { children: [it.icon && _jsx(Icon, { name: it.icon }), _jsxs("span", { className: "nav-list-text", children: [_jsx("span", { className: "nav-list-label", children: it.label }), it.description != null && _jsx("span", { className: "nav-list-description", children: it.description })] }), it.value != null && _jsx("span", { className: "nav-list-value", children: it.value }), it.href && _jsx(Icon, { name: "chevron--right", size: "sm", className: "nav-list-chevron" })] });
|
|
171
|
+
return _jsx("li", { children: it.href && !it.disabled
|
|
172
|
+
? _jsx("a", { href: it.href, className: "nav-list-row", onClick: it.onClick, children: miolo })
|
|
173
|
+
: _jsx("button", { type: "button", className: "nav-list-row", "aria-disabled": it.disabled || undefined, onClick: it.disabled ? undefined : it.onClick, children: miolo }) }, it.id);
|
|
174
|
+
}) });
|
|
175
|
+
}
|
|
176
|
+
export function CommandPalette({ open, onClose, items, placeholder, label }) {
|
|
177
|
+
const s = useAureaStrings();
|
|
178
|
+
const portal = usePortalContainer();
|
|
179
|
+
if (!open)
|
|
180
|
+
return null;
|
|
181
|
+
// SEM AGRUPAMENTO na v1, e a medição é que decidiu (13/08/2026). O `Autocomplete.Root` não
|
|
182
|
+
// consome a estrutura agrupada como o `Combobox.Root` consome — e o caminho alternativo, dar a
|
|
183
|
+
// cada `Group` a sua fatia de itens, PASSA POR CIMA do filtro do motor: digitar "the" devolvia
|
|
184
|
+
// os três comandos. Entre agrupar e filtrar, filtrar é o ponto de uma paleta de comandos.
|
|
185
|
+
// Fica registrado como limite, não como esquecimento: quando alguém precisar de grupo aqui, o
|
|
186
|
+
// caminho é o `Combobox.Root`, e isso é troca de motor, não ajuste.
|
|
187
|
+
const executa = (item) => { onClose(); item.run(); };
|
|
188
|
+
const linha = (item) => _jsxs(BaseAutocomplete.Item, { value: item, className: "menu-item command-item", onClick: () => executa(item), children: [item.icon && _jsx(Icon, { name: item.icon }), _jsx("span", { className: "command-item-label", children: item.label }), item.kbd && _jsx(Kbd, { children: item.kbd })] }, item.id);
|
|
189
|
+
// Escape fecha, e é o teclado que a pessoa tenta primeiro. O motor não fecha sozinho porque
|
|
190
|
+
// quem é dono do `open` é o consumidor — mesma regra do Dialog e do Drawer daqui.
|
|
191
|
+
return _jsx("div", { className: "command-overlay", onKeyDown: e => { if (e.key === "Escape")
|
|
192
|
+
onClose(); }, children: _jsx("div", { className: "command-palette", role: "dialog", "aria-label": label ?? s.commandLabel, children: _jsxs(BaseAutocomplete.Root, { items: items, itemToStringValue: (i) => i.label, mode: "list", open: true, children: [_jsx(BaseAutocomplete.Input, { autoFocus: true, className: "input", onKeyDown: e => { if (e.key === "Escape")
|
|
193
|
+
onClose(); }, "aria-label": placeholder ?? s.commandPlaceholder, placeholder: placeholder ?? s.commandPlaceholder }), _jsx(BaseAutocomplete.Portal, { container: portal, children: _jsx(BaseAutocomplete.Positioner, { sideOffset: 6, className: "command-positioner", children: _jsxs(BaseAutocomplete.Popup, { className: "menu command-list", children: [_jsx(BaseAutocomplete.Empty, { className: "combobox-empty", children: s.comboboxEmpty }), _jsx(BaseAutocomplete.List, { children: _jsx(BaseAutocomplete.Collection, { children: linha }) })] }) }) })] }) }) });
|
|
194
|
+
}
|
|
195
|
+
export function CommandPaletteShell({ open, query, onQueryChange, children }) { const s = useAureaStrings(); if (!open)
|
|
196
|
+
return null; return _jsx("div", { className: "command-overlay", children: _jsxs("div", { className: "command-palette", role: "dialog", "aria-label": s.commandLabel, children: [_jsx(SearchField, { autoFocus: true, "aria-label": s.commandPlaceholder, value: query, onChange: e => onQueryChange(e.target.value), placeholder: s.commandPlaceholder }), children] }) }); }
|
package/dist/navigation.d.ts
CHANGED
|
@@ -1,85 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export type StepState = "default" | "active" | "done" | "error";
|
|
4
|
-
export interface StepItem {
|
|
5
|
-
label: ReactNode;
|
|
6
|
-
state?: StepState;
|
|
7
|
-
optional?: ReactNode;
|
|
8
|
-
onClick?: () => void;
|
|
9
|
-
}
|
|
10
|
-
export declare function Stepper({ items, label, className }: {
|
|
11
|
-
items: StepItem[];
|
|
12
|
-
label?: string;
|
|
13
|
-
className?: string;
|
|
14
|
-
}): React.JSX.Element;
|
|
15
|
-
export declare function Breadcrumb({ items, label }: {
|
|
16
|
-
items: Array<{
|
|
17
|
-
label: ReactNode;
|
|
18
|
-
href?: string;
|
|
19
|
-
}>;
|
|
20
|
-
label?: string;
|
|
21
|
-
}): React.JSX.Element;
|
|
22
|
-
export declare function Tabs({ tabs, value, onChange, label }: {
|
|
23
|
-
tabs: Array<{
|
|
24
|
-
id: string;
|
|
25
|
-
label: ReactNode;
|
|
26
|
-
content: ReactNode;
|
|
27
|
-
}>;
|
|
28
|
-
value: string;
|
|
29
|
-
onChange: (id: string) => void;
|
|
30
|
-
label?: string;
|
|
31
|
-
}): React.JSX.Element;
|
|
32
|
-
export declare function Pagination({ page, total, onPageChange }: {
|
|
33
|
-
page: number;
|
|
34
|
-
total: number;
|
|
35
|
-
onPageChange: (p: number) => void;
|
|
36
|
-
}): React.JSX.Element;
|
|
37
|
-
export interface TocItem {
|
|
38
|
-
id: string;
|
|
39
|
-
label: string;
|
|
40
|
-
sub?: boolean;
|
|
41
|
-
}
|
|
42
|
-
export declare function TableOfContents({ items, current, label, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement> & {
|
|
43
|
-
items: TocItem[];
|
|
44
|
-
current?: string;
|
|
45
|
-
label?: string;
|
|
46
|
-
}): React.JSX.Element;
|
|
47
|
-
export interface TreeNode {
|
|
48
|
-
id: string;
|
|
49
|
-
label: ReactNode;
|
|
50
|
-
icon?: IconName;
|
|
51
|
-
children?: TreeNode[];
|
|
52
|
-
}
|
|
53
|
-
export declare function TreeView({ items, defaultExpandedIds, onSelect, label, className }: {
|
|
54
|
-
items: TreeNode[];
|
|
55
|
-
defaultExpandedIds?: string[];
|
|
56
|
-
onSelect?: (node: TreeNode) => void;
|
|
57
|
-
label?: string;
|
|
58
|
-
className?: string;
|
|
59
|
-
}): ReactElement<unknown, string | React.JSXElementConstructor<any>>;
|
|
60
|
-
export interface SidebarItem {
|
|
61
|
-
id: string;
|
|
62
|
-
label: ReactNode;
|
|
63
|
-
href?: string;
|
|
64
|
-
icon?: IconName;
|
|
65
|
-
badge?: ReactNode;
|
|
66
|
-
onClick?: () => void;
|
|
67
|
-
items?: SidebarItem[];
|
|
68
|
-
}
|
|
69
|
-
export declare function Sidebar({ items, current, collapsed, label, children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement> & {
|
|
70
|
-
items?: SidebarItem[];
|
|
71
|
-
current?: string;
|
|
72
|
-
collapsed?: boolean;
|
|
73
|
-
label?: string;
|
|
74
|
-
}): React.JSX.Element;
|
|
75
|
-
export type TopbarVariant = "floating" | "flush" | "pill";
|
|
76
|
-
export declare function Topbar({ variant, brand, children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement> & {
|
|
77
|
-
variant?: TopbarVariant;
|
|
78
|
-
brand?: ReactNode;
|
|
79
|
-
}): React.JSX.Element;
|
|
80
|
-
export declare function CommandPaletteShell({ open, query, onQueryChange, children }: {
|
|
81
|
-
open: boolean;
|
|
82
|
-
query: string;
|
|
83
|
-
onQueryChange: (v: string) => void;
|
|
84
|
-
children?: ReactNode;
|
|
85
|
-
}): React.JSX.Element | null;
|
|
1
|
+
export * from "./navigation-client.js";
|
|
2
|
+
export { Topbar, type TopbarVariant } from "./markup.js";
|
package/dist/navigation.js
CHANGED
|
@@ -1,138 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { Tabs as BaseTabs } from "@base-ui/react/tabs";
|
|
8
|
-
import { cx, useAureaStrings } from "./internal.js";
|
|
9
|
-
import { Icon } from "./system.js";
|
|
10
|
-
import { Button } from "./actions.js";
|
|
11
|
-
import { Badge } from "./feedback.js";
|
|
12
|
-
import { SearchField } from "./inputs.js";
|
|
13
|
-
export function Stepper({ items, label, className }) {
|
|
14
|
-
const s = useAureaStrings();
|
|
15
|
-
return _jsx("div", { role: "list", "aria-label": label ?? s.stepperLabel, className: cx("stepper", className), children: items.map((it, n) => {
|
|
16
|
-
const st = it.state ?? "default";
|
|
17
|
-
const marca = st === "done" ? _jsx(Icon, { name: "checkmark" }) : st === "error" ? _jsx(Icon, { name: "error" }) : n + 1;
|
|
18
|
-
const miolo = _jsxs(_Fragment, { children: [_jsx("span", { className: "step-dot", children: marca }), _jsx("strong", { children: it.label }), it.optional && _jsx("small", { className: "step-optional", children: it.optional })] });
|
|
19
|
-
return _jsx("div", { role: "listitem", className: cx("step", st !== "default" && `step-${st}`), "aria-current": st === "active" ? "step" : undefined, children: it.onClick ? _jsx("button", { type: "button", className: "step-trigger", onClick: it.onClick, children: miolo }) : miolo }, n);
|
|
20
|
-
}) });
|
|
21
|
-
}
|
|
22
|
-
export function Breadcrumb({ items, label }) { const s = useAureaStrings(); return _jsx("nav", { className: "breadcrumb", "aria-label": label ?? s.breadcrumbLabel, children: items.map((i, n) => _jsxs(React.Fragment, { children: [n > 0 && _jsx(Icon, { name: "chevron--right", size: "sm" }), " ", i.href ? _jsx("a", { href: i.href, children: i.label }) : _jsx("strong", { "aria-current": "page", children: i.label })] }, n)) }); }
|
|
23
|
-
export function Tabs({ tabs, value, onChange, label }) { const s = useAureaStrings(); return _jsxs(BaseTabs.Root, { value: value, onValueChange: v => onChange(String(v)), children: [_jsx(BaseTabs.List, { className: "tabs", "aria-label": label ?? s.tabsLabel, activateOnFocus: true, children: tabs.map(t => _jsx(BaseTabs.Tab, { value: t.id, className: "tab", children: t.label }, t.id)) }), tabs.map(t => _jsx(BaseTabs.Panel, { value: t.id, className: "card card-inset", tabIndex: 0, children: t.content }, t.id))] }); }
|
|
24
|
-
export function Pagination({ page, total, onPageChange }) { const s = useAureaStrings(); return _jsxs("nav", { className: "pagination", "aria-label": s.paginationLabel, children: [_jsx(Button, { variant: "ghost", size: "sm", disabled: page <= 1, onClick: () => onPageChange(page - 1), children: s.previous }), _jsxs(Badge, { variant: "primary", children: [page, " / ", total] }), _jsx(Button, { variant: "ghost", size: "sm", disabled: page >= total, onClick: () => onPageChange(page + 1), children: s.next })] }); }
|
|
25
|
-
export function TableOfContents({ items, current, label, className, ...props }) {
|
|
26
|
-
const s = useAureaStrings();
|
|
27
|
-
return _jsxs("nav", { className: cx("toc", className), "aria-label": label ?? s.tocLabel, ...props, children: [_jsx("p", { className: "toc-label", children: label ?? s.tocLabel }), items.map(i => _jsx("a", { href: `#${i.id}`, className: cx(i.sub && "toc-sub"), ...(i.id === current ? { "aria-current": "true" } : {}), children: i.label }, i.id))] });
|
|
28
|
-
}
|
|
29
|
-
function flattenVisible(nodes, expanded, level = 1, parentId, acc = []) {
|
|
30
|
-
for (const node of nodes) {
|
|
31
|
-
acc.push({ node, level, parentId });
|
|
32
|
-
if (node.children?.length && expanded.has(node.id))
|
|
33
|
-
flattenVisible(node.children, expanded, level + 1, node.id, acc);
|
|
34
|
-
}
|
|
35
|
-
return acc;
|
|
36
|
-
}
|
|
37
|
-
export function TreeView({ items, defaultExpandedIds, onSelect, label, className }) {
|
|
38
|
-
const s = useAureaStrings();
|
|
39
|
-
const baseId = React.useId();
|
|
40
|
-
const [expanded, setExpanded] = React.useState(() => new Set(defaultExpandedIds));
|
|
41
|
-
const [selected, setSelected] = React.useState();
|
|
42
|
-
const [active, setActive] = React.useState(() => items[0]?.id);
|
|
43
|
-
const rootRef = React.useRef(null);
|
|
44
|
-
const visible = flattenVisible(items, expanded);
|
|
45
|
-
// Roving tab stop derivado: se o nó ativo saiu do conjunto visível (dados
|
|
46
|
-
// trocados, nó removido), o primeiro visível volta a ser tabulável — senão a
|
|
47
|
-
// árvore inteira fica tabIndex=-1 e some da ordem do Tab (auditoria, MÉDIO 1).
|
|
48
|
-
const effectiveActive = active !== undefined && visible.some(v => v.node.id === active) ? active : visible[0]?.node.id;
|
|
49
|
-
const focusId = (id) => { setActive(id); rootRef.current?.querySelector(`[data-tree-id="${CSS.escape(id)}"]`)?.focus(); };
|
|
50
|
-
const toggle = (id, open) => setExpanded(prev => { const n = new Set(prev); if (open)
|
|
51
|
-
n.add(id);
|
|
52
|
-
else
|
|
53
|
-
n.delete(id); return n; });
|
|
54
|
-
const select = (node) => { setSelected(node.id); onSelect?.(node); };
|
|
55
|
-
const onKeyDown = (e) => {
|
|
56
|
-
const idx = visible.findIndex(v => v.node.id === effectiveActive);
|
|
57
|
-
if (idx < 0)
|
|
58
|
-
return;
|
|
59
|
-
const cur = visible[idx], hasChildren = !!cur.node.children?.length, isOpen = expanded.has(cur.node.id);
|
|
60
|
-
const rtl = getComputedStyle(e.currentTarget).direction === "rtl";
|
|
61
|
-
const expandKey = rtl ? "ArrowLeft" : "ArrowRight", collapseKey = rtl ? "ArrowRight" : "ArrowLeft";
|
|
62
|
-
switch (e.key) {
|
|
63
|
-
case "ArrowDown":
|
|
64
|
-
e.preventDefault();
|
|
65
|
-
if (idx < visible.length - 1)
|
|
66
|
-
focusId(visible[idx + 1].node.id);
|
|
67
|
-
break;
|
|
68
|
-
case "ArrowUp":
|
|
69
|
-
e.preventDefault();
|
|
70
|
-
if (idx > 0)
|
|
71
|
-
focusId(visible[idx - 1].node.id);
|
|
72
|
-
break;
|
|
73
|
-
case expandKey:
|
|
74
|
-
e.preventDefault();
|
|
75
|
-
if (hasChildren && !isOpen)
|
|
76
|
-
toggle(cur.node.id, true);
|
|
77
|
-
else if (hasChildren && isOpen)
|
|
78
|
-
focusId(cur.node.children[0].id);
|
|
79
|
-
break;
|
|
80
|
-
case collapseKey:
|
|
81
|
-
e.preventDefault();
|
|
82
|
-
if (hasChildren && isOpen)
|
|
83
|
-
toggle(cur.node.id, false);
|
|
84
|
-
else if (cur.parentId)
|
|
85
|
-
focusId(cur.parentId);
|
|
86
|
-
break;
|
|
87
|
-
case "Home":
|
|
88
|
-
e.preventDefault();
|
|
89
|
-
focusId(visible[0].node.id);
|
|
90
|
-
break;
|
|
91
|
-
case "End":
|
|
92
|
-
e.preventDefault();
|
|
93
|
-
focusId(visible[visible.length - 1].node.id);
|
|
94
|
-
break;
|
|
95
|
-
case "Enter":
|
|
96
|
-
case " ":
|
|
97
|
-
e.preventDefault();
|
|
98
|
-
select(cur.node);
|
|
99
|
-
if (hasChildren)
|
|
100
|
-
toggle(cur.node.id, !isOpen);
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
const renderNodes = (nodes, level) => (_jsx("ul", { ref: level === 1 ? rootRef : undefined, className: cx(level === 1 ? "tree" : "tree-group", level === 1 && className), role: level === 1 ? "tree" : "group", "aria-label": level === 1 ? (label ?? s.treeLabel) : undefined, onKeyDown: level === 1 ? onKeyDown : undefined, children: nodes.map(node => {
|
|
105
|
-
const hasChildren = !!node.children?.length, isOpen = expanded.has(node.id), isSelected = selected === node.id, labelId = baseId + node.id;
|
|
106
|
-
return _jsxs("li", { className: "tree-item", role: "treeitem", "data-tree-id": node.id, "aria-level": level, "aria-expanded": hasChildren ? isOpen : undefined, "aria-selected": isSelected, "aria-labelledby": labelId, tabIndex: node.id === effectiveActive ? 0 : -1, children: [_jsxs("span", { className: "tree-node", "data-selected": isSelected || undefined, style: { paddingInlineStart: `calc(var(--space-3) + ${level - 1} * var(--space-4))` }, onClick: () => { focusId(node.id); select(node); if (hasChildren)
|
|
107
|
-
toggle(node.id, !isOpen); }, children: [hasChildren ? _jsx(Icon, { name: "chevron--right", size: "sm", className: "tree-twist" }) : _jsx("span", { className: "tree-indent", "aria-hidden": "true" }), node.icon && _jsx(Icon, { name: node.icon, size: "sm" }), _jsx("span", { id: labelId, className: "tree-label", children: node.label })] }), hasChildren && isOpen && renderNodes(node.children, level + 1)] }, node.id);
|
|
108
|
-
}) }));
|
|
109
|
-
return renderNodes(items, 1);
|
|
110
|
-
}
|
|
111
|
-
function sidebarList(items, ctx, sub, labelledBy) {
|
|
112
|
-
return _jsx("ul", { className: cx("sidebar-list", sub && "sidebar-sub"), "aria-labelledby": labelledBy, children: items.map(it => {
|
|
113
|
-
const lid = ctx.baseId + it.id;
|
|
114
|
-
const filhos = it.items?.length ? it.items : undefined;
|
|
115
|
-
// Rótulo escondido vira `.sr-only` em vez de sumir do DOM: na lateral recolhida o item
|
|
116
|
-
// continua tendo nome para quem usa leitor de tela. Ícone sozinho não nomeia nada.
|
|
117
|
-
const oculto = (no) => ctx.collapsed ? _jsx("span", { className: "sr-only", children: no }) : no;
|
|
118
|
-
if (filhos && !it.href && !it.onClick)
|
|
119
|
-
return _jsxs("li", { children: [_jsx("p", { id: lid, className: cx("sidebar-group-label", ctx.collapsed && "sr-only"), children: it.label }), sidebarList(filhos, ctx, false, lid)] }, it.id);
|
|
120
|
-
const ativo = it.id === ctx.current;
|
|
121
|
-
const miolo = _jsxs(_Fragment, { children: [it.icon && _jsx(Icon, { name: it.icon, size: "sm" }), _jsx("span", { className: cx("sidebar-label", ctx.collapsed && "sr-only"), children: it.label }), it.badge != null && oculto(it.badge)] });
|
|
122
|
-
return _jsxs("li", { children: [it.href
|
|
123
|
-
? _jsx("a", { id: lid, href: it.href, className: "sidebar-item", "aria-current": ativo ? "page" : undefined, onClick: it.onClick, children: miolo })
|
|
124
|
-
: _jsx("button", { id: lid, type: "button", className: "sidebar-item", "aria-current": ativo ? "page" : undefined, onClick: it.onClick, children: miolo }), filhos && sidebarList(filhos, ctx, true, lid)] }, it.id);
|
|
125
|
-
}) });
|
|
126
|
-
}
|
|
127
|
-
export function Sidebar({ items, current, collapsed, label, children, className, ...props }) {
|
|
128
|
-
const s = useAureaStrings();
|
|
129
|
-
const baseId = React.useId();
|
|
130
|
-
return _jsxs("aside", { className: cx("sidebar", collapsed && "sidebar-collapsed", className), ...props, children: [items && items.length > 0 && _jsx("nav", { className: "sidebar-nav", "aria-label": label ?? s.sidebarLabel, children: sidebarList(items, { baseId, current, collapsed }) }), children] });
|
|
131
|
-
}
|
|
132
|
-
export function Topbar({ variant = "floating", brand, children, className, ...props }) { return _jsxs("header", { className: cx("topbar", `topbar-${variant}`, className), ...props, children: [brand != null && _jsx("div", { className: "brand", children: brand }), children] }); }
|
|
133
|
-
// Shell NÃO-modal de propósito: é presentational (o consumidor controla open) e não
|
|
134
|
-
// tem focus trap/inert/Escape — declarar aria-modal sem entregar a modalidade faria
|
|
135
|
-
// leitores ocultarem o resto da página com o teclado ainda alcançando tudo
|
|
136
|
-
// (auditoria 18/07/2026, ALTO 2; APG dialog-modal). Modal de verdade = BaseDialog.
|
|
137
|
-
export function CommandPaletteShell({ open, query, onQueryChange, children }) { const s = useAureaStrings(); if (!open)
|
|
138
|
-
return null; return _jsx("div", { className: "command-overlay", children: _jsxs("div", { className: "command-palette", role: "dialog", "aria-label": s.commandLabel, children: [_jsx(SearchField, { autoFocus: true, "aria-label": s.commandPlaceholder, value: query, onChange: e => onQueryChange(e.target.value), placeholder: s.commandPlaceholder }), children] }) }); }
|
|
1
|
+
// VITRINE da categoria — é este arquivo que `@aurea-uds/react/navigation` resolve. Sem
|
|
2
|
+
// `"use client"` de propósito: a diretiva contamina o módulo inteiro e faria a marcação pura
|
|
3
|
+
// chegar como cliente por vizinhança (ADR-0026; o check 26b reprova quem a puser de volta aqui).
|
|
4
|
+
export * from "./navigation-client.js";
|
|
5
|
+
export { Topbar } from "./markup.js";
|
package/dist/overlays.d.ts
CHANGED
|
@@ -7,6 +7,16 @@ export declare function Dialog({ open, title, children, footer, onClose }: {
|
|
|
7
7
|
footer?: ReactNode;
|
|
8
8
|
onClose: () => void;
|
|
9
9
|
}): React.JSX.Element;
|
|
10
|
+
export declare function ConfirmDialog({ open, title, description, confirmLabel, cancelLabel, destructive, onConfirm, onCancel }: {
|
|
11
|
+
open: boolean;
|
|
12
|
+
title: ReactNode;
|
|
13
|
+
description: ReactNode;
|
|
14
|
+
confirmLabel?: string;
|
|
15
|
+
cancelLabel?: string;
|
|
16
|
+
destructive?: boolean;
|
|
17
|
+
onConfirm: () => void;
|
|
18
|
+
onCancel: () => void;
|
|
19
|
+
}): React.JSX.Element;
|
|
10
20
|
export declare function Drawer({ open, title, children, onClose, side }: {
|
|
11
21
|
open: boolean;
|
|
12
22
|
title: ReactNode;
|
|
@@ -14,6 +24,19 @@ export declare function Drawer({ open, title, children, onClose, side }: {
|
|
|
14
24
|
onClose: () => void;
|
|
15
25
|
side?: "left" | "right";
|
|
16
26
|
}): React.JSX.Element;
|
|
27
|
+
export type AccessGateProps = {
|
|
28
|
+
allowed: boolean;
|
|
29
|
+
children: ReactElement;
|
|
30
|
+
} & ({
|
|
31
|
+
mode?: "hide";
|
|
32
|
+
fallback?: ReactNode;
|
|
33
|
+
reason?: never;
|
|
34
|
+
} | {
|
|
35
|
+
mode: "disable";
|
|
36
|
+
reason: ReactNode;
|
|
37
|
+
fallback?: never;
|
|
38
|
+
});
|
|
39
|
+
export declare function AccessGate({ allowed, children, ...resto }: AccessGateProps): React.JSX.Element;
|
|
17
40
|
export type OverlaySide = "top" | "right" | "bottom" | "left";
|
|
18
41
|
export declare function Tooltip({ children, content, side }: {
|
|
19
42
|
children: ReactElement;
|
package/dist/overlays.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
// Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
|
|
4
4
|
// do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
|
|
5
5
|
// internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { Dialog as BaseDialog } from "@base-ui/react/dialog";
|
|
8
|
+
import { AlertDialog as BaseAlertDialog } from "@base-ui/react/alert-dialog";
|
|
8
9
|
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
9
10
|
import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
|
|
10
11
|
import { Menu as BaseMenu } from "@base-ui/react/menu";
|
|
@@ -12,17 +13,60 @@ import { ContextMenu as BaseContextMenu } from "@base-ui/react/context-menu";
|
|
|
12
13
|
import { PreviewCard as BasePreviewCard } from "@base-ui/react/preview-card";
|
|
13
14
|
import { cx, useAureaStrings, usePortalContainer } from "./internal.js";
|
|
14
15
|
import { Icon } from "./system.js";
|
|
16
|
+
import { Button } from "./actions.js";
|
|
15
17
|
export function Dialog({ open, title, children, footer, onClose }) { const s = useAureaStrings(); const portal = usePortalContainer(); return _jsx(BaseDialog.Root, { open: open, onOpenChange: o => { if (!o)
|
|
16
18
|
onClose(); }, children: _jsxs(BaseDialog.Portal, { container: portal, children: [_jsx(BaseDialog.Backdrop, { className: "dialog-backdrop" }), _jsxs(BaseDialog.Popup, { className: "dialog", children: [_jsxs("header", { children: [_jsx(BaseDialog.Title, { render: _jsx("h2", {}), children: title }), _jsx(BaseDialog.Close, { className: "btn btn-ghost btn-icon", "aria-label": s.close, children: _jsx(Icon, { name: "close" }) })] }), _jsx("div", { className: "dialog-body", children: children }), footer && _jsx("footer", { children: footer })] })] }) }); }
|
|
19
|
+
// ConfirmDialog (M5): a decisão que não se fecha por engano. NÃO é um Dialog com dois botões —
|
|
20
|
+
// é `role="alertdialog"`, e a diferença é de comportamento, não de aparência: o Base UI tira do
|
|
21
|
+
// AlertDialog.Root as props `modal` e `disablePointerDismissal` (medido no d.ts de 1.6.0), ou
|
|
22
|
+
// seja, clicar fora NÃO fecha. Num Dialog comum, clicar fora vira "cancelei" sem a pessoa ter
|
|
23
|
+
// decidido — que é exatamente o acidente que este componente existe para impedir.
|
|
24
|
+
//
|
|
25
|
+
// FOCO NO BOTÃO SEGURO: quem abre um "isto apaga" e aperta Enter por reflexo tem de cancelar,
|
|
26
|
+
// não apagar. As quatro referências convergem em cancelar-antes-de-agir na ORDEM visual, e
|
|
27
|
+
// nenhuma delas move o foco — este passo saiu de medir o teclado, não de ler.
|
|
28
|
+
//
|
|
29
|
+
// DUAS coisas garantem, e as duas foram medidas em 13/08/2026, uma de cada vez:
|
|
30
|
+
// • a ORDEM do DOM — o Cancelar vem primeiro, e o motor foca o primeiro focável. Sozinha, ela
|
|
31
|
+
// já faz o teste passar; tirar só o `initialFocus` NÃO reprova.
|
|
32
|
+
// • o `initialFocus`, que é o cinto: com a ordem dos dois botões INVERTIDA ele continua
|
|
33
|
+
// segurando o foco no Cancelar, e é aí que ele prova que não é enfeite. Sem ele e com a
|
|
34
|
+
// ordem invertida, o teste reprova — que é o defeito de verdade, porque inverter a ordem é
|
|
35
|
+
// uma mudança de aparência que alguém faz sem pensar no teclado.
|
|
36
|
+
// O teste cobra o EFEITO ("o foco nasce no seguro"), não o mecanismo. É por isso que ele
|
|
37
|
+
// sobrevive a trocar um dos dois — e reprova quando os dois somem.
|
|
38
|
+
//
|
|
39
|
+
// ESCOPO MENOR que a referência (BUILDING.md §5): lá são nove peças compostas
|
|
40
|
+
// (Root/Trigger/Content/Header/Title/Description/Footer/Cancel/Action). Aqui é uma prop `open`,
|
|
41
|
+
// como no `Dialog` e no `Drawer` — a composição não acrescenta escolha nenhuma num diálogo cujo
|
|
42
|
+
// corpo é uma frase e dois botões.
|
|
43
|
+
//
|
|
44
|
+
// `description` é prop e não `children` porque ela é o nó de `aria-describedby`: o AlertDialog
|
|
45
|
+
// só anuncia o que passa pelo `Description`. Como `children`, um consumidor poria um <div> no
|
|
46
|
+
// meio e o leitor de tela perderia a frase que diz o que se perde.
|
|
47
|
+
export function ConfirmDialog({ open, title, description, confirmLabel, cancelLabel, destructive, onConfirm, onCancel }) {
|
|
48
|
+
const s = useAureaStrings();
|
|
49
|
+
const portal = usePortalContainer();
|
|
50
|
+
const seguro = React.useRef(null);
|
|
51
|
+
return _jsx(BaseAlertDialog.Root, { open: open, onOpenChange: o => { if (!o)
|
|
52
|
+
onCancel(); }, children: _jsxs(BaseAlertDialog.Portal, { container: portal, children: [_jsx(BaseAlertDialog.Backdrop, { className: "dialog-backdrop" }), _jsxs(BaseAlertDialog.Popup, { className: "dialog dialog-confirm", initialFocus: seguro, children: [_jsx("header", { children: _jsx(BaseAlertDialog.Title, { render: _jsx("h2", {}), children: title }) }), _jsx(BaseAlertDialog.Description, { className: "dialog-body", children: description }), _jsxs("footer", { children: [_jsx(Button, { ref: seguro, variant: "secondary", onClick: onCancel, children: cancelLabel ?? s.confirmCancel }), _jsx(Button, { variant: destructive ? "danger" : "primary", onClick: onConfirm, children: confirmLabel ?? s.confirmProceed })] })] })] }) });
|
|
53
|
+
}
|
|
17
54
|
export function Drawer({ open, title, children, onClose, side = "right" }) { const s = useAureaStrings(); const portal = usePortalContainer(); return _jsx(BaseDialog.Root, { open: open, onOpenChange: o => { if (!o)
|
|
18
55
|
onClose(); }, children: _jsxs(BaseDialog.Portal, { container: portal, children: [_jsx(BaseDialog.Backdrop, { className: "drawer-backdrop" }), _jsxs(BaseDialog.Popup, { className: cx("drawer", `drawer-${side}`), children: [_jsxs("header", { children: [_jsx(BaseDialog.Title, { render: _jsx("h2", {}), children: title }), _jsx(BaseDialog.Close, { className: "btn btn-ghost btn-icon", "aria-label": s.close, children: _jsx(Icon, { name: "close" }) })] }), children] })] }) }); }
|
|
56
|
+
export function AccessGate({ allowed, children, ...resto }) {
|
|
57
|
+
if (allowed)
|
|
58
|
+
return children;
|
|
59
|
+
if (resto.mode === "disable")
|
|
60
|
+
return _jsx(Tooltip, { content: resto.reason, children: React.cloneElement(children, { "aria-disabled": true }) });
|
|
61
|
+
return _jsx(_Fragment, { children: resto.fallback ?? null });
|
|
62
|
+
}
|
|
19
63
|
// `role="tooltip"` + `aria-describedby` no disparador é o que faz a dica EXISTIR para leitor
|
|
20
64
|
// de tela. Medido em 30/07/2026: sem isso, o popup saía sem role e sem id, e o disparador sem
|
|
21
65
|
// aria-describedby — quem navega por leitor de tela ouvia só o rótulo do botão e nunca o
|
|
22
66
|
// conteúdo da dica. Era tooltip visual, não acessível (padrão APG Tooltip).
|
|
23
67
|
// O id aponta para um elemento que só existe quando aberto; referência pendente é ignorada
|
|
24
68
|
// pela tecnologia assistiva, então não precisa acompanhar o estado.
|
|
25
|
-
export function Tooltip({ children, content, side = "top" }) { const id = React.useId(); const portal = usePortalContainer(); return _jsxs(BaseTooltip.Root, { children: [_jsx(BaseTooltip.Trigger, { render: children, "aria-describedby": id }), _jsx(BaseTooltip.Portal, { container: portal, children: _jsx(BaseTooltip.Positioner, { side: side, sideOffset: 8, children: _jsx(BaseTooltip.Popup, { id: id, role: "tooltip", className: "tooltip", children: content }) }) })] }); }
|
|
69
|
+
export function Tooltip({ children, content, side = "top" }) { const id = React.useId(); const triggerId = children.props.id; const portal = usePortalContainer(); return _jsxs(BaseTooltip.Root, { children: [_jsx(BaseTooltip.Trigger, { id: triggerId, render: children, "aria-describedby": id }), _jsx(BaseTooltip.Portal, { container: portal, children: _jsx(BaseTooltip.Positioner, { side: side, sideOffset: 8, children: _jsx(BaseTooltip.Popup, { id: id, role: "tooltip", className: "tooltip", children: content }) }) })] }); }
|
|
26
70
|
export function Popover({ trigger, title, children, side = "bottom" }) { const portal = usePortalContainer(); return _jsxs(BasePopover.Root, { children: [_jsx(BasePopover.Trigger, { render: trigger }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { side: side, sideOffset: 8, children: _jsxs(BasePopover.Popup, { className: "popover", children: [title && _jsx(BasePopover.Title, { render: _jsx("strong", {}), children: title }), children] }) }) })] }); }
|
|
27
71
|
// HoverCard (Lote 1 do BUILDING.md). Não é Tooltip nem Popover, e a diferença é de propósito,
|
|
28
72
|
// não de aparência: a Tooltip é um RÓTULO curto (`role="tooltip"`, some ao mover o mouse); o
|
package/dist/pure.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export declare const universalStates: readonly UniversalState[];
|
|
|
4
4
|
export declare const stateSeverity: (state: UniversalState) => "info" | "warning";
|
|
5
5
|
export interface AureaStrings {
|
|
6
6
|
close: string;
|
|
7
|
+
confirmCancel: string;
|
|
8
|
+
dataError: string;
|
|
9
|
+
dataEmpty: string;
|
|
10
|
+
confirmProceed: string;
|
|
7
11
|
paginationLabel: string;
|
|
8
12
|
previous: string;
|
|
9
13
|
next: string;
|
|
@@ -68,6 +72,22 @@ export interface AureaStrings {
|
|
|
68
72
|
mediaSkipForward: string;
|
|
69
73
|
mediaFullscreenEnter: string;
|
|
70
74
|
mediaFullscreenExit: string;
|
|
75
|
+
carouselLabel: string;
|
|
76
|
+
carouselSlide: string;
|
|
77
|
+
positionOf: string;
|
|
78
|
+
carouselPrev: string;
|
|
79
|
+
carouselNext: string;
|
|
80
|
+
galleryLabel: string;
|
|
81
|
+
sortableLabel: string;
|
|
82
|
+
sortableHandle: string;
|
|
83
|
+
sortableHelp: string;
|
|
84
|
+
sortableGrabbed: string;
|
|
85
|
+
sortableDropped: string;
|
|
86
|
+
sortableMoved: string;
|
|
87
|
+
sortableCanceled: string;
|
|
88
|
+
blockEditorLabel: string;
|
|
89
|
+
blockLabel: string;
|
|
90
|
+
blockRemove: string;
|
|
71
91
|
uploadSending: string;
|
|
72
92
|
uploadCancel: string;
|
|
73
93
|
uploadRetry: string;
|
|
@@ -142,6 +162,7 @@ export interface AureaStrings {
|
|
|
142
162
|
tocLabel: string;
|
|
143
163
|
navigationToggle: string;
|
|
144
164
|
sidebarLabel: string;
|
|
165
|
+
bottomNavLabel: string;
|
|
145
166
|
universalState: Record<UniversalState, string>;
|
|
146
167
|
}
|
|
147
168
|
export declare const defaultStrings: AureaStrings;
|
|
@@ -164,3 +185,13 @@ export declare function gridStateFromParams(params: URLSearchParams, filters?: A
|
|
|
164
185
|
column: string;
|
|
165
186
|
facet?: boolean;
|
|
166
187
|
}>): GridState;
|
|
188
|
+
export interface ScreenState extends GridState {
|
|
189
|
+
tab?: string;
|
|
190
|
+
view?: string;
|
|
191
|
+
detail?: string;
|
|
192
|
+
}
|
|
193
|
+
export declare function screenStateToParams(state: ScreenState, into?: URLSearchParams): URLSearchParams;
|
|
194
|
+
export declare function screenStateFromParams(params: URLSearchParams, filters?: Array<{
|
|
195
|
+
column: string;
|
|
196
|
+
facet?: boolean;
|
|
197
|
+
}>): ScreenState;
|