@aurea-uds/react 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/inputs.d.ts CHANGED
@@ -26,6 +26,32 @@ export declare function Switch({ label, className, ...props }: InputHTMLAttribut
26
26
  label: ReactNode;
27
27
  }): React.JSX.Element;
28
28
  export declare function Range(props: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement>): React.JSX.Element;
29
+ export declare function NumberField({ value, defaultValue, onValueChange, min, max, step, disabled, readOnly, required, label, id, className }: {
30
+ value?: number | null;
31
+ defaultValue?: number;
32
+ onValueChange?: (v: number | null) => void;
33
+ min?: number;
34
+ max?: number;
35
+ step?: number;
36
+ disabled?: boolean;
37
+ readOnly?: boolean;
38
+ required?: boolean;
39
+ label?: string;
40
+ id?: string;
41
+ className?: string;
42
+ }): React.JSX.Element;
43
+ export declare function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className }: {
44
+ length: number;
45
+ value?: string;
46
+ defaultValue?: string;
47
+ onValueChange?: (v: string) => void;
48
+ mask?: boolean;
49
+ disabled?: boolean;
50
+ required?: boolean;
51
+ label?: string;
52
+ id?: string;
53
+ className?: string;
54
+ }): React.JSX.Element;
29
55
  export declare function SegmentedControl({ items, value, onChange, label }: {
30
56
  items: Array<{
31
57
  value: string;
package/dist/inputs.js CHANGED
@@ -1,10 +1,15 @@
1
+ "use client";
1
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
4
5
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
5
6
  import React, { forwardRef } from "react";
6
7
  import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
7
- import { cx, useAureaStrings } from "./internal.js";
8
+ import { NumberField as BaseNumberField } from "@base-ui/react/number-field";
9
+ import { OTPField as BaseOTPField } from "@base-ui/react/otp-field";
10
+ import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
11
+ import { Radio as BaseRadio } from "@base-ui/react/radio";
12
+ import { cx, useAureaStrings, usePortalContainer } from "./internal.js";
8
13
  import { Icon } from "./system.js";
9
14
  import { IconButton } from "./actions.js";
10
15
  // FileInput mora em arquivo próprio (132 linhas — upload real, aborto, progresso) e é público
@@ -41,21 +46,81 @@ export function Checkbox({ label, labelHidden, description, className, ...props
41
46
  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
47
  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
48
  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)) }); }
49
+ // NumberField (Lote 1 do BUILDING.md). Um `<input type="number">` cru tem três problemas que
50
+ // este resolve, e todos vieram do motor, não de nós: as setas nativas são alvos minúsculos e
51
+ // somem no Safari; a roda do mouse altera o valor por acidente sobre o campo focado; e o valor
52
+ // digitado não é formatado por locale. O Base UI trata os três.
53
+ // A anatomia (menos · campo · mais numa peça só) é a que as referências convergem.
54
+ // Fora de propósito: o ScrubArea do Base UI — arrastar o rótulo para variar o número. É gesto
55
+ // que ninguém descobre sozinho e que não tem equivalente por teclado.
56
+ export function NumberField({ value, defaultValue, onValueChange, min, max, step, disabled, readOnly, required, label, id, className }) {
57
+ const s = useAureaStrings();
58
+ return _jsx(BaseNumberField.Root, { id: id, value: value, defaultValue: defaultValue, onValueChange: onValueChange, min: min, max: max, step: step, disabled: disabled, readOnly: readOnly, required: required, className: cx("number-field", className), children: _jsxs(BaseNumberField.Group, { className: "number-field-group", children: [_jsx(BaseNumberField.Decrement, { className: "btn btn-ghost btn-icon", "aria-label": s.decrement, children: _jsx(Icon, { name: "subtract" }) }), _jsx(BaseNumberField.Input, { className: "input number-field-input", "aria-label": label }), _jsx(BaseNumberField.Increment, { className: "btn btn-ghost btn-icon", "aria-label": s.increment, children: _jsx(Icon, { name: "add" }) })] }) });
59
+ }
60
+ // OTPField (Lote 1 do BUILDING.md). Um campo por dígito, com o comportamento que ninguém acerta
61
+ // à mão: colar o código inteiro distribui pelos campos, Backspace volta um, e o
62
+ // `autocomplete="one-time-code"` deixa o iOS oferecer o código do SMS. Tudo do motor.
63
+ // Uma referência resolve isto com um pacote npm separado; aqui não entra dependência nova —
64
+ // o `@base-ui/react`, que já é a única dependência de runtime da biblioteca, tem `otp-field`.
65
+ // `length` é obrigatório de propósito: sem ele o campo não sabe quando está completo.
66
+ // CADA caixa leva nome próprio, e não só o grupo. O `catalog-sweep` pegou isto no primeiro
67
+ // lote: com `aria-label` só na raiz, o axe acusou `label` em todas as seis — seis campos de
68
+ // formulário anônimos. Um leitor de tela anunciaria "editar texto" seis vezes seguidas sem
69
+ // dizer qual é qual. O grupo continua nomeado (`role="group"`), que é o que diz para que serve
70
+ // o código; o nome de cada caixa é o que diz onde você está.
71
+ export function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className }) {
72
+ const s = useAureaStrings();
73
+ return _jsx(BaseOTPField.Root, { id: id, length: length, value: value, defaultValue: defaultValue, onValueChange: onValueChange, mask: mask, disabled: disabled, required: required, role: "group", "aria-label": label, className: cx("otp-field", className), children: Array.from({ length }, (_, i) => _jsx(BaseOTPField.Input, { render: _jsx("input", { "aria-label": `${s.otpDigit} ${i + 1}` }), className: "input otp-slot" }, i)) });
74
+ }
75
+ // SegmentedControl — achado M20, fechado na Parte C do PLANO-1.0 em 07/08/2026.
76
+ //
77
+ // O DEFEITO: era `role="group"` com N botões de `aria-pressed`. Isso descreve N alternâncias
78
+ // INDEPENDENTES — cada botão anuncia "pressionado/não pressionado", nada diz que só um pode
79
+ // valer, e nunca se ouve "1 de 2". Para escolha única entre poucas opções o padrão APG é
80
+ // `radiogroup`, e a diferença é o que o leitor de tela consegue prometer.
81
+ // A troca mudou a semântica de um componente publicado, então foi registrada: ADR-0016.
82
+ //
83
+ // O MOTOR ENTREGA, ENTÃO NÃO ESCREVEMOS (BUILDING.md §1). O `RadioGroup` do Base UI 1.6 traz o
84
+ // padrão inteiro: roving tabindex, setas que movem E selecionam, Home/End, e `aria-checked`.
85
+ // Escrever isso à mão seria reimplementar um composite que já está testado.
86
+ //
87
+ // `render={<button type="button"/>}` não é enfeite, e a medição de 07/08 é que disse: o
88
+ // `Radio.Root` renderiza um `<span>` por padrão, e a pele da Aurea é `.segmented button`. Sem
89
+ // isso o componente perderia a pele inteira — e o check 18 não veria, porque ele olha CLASSE.
90
+ // O motor também emite um `<input type="radio">` escondido por item (para envio de formulário);
91
+ // ele é `position:fixed` e `aria-hidden`, então não entra no flex nem na árvore de acessibilidade.
92
+ //
93
+ // E `nativeButton` é o par obrigatório do `render` acima — medido em 11/08/2026. O `Radio.Root`
94
+ // declara `nativeButton = false` por padrão (é o valor certo para o `<span>` que ele renderiza
95
+ // sozinho), e o `useButton` do motor CONFERE isso no DOM montado, dentro de um efeito: sem esta
96
+ // palavra sai um `console.error` do Base UI a cada montagem de CLIENTE. Não no catálogo, que é
97
+ // HTML estático — no console de quem INSTALA.
98
+ // O DOM foi medido nos dois lados antes da troca. O `role="button"` que a mensagem ameaça NÃO
99
+ // aparecia (o `role="radio"` do motor já vencia a fusão); o que muda de fato é o `<input>`
100
+ // escondido perder o `id`, que migra para este botão — e migra para o lado certo, porque o botão
101
+ // é o rádio e o input é `aria-hidden`. Comportamento idêntico: Enter não seleciona, Espaço
102
+ // seleciona, a seta move E seleciona. O que sai é a camada sintética que o motor punha por cima
103
+ // da ativação nativa do `<button>`.
104
+ export function SegmentedControl({ items, value, onChange, label }) {
105
+ const s = useAureaStrings();
106
+ return _jsx(BaseRadioGroup, { className: "segmented", "aria-label": label ?? s.optionsLabel, value: value, onValueChange: v => onChange(String(v)), children: items.map(i => _jsx(BaseRadio.Root, { value: i.value, className: i.value === value ? "active" : undefined, nativeButton: true, render: _jsx("button", { type: "button" }), children: i.label }, i.value)) });
107
+ }
45
108
  export function Combobox({ items, value, onValueChange, placeholder, label, id, className }) {
46
109
  const s = useAureaStrings();
110
+ const portal = usePortalContainer();
47
111
  const autoId = React.useId();
48
112
  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) })] }) }) })] });
113
+ 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, { container: 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
114
  }
51
115
  const isGrouped = (items) => items.length > 0 && "items" in items[0];
52
116
  export function MultiCombobox({ items, value, onValueChange, onInputChange, loading, placeholder, label, id, className }) {
53
117
  const s = useAureaStrings();
118
+ const portal = usePortalContainer();
54
119
  const autoId = React.useId();
55
120
  const inputId = id ?? autoId;
56
121
  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
122
  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)
123
+ 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, { container: 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
124
  ? (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
125
  : renderItem })] }) }) })] });
61
126
  }
@@ -1,72 +1,10 @@
1
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;
2
+ import { type AureaStrings } from "./pure.js";
3
+ export { cx, defaultStrings, ptBR, defaultSpriteUrl, universalStates, stateSeverity, type AureaStrings, type UniversalState } from "./pure.js";
67
4
  export declare const StringsContext: import("react").Context<AureaStrings>;
68
5
  export declare const useAureaStrings: () => AureaStrings;
69
- export declare const defaultSpriteUrl = "/aurea-icons.svg";
70
6
  export declare const SpriteContext: import("react").Context<string>;
71
7
  export declare const useSpriteUrl: () => string;
8
+ export declare const PortalContext: import("react").Context<HTMLElement | null | undefined>;
9
+ export declare const usePortalContainer: () => HTMLElement | null | undefined;
72
10
  export declare function Kbd({ children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement>): import("react").JSX.Element;
package/dist/internal.js CHANGED
@@ -1,29 +1,40 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
+ // A diretiva na LINHA 1, como as referências a escrevem (Base UI e shadcn/ui, medidos em
4
+ // 06/08/2026) — Parte A do PLANO-1.0. Este módulo chama `createContext`, que é API só de
5
+ // cliente: sem a diretiva, `import {Button} from "@aurea-uds/react"` dentro de um componente
6
+ // de servidor quebrava na hora, e quebrava no consumidor.
7
+ //
8
+ // O que NÃO tem estado saiu daqui para o `pure.tsx` — está explicado lá. A reexportação abaixo
9
+ // existe para os 18 módulos de cliente seguirem importando `cx` de "./internal.js" sem mudança.
10
+ //
2
11
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
12
  // 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.
13
+ // pure → internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
5
14
  // INTERNO: o que todo módulo precisa e ninguém publica como componente próprio.
6
15
  // NÃO é subpath — `@aurea-uds/react/internal` não existe, de propósito.
7
16
  // Kbd mora aqui por dependência, não por categoria: o Button precisa dele e ele não precisa de
8
17
  // ninguém. A casa pública dele continua sendo `/data-display`, que o reexporta.
9
18
  import { createContext, useContext } from "react";
10
- export const cx = (...v) => v.filter(Boolean).join(" ");
11
- export const defaultStrings = { close: "Close", paginationLabel: "Pagination", previous: "Previous", next: "Next", breadcrumbLabel: "Breadcrumb", tabsLabel: "Tabs", optionsLabel: "Options", tableLabel: "Table", commandLabel: "Command palette", commandPlaceholder: "Type a command…", dismissNotification: "Dismiss notification", toolbarLabel: "Toolbar", comboboxEmpty: "No results", comboboxClear: "Clear selection", comboboxOpen: "Open list", comboboxRemove: "Remove", comboboxLoading: "Loading…", fileDropPrompt: "Drag files here or click to select", fileAdded: "File added", fileRemoved: "File removed", fileRemove: "Remove", fileTooLarge: "File exceeds the size limit", fileWrongType: "File type not allowed", treeLabel: "Tree", notificationsLabel: "Notifications", notificationMarkAll: "Mark all as read", notificationEmpty: "No notifications", notificationUnread: "Unread", notificationNew: "new notifications", dataGridFilter: "Filter", dataGridEmpty: "No results", dataGridSelectAll: "Select all rows", dataGridSelectRow: "Select row", mediaPlayer: "Media player", mediaPlay: "Play", mediaPause: "Pause", mediaMute: "Mute", mediaUnmute: "Unmute", mediaSeek: "Seek", mediaVolume: "Volume", mediaCaptionsShow: "Show captions", mediaCaptionsHide: "Hide captions", mediaSkipBack: "Skip back 10 seconds", mediaSkipForward: "Skip forward 10 seconds", mediaFullscreenEnter: "Enter full screen", mediaFullscreenExit: "Exit full screen", uploadSending: "Uploading", uploadCancel: "Cancel upload", uploadRetry: "Retry upload", uploadError: "Upload failed", uploadCanceled: "Upload canceled", uploadComplete: "Upload complete", codeEditor: "Code editor", chatLabel: "Conversation", chatMessage: "Message", chatSend: "Send", qrCode: "QR code", copyCode: "Copy code", tocLabel: "On this page", navigationToggle: "Navigation" };
12
- // pt-BR preservado como locale (mesmos valores que já eram o default): passe
13
- // <AureaProvider strings={ptBR}> para restaurar português.
14
- export const ptBR = { close: "Fechar", paginationLabel: "Paginação", previous: "Anterior", next: "Próxima", breadcrumbLabel: "Breadcrumb", tabsLabel: "Abas", optionsLabel: "Opções", tableLabel: "Tabela", commandLabel: "Paleta de comandos", commandPlaceholder: "Digite um comando…", dismissNotification: "Dispensar notificação", toolbarLabel: "Barra de ferramentas", comboboxEmpty: "Nenhum resultado", comboboxClear: "Limpar seleção", comboboxOpen: "Abrir lista", comboboxRemove: "Remover", comboboxLoading: "Carregando…", fileDropPrompt: "Arraste arquivos aqui ou clique para selecionar", fileAdded: "Arquivo adicionado", fileRemoved: "Arquivo removido", fileRemove: "Remover", fileTooLarge: "Arquivo maior que o limite", fileWrongType: "Tipo de arquivo não aceito", treeLabel: "Árvore", notificationsLabel: "Notificações", notificationMarkAll: "Marcar todas como lidas", notificationEmpty: "Nenhuma notificação", notificationUnread: "Não lida", notificationNew: "novas notificações", dataGridFilter: "Filtrar", dataGridEmpty: "Nenhum resultado", dataGridSelectAll: "Selecionar todas as linhas", dataGridSelectRow: "Selecionar linha", mediaPlayer: "Reprodutor de mídia", mediaPlay: "Reproduzir", mediaPause: "Pausar", mediaMute: "Silenciar", mediaUnmute: "Ativar som", mediaSeek: "Posição da reprodução", mediaVolume: "Volume", mediaCaptionsShow: "Ativar legendas", mediaCaptionsHide: "Desativar legendas", mediaSkipBack: "Voltar 10 segundos", mediaSkipForward: "Avançar 10 segundos", mediaFullscreenEnter: "Tela cheia", mediaFullscreenExit: "Sair da tela cheia", uploadSending: "Enviando", uploadCancel: "Cancelar envio", uploadRetry: "Tentar envio novamente", uploadError: "Falha no envio", uploadCanceled: "Envio cancelado", uploadComplete: "Envio concluído", codeEditor: "Editor de código", chatLabel: "Conversa", chatMessage: "Mensagem", chatSend: "Enviar", qrCode: "Código QR", copyCode: "Copiar código", tocLabel: "Nesta página", navigationToggle: "Navegação" };
19
+ import { cx, defaultStrings, defaultSpriteUrl } from "./pure.js";
20
+ export { cx, defaultStrings, ptBR, defaultSpriteUrl, universalStates, stateSeverity } from "./pure.js";
15
21
  export const StringsContext = createContext(defaultStrings);
16
22
  export const useAureaStrings = () => useContext(StringsContext);
17
- // Sprite de ícones: CONFIGURAÇÃO DE APLICAÇÃO, então entra pelo provider, uma vez.
18
- // Antes o default absoluto "/aurea-icons.svg" vivia no Icon e 15 componentes declaravam
19
- // e repassavam `spriteUrl` à mão — 52 sítios de código. Consequência: aplicação servida
20
- // em subcaminho perdia TODOS os ícones, e para consertar tinha de passar a prop em cada
21
- // componente (auditoria 26/07/2026, achado A4 — causa raiz do A3 daquela auditoria, que
22
- // foi corrigido nos fallbacks do catálogo e não aqui). O default segue o mesmo para quem
23
- // não configura nada; `Icon` aceita a prop como override local.
24
- export const defaultSpriteUrl = "/aurea-icons.svg";
25
23
  export const SpriteContext = createContext(defaultSpriteUrl);
26
24
  export const useSpriteUrl = () => useContext(SpriteContext);
25
+ // ONDE OS POPUPS SÃO MONTADOS — achado B7, fechado na Parte C do PLANO-1.0 (07/08/2026).
26
+ // O DEFEITO não era o portal: era o consumidor não ter como mexer nele. Todo popup da Aurea
27
+ // (diálogo, gaveta, dica, popover, os dois menus, notificação, combobox, toast) monta num
28
+ // portal no nível do `body` — é o que permite empilhar e posicionar sem herdar `overflow` nem
29
+ // `transform` de ninguém. A consequência é que o conteúdo dele fica FORA de qualquer landmark,
30
+ // e o axe acusa `region` ("todo conteúdo num landmark"). A auditoria registrou, e escreveu que
31
+ // "a única forma de satisfazer seria o consumidor montar o portal dentro do landmark dele" —
32
+ // só que ele não tinha essa forma, porque o `container` do Base UI nunca foi exposto.
33
+ // Agora tem, e entra pelo provider, UMA vez, como o sprite: é configuração de aplicação, não
34
+ // prop de componente (foi a lição do achado A4, com 52 sítios repassando `spriteUrl` à mão).
35
+ // `undefined` é o default e mantém o comportamento de sempre: `document.body`.
36
+ export const PortalContext = createContext(undefined);
37
+ export const usePortalContainer = () => useContext(PortalContext);
27
38
  // direction informa o Base UI (lado dos popovers, setas do teclado). O CSS
28
39
  // espelha sozinho por propriedade lógica, mas depende do atributo dir no DOM:
29
40
  // quem consome precisa pôr dir="rtl" no <html>. Este provider não mexe no DOM.
package/dist/layout.d.ts CHANGED
@@ -6,9 +6,10 @@ export declare function Card({ variant, className, ...props }: HTMLAttributes<HT
6
6
  export declare function Stack({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): import("react").JSX.Element;
7
7
  export declare function Cluster({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): import("react").JSX.Element;
8
8
  export declare function Grid({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): import("react").JSX.Element;
9
- export declare function AppShell({ brand, navigation, topbar, topbarVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
9
+ export declare function AppShell({ brand, navigation, topbar, topbarVariant, sidebarCollapsed, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
10
10
  brand: ReactNode;
11
11
  navigation: ReactNode;
12
12
  topbar?: ReactNode;
13
13
  topbarVariant?: Exclude<TopbarVariant, "pill">;
14
+ sidebarCollapsed?: boolean;
14
15
  }): import("react").JSX.Element;
package/dist/layout.js CHANGED
@@ -1,7 +1,51 @@
1
+ "use client";
1
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { cx, useAureaStrings } from "./internal.js";
3
4
  import { IconButton } from "./actions.js";
4
5
  import { Sidebar, Topbar } from "./navigation.js";
6
+ // ── Auxiliar de topo: mora ANTES do primeiro export, e a posição é obrigatória ───────────────
7
+ // O check 22 mede o corpo de um componente do `export` dele até o PRÓXIMO export — fatia longa
8
+ // de propósito, porque a implementação de vários componentes daqui continua num auxiliar não
9
+ // exportado logo abaixo (a `Sidebar` é o caso: o `aria-current` dela mora num). Encurtar a fatia
10
+ // foi tentado em 09/08/2026 e a prova contra o defeito recusou: a `Sidebar` com `states: []`
11
+ // deixava de ser acusada.
12
+ // Consequência: auxiliar escrito ENTRE dois exports é lido como parte do componente anterior.
13
+ // Esta função tem `button:not([disabled])` num seletor, e o `\bdisabled\b` da regra acusou o
14
+ // `Grid` de emitir estado que ele não emite. Escrita aqui em cima, não pertence à fatia de
15
+ // ninguém. **Auxiliar de topo em módulo de componente vem antes dos exports.**
16
+ //
17
+ // O que ela faz e por que existe: o popover nativo não entrega a mesma gestão de foco nos três
18
+ // motores, e a tabela medida está no `aurea.js` do core. As mesmas cinco linhas moram lá, e a
19
+ // repetição é deliberada porque são DOIS RUNTIMES, não dois desenhos — o do core é delegado e
20
+ // serve página estática e consumo vanilla (é o que o catálogo carrega); este serve quem instala
21
+ // só o `@aurea-uds/react`, que importa o CSS do core e nunca o JS dele. Rodar os dois é
22
+ // inofensivo: focar o mesmo elemento duas vezes não faz nada.
23
+ //
24
+ // A comparação é com `"closed"` e NÃO com `"open"`, e isso é cicatriz — não simplifique de
25
+ // volta. O `ToggleEvent.newState` só tem esses dois valores, então as duas formas são idênticas
26
+ // em efeito; a diferença é no GATE. O check 15 trata qualquer string do fonte como possível nome
27
+ // de classe (de propósito: a versão estrita não vê `badge-${variant}` e quase fez o D1 apagar
28
+ // classes vivas). Com o literal `"open"` aqui, a `.drawer-wrap.open` do core — resto morto da
29
+ // página vanilla removida na Parte D — passava a parecer PRODUZIDA pela biblioteca, e a catraca
30
+ // queria travar um ganho falso.
31
+ function focoDaGaveta(e) {
32
+ const gaveta = e.currentTarget;
33
+ // Primeiro focável de DENTRO, não a gaveta: o WebKit não entra no conteúdo do popover pelo
34
+ // Tab nem com o foco no elemento dele — medido no K1.
35
+ if (e.newState !== "closed") {
36
+ const primeiro = gaveta.querySelector('a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])');
37
+ if (primeiro)
38
+ primeiro.focus();
39
+ else {
40
+ gaveta.tabIndex = -1;
41
+ gaveta.focus();
42
+ }
43
+ return;
44
+ }
45
+ // Só devolve o foco se ele ficou órfão; se a pessoa já o moveu, puxá-lo de volta seria roubo.
46
+ if (document.activeElement === document.body || gaveta.contains(document.activeElement))
47
+ document.querySelector(`[popovertarget="${gaveta.id}"]`)?.focus();
48
+ }
5
49
  export function Card({ variant = "base", className, ...props }) { return _jsx("div", { className: cx("card", variant !== "base" && `card-${variant}`, className), ...props }); }
6
50
  export function Stack({ className, ...props }) { return _jsx("div", { className: cx("stack", className), ...props }); }
7
51
  export function Cluster({ className, ...props }) { return _jsx("div", { className: cx("cluster", className), ...props }); }
@@ -10,13 +54,23 @@ export function Grid({ className, ...props }) { return _jsx("div", { className:
10
54
  // marca sai — no topo, não na lateral. `topbarVariant` escolhe a pele do topo; o shell
11
55
  // precisa saber porque a lateral se encaixa abaixo dele (o `flush` não tem folga em
12
56
  // cima, então ela sobe um --space-4). `pill` é cabeçalho de site — não é pra shell.
13
- // A lateral RECOLHE em tela estreita, e sem uma linha de JavaScript: o <aside> é um popover
14
- // nativo e o botão é o invoker. O navegador entrega Escape, clique fora, `aria-expanded` no
15
- // disparador e a volta do foco para ele ao fechar Baseline desde 04/2025, verificado em
16
- // 30/07/2026. Era o achado A8: em 375px o `h1` da página começava 4,6 telas abaixo, depois
17
- // dos 65 itens da navegação. Não-modal de propósito, como o CommandPaletteShell: o foco não
18
- // fica preso, e por isso não declaramos modalidade que não entregamos.
57
+ // A lateral RECOLHE em tela estreita: o <aside> é um popover nativo e o botão é o invoker. O
58
+ // navegador entrega Escape, clique fora e o `aria-expanded` no disparador. Era o achado A8: em
59
+ // 375px o `h1` da página começava 4,6 telas abaixo, depois dos 65 itens da navegação.
60
+ // Não-modal de propósito, como o CommandPaletteShell: o foco não fica preso, e por isso não
61
+ // declaramos modalidade que não entregamos.
62
+ //
63
+ // ATÉ 09/08/2026 ESTE COMENTÁRIO DIZIA "sem uma linha de JavaScript" E QUE O NAVEGADOR TAMBÉM
64
+ // ENTREGAVA A VOLTA DO FOCO. Entregava — no Chromium, que era o único motor onde a suíte
65
+ // rodava. O item K1 pôs Firefox e WebKit no `playwright.config.ts` e a medição derrubou a
66
+ // frase: no WebKit o foco vai para o <body> ao abrir, OITO Tabs não entram na gaveta e o
67
+ // Escape não devolve nada. Gaveta aberta e inalcançável por teclado é WCAG 2.1.1.
68
+ // A gestão de foco agora é NOSSA, igual nos três motores — `focoDaGaveta`, abaixo.
19
69
  // Id fixo em vez de useId: um documento tem UM AppShell (ele é dono do <main>), então não há
20
70
  // colisão possível — e o CSS e o gate precisam de um alvo estável.
71
+ // `sidebarCollapsed` é CONTROLADO pelo consumidor, como o `open` do CommandPaletteShell: o
72
+ // shell não decide quando recolher nem desenha o botão que recolhe — quem sabe se há espaço,
73
+ // e se a preferência se guarda, é a aplicação. O que o shell faz é o que só ele pode: a coluna
74
+ // do grid encolhe para --sidebar-rail (a regra `:has(.sidebar-collapsed)` no core).
21
75
  const SHELL_NAV_ID = "aurea-shell-nav";
22
- export function AppShell({ brand, navigation, topbar, topbarVariant = "floating", children, className, ...props }) { const s = useAureaStrings(); return _jsxs("div", { className: cx("app-shell", topbarVariant === "flush" && "app-shell-flush", className), ...props, children: [_jsx(Topbar, { variant: topbarVariant, brand: _jsxs(_Fragment, { children: [_jsx(IconButton, { className: "nav-toggle", icon: "menu", label: s.navigationToggle, popoverTarget: SHELL_NAV_ID }), brand] }), children: topbar }), _jsx(Sidebar, { id: SHELL_NAV_ID, popover: "auto", children: navigation }), _jsx("main", { className: "content", children: children })] }); }
76
+ export function AppShell({ brand, navigation, topbar, topbarVariant = "floating", sidebarCollapsed, children, className, ...props }) { const s = useAureaStrings(); return _jsxs("div", { className: cx("app-shell", topbarVariant === "flush" && "app-shell-flush", className), ...props, children: [_jsx(Topbar, { variant: topbarVariant, brand: _jsxs(_Fragment, { children: [_jsx(IconButton, { className: "nav-toggle", icon: "menu", label: s.navigationToggle, popoverTarget: SHELL_NAV_ID }), brand] }), children: topbar }), _jsx(Sidebar, { id: SHELL_NAV_ID, popover: "auto", collapsed: sidebarCollapsed, onToggle: focoDaGaveta, children: navigation }), _jsx("main", { className: "content", children: children })] }); }
package/dist/media.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
@@ -1,5 +1,17 @@
1
1
  import React, { type HTMLAttributes, type RefAttributes, type ReactNode, type ReactElement } from "react";
2
2
  import { type IconName } from "./system.js";
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;
3
15
  export declare function Breadcrumb({ items, label }: {
4
16
  items: Array<{
5
17
  label: ReactNode;
@@ -45,7 +57,21 @@ export declare function TreeView({ items, defaultExpandedIds, onSelect, label, c
45
57
  label?: string;
46
58
  className?: string;
47
59
  }): ReactElement<unknown, string | React.JSXElementConstructor<any>>;
48
- export declare function Sidebar({ children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement>): React.JSX.Element;
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;
49
75
  export type TopbarVariant = "floating" | "flush" | "pill";
50
76
  export declare function Topbar({ variant, brand, children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement> & {
51
77
  variant?: TopbarVariant;
@@ -1,4 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
4
5
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
@@ -9,6 +10,15 @@ import { Icon } from "./system.js";
9
10
  import { Button } from "./actions.js";
10
11
  import { Badge } from "./feedback.js";
11
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
+ }
12
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)) }); }
13
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))] }); }
14
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 })] }); }
@@ -98,12 +108,27 @@ export function TreeView({ items, defaultExpandedIds, onSelect, label, className
98
108
  }) }));
99
109
  return renderNodes(items, 1);
100
110
  }
101
- // Sidebar/Topbar: eram <aside>/<header> soltos DENTRO do AppShell; agora são componentes
102
- // nomeados que o AppShell COMPÕE (dogfooding, AUREA.md §2.4 "se o catálogo mostra uma
103
- // Sidebar, a sidebar dele É a Sidebar da Aurea"). O landmark vem do elemento nativo
104
- // (<aside>=complementary, <header>=banner); o <nav> de navegação é do consumidor, passado
105
- // como children por isso não embutimos <nav> aqui (aninharia landmark).
106
- export function Sidebar({ children, className, ...props }) { return _jsx("aside", { className: cx("sidebar", className), ...props, children: children }); }
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
+ }
107
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] }); }
108
133
  // Shell NÃO-modal de propósito: é presentational (o consumidor controla open) e não
109
134
  // tem focus trap/inert/Escape — declarar aria-modal sem entregar a modalidade faria
@@ -26,6 +26,11 @@ export declare function Popover({ trigger, title, children, side }: {
26
26
  children: ReactNode;
27
27
  side?: OverlaySide;
28
28
  }): React.JSX.Element;
29
+ export declare function HoverCard({ trigger, children, side }: {
30
+ trigger: ReactElement;
31
+ children: ReactNode;
32
+ side?: OverlaySide;
33
+ }): React.JSX.Element;
29
34
  export interface MenuItemDef {
30
35
  label: ReactNode;
31
36
  onClick?: () => void;
package/dist/overlays.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
3
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
@@ -8,29 +9,40 @@ import { Popover as BasePopover } from "@base-ui/react/popover";
8
9
  import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
9
10
  import { Menu as BaseMenu } from "@base-ui/react/menu";
10
11
  import { ContextMenu as BaseContextMenu } from "@base-ui/react/context-menu";
11
- import { cx, useAureaStrings } from "./internal.js";
12
+ import { PreviewCard as BasePreviewCard } from "@base-ui/react/preview-card";
13
+ import { cx, useAureaStrings, usePortalContainer } from "./internal.js";
12
14
  import { Icon } from "./system.js";
13
- export function Dialog({ open, title, children, footer, onClose }) { const s = useAureaStrings(); return _jsx(BaseDialog.Root, { open: open, onOpenChange: o => { if (!o)
14
- onClose(); }, children: _jsxs(BaseDialog.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 })] })] }) }); }
15
- export function Drawer({ open, title, children, onClose, side = "right" }) { const s = useAureaStrings(); return _jsx(BaseDialog.Root, { open: open, onOpenChange: o => { if (!o)
16
- onClose(); }, children: _jsxs(BaseDialog.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] })] }) }); }
15
+ 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
+ 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 })] })] }) }); }
17
+ 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
+ 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] })] }) }); }
17
19
  // `role="tooltip"` + `aria-describedby` no disparador é o que faz a dica EXISTIR para leitor
18
20
  // de tela. Medido em 30/07/2026: sem isso, o popup saía sem role e sem id, e o disparador sem
19
21
  // aria-describedby — quem navega por leitor de tela ouvia só o rótulo do botão e nunca o
20
22
  // conteúdo da dica. Era tooltip visual, não acessível (padrão APG Tooltip).
21
23
  // O id aponta para um elemento que só existe quando aberto; referência pendente é ignorada
22
24
  // pela tecnologia assistiva, então não precisa acompanhar o estado.
23
- export function Tooltip({ children, content, side = "top" }) { const id = React.useId(); return _jsxs(BaseTooltip.Root, { children: [_jsx(BaseTooltip.Trigger, { render: children, "aria-describedby": id }), _jsx(BaseTooltip.Portal, { children: _jsx(BaseTooltip.Positioner, { side: side, sideOffset: 8, children: _jsx(BaseTooltip.Popup, { id: id, role: "tooltip", className: "tooltip", children: content }) }) })] }); }
24
- export function Popover({ trigger, title, children, side = "bottom" }) { return _jsxs(BasePopover.Root, { children: [_jsx(BasePopover.Trigger, { render: trigger }), _jsx(BasePopover.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] }) }) })] }); }
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 }) }) })] }); }
26
+ 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
+ // HoverCard (Lote 1 do BUILDING.md). Não é Tooltip nem Popover, e a diferença é de propósito,
28
+ // não de aparência: a Tooltip é um RÓTULO curto (`role="tooltip"`, some ao mover o mouse); o
29
+ // Popover abre por CLIQUE e pode conter foco; este é uma PRÉVIA rica que aparece ao repousar o
30
+ // ponteiro sobre um link e cujo conteúdo é alcançável — o cartão de perfil ao passar sobre um
31
+ // nome. As três referências que o têm chamam de hover-card ou preview-card e concordam nisso.
32
+ // Superfície reusa `.popover` de propósito: é a mesma camada flutuante do sistema, e dar a ela
33
+ // um segundo nome criaria duas peles para a mesma coisa.
34
+ // Por depender de repouso do ponteiro, NÃO serve para informação essencial — quem navega só por
35
+ // teclado ou toque não abre um hover card. Conteúdo obrigatório vai em Popover.
36
+ export function HoverCard({ trigger, children, side = "bottom" }) { const portal = usePortalContainer(); return _jsxs(BasePreviewCard.Root, { children: [_jsx(BasePreviewCard.Trigger, { render: trigger }), _jsx(BasePreviewCard.Portal, { container: portal, children: _jsx(BasePreviewCard.Positioner, { side: side, sideOffset: 8, children: _jsx(BasePreviewCard.Popup, { className: "popover hover-card", children: children }) }) })] }); }
25
37
  // ContextMenu.Item/.Separator/.Popup são os MESMOS componentes de Menu.* no Base UI,
26
38
  // então os itens renderizam igual nos dois menus.
27
39
  const renderMenuItems = (items) => items.map((it, i) => it === "separator"
28
40
  ? _jsx(BaseMenu.Separator, { className: "menu-sep" }, i)
29
41
  : _jsxs(BaseMenu.Item, { className: "menu-item", disabled: it.disabled, onClick: it.onClick, children: [it.leadingIcon && _jsx(Icon, { name: it.leadingIcon }), it.label] }, i));
30
- export function DropdownMenu({ trigger, items, side = "bottom", label }) { return _jsxs(BaseMenu.Root, { children: [_jsx(BaseMenu.Trigger, { render: trigger }), _jsx(BaseMenu.Portal, { children: _jsx(BaseMenu.Positioner, { side: side, sideOffset: 6, children: _jsx(BaseMenu.Popup, { className: "menu", "aria-label": label, children: renderMenuItems(items) }) }) })] }); }
42
+ export function DropdownMenu({ trigger, items, side = "bottom", label }) { const portal = usePortalContainer(); return _jsxs(BaseMenu.Root, { children: [_jsx(BaseMenu.Trigger, { render: trigger }), _jsx(BaseMenu.Portal, { container: portal, children: _jsx(BaseMenu.Positioner, { side: side, sideOffset: 6, children: _jsx(BaseMenu.Popup, { className: "menu", "aria-label": label, children: renderMenuItems(items) }) }) })] }); }
31
43
  // ContextMenu: abre no botão direito e — por teclado — em Shift+F10 / tecla Menu,
32
44
  // que o browser só dispara (como evento contextmenu) sobre um elemento FOCADO. Por
33
45
  // isso o gatilho é focável (tabIndex 0) e tem nome acessível (auditoria 18/07/2026,
34
46
  // MÉDIO 3); sem isso o teclado não alcança o menu. Consumidor pode sobrescrever
35
47
  // tabIndex via children se o próprio já for focável.
36
- export function ContextMenu({ children, items, label, className }) { return _jsxs(BaseContextMenu.Root, { children: [_jsx(BaseContextMenu.Trigger, { className: className, tabIndex: 0, "aria-label": label, "aria-haspopup": "menu", children: children }), _jsx(BaseContextMenu.Portal, { children: _jsx(BaseContextMenu.Positioner, { children: _jsx(BaseContextMenu.Popup, { className: "menu", "aria-label": label, children: renderMenuItems(items) }) }) })] }); }
48
+ export function ContextMenu({ children, items, label, className }) { const portal = usePortalContainer(); return _jsxs(BaseContextMenu.Root, { children: [_jsx(BaseContextMenu.Trigger, { className: className, tabIndex: 0, "aria-label": label, "aria-haspopup": "menu", children: children }), _jsx(BaseContextMenu.Portal, { container: portal, children: _jsx(BaseContextMenu.Positioner, { children: _jsx(BaseContextMenu.Popup, { className: "menu", "aria-label": label, children: renderMenuItems(items) }) }) })] }); }