@aurea-uds/react 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +34 -2
  2. package/dist/actions.d.ts +10 -3
  3. package/dist/actions.js +55 -6
  4. package/dist/code-client.d.ts +6 -0
  5. package/dist/code-client.js +24 -0
  6. package/dist/code.d.ts +2 -13
  7. package/dist/code.js +5 -34
  8. package/dist/data-display-client.d.ts +14 -0
  9. package/dist/data-display-client.js +22 -0
  10. package/dist/data-display.d.ts +2 -23
  11. package/dist/data-display.js +5 -14
  12. package/dist/feedback-client.d.ts +64 -0
  13. package/dist/feedback-client.js +110 -0
  14. package/dist/feedback.d.ts +2 -63
  15. package/dist/feedback.js +5 -101
  16. package/dist/file-input.js +57 -9
  17. package/dist/identity-client.d.ts +9 -0
  18. package/dist/identity-client.js +12 -0
  19. package/dist/identity.d.ts +2 -17
  20. package/dist/identity.js +5 -25
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.js +19 -3
  23. package/dist/inputs-client.d.ts +105 -0
  24. package/dist/inputs-client.js +264 -0
  25. package/dist/inputs.d.ts +2 -91
  26. package/dist/inputs.js +9 -126
  27. package/dist/internal.d.ts +20 -5
  28. package/dist/internal.js +91 -10
  29. package/dist/layout-client.d.ts +9 -0
  30. package/dist/layout-client.js +72 -0
  31. package/dist/layout.d.ts +2 -15
  32. package/dist/layout.js +5 -76
  33. package/dist/markup.d.ts +72 -0
  34. package/dist/markup.js +87 -0
  35. package/dist/media-client.d.ts +36 -0
  36. package/dist/media-client.js +239 -0
  37. package/dist/media.d.ts +2 -9
  38. package/dist/media.js +5 -99
  39. package/dist/navigation-client.d.ts +94 -0
  40. package/dist/navigation-client.js +154 -0
  41. package/dist/navigation.d.ts +2 -85
  42. package/dist/navigation.js +5 -138
  43. package/dist/overlays.d.ts +23 -0
  44. package/dist/overlays.js +45 -1
  45. package/dist/pure.d.ts +30 -0
  46. package/dist/pure.js +26 -2
  47. package/dist/system.d.ts +9 -0
  48. package/dist/system.js +46 -0
  49. package/package.json +180 -176
package/dist/feedback.js CHANGED
@@ -1,101 +1,5 @@
1
- "use client";
2
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
4
- // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
5
- // internal system actions feedback → inputs → navigation → layout → data-display → resto.
6
- import React from "react";
7
- import { Popover as BasePopover } from "@base-ui/react/popover";
8
- import { cx, useAureaStrings, usePortalContainer, stateSeverity } from "./internal.js";
9
- import { Icon } from "./system.js";
10
- import { Button, IconButton } from "./actions.js";
11
- export function Badge({ variant = "neutral", className, ...props }) { return _jsx("span", { className: cx("badge", variant !== "neutral" && `badge-${variant}`, className), ...props }); }
12
- // Status (DIRECTION §3.6): condição OPERACIONAL — ponto + rótulo. Não é Badge: Badge é
13
- // metadado curto num pill; Status diz em que estado a coisa está. Reusa o .status-dot que
14
- // já existia solto. A variante colore só o PONTO (currentColor); o rótulo fica legível em
15
- // --foreground. Cor não é o único sinal — quem diz o estado é o texto (WCAG 1.4.1); o
16
- // ponto é decorativo e sai do leitor de tela. ponytail: rótulo é do consumidor (sem i18n
17
- // nova) — a variante é só a cor.
18
- // `state` (Parte J) é EIXO À PARTE de `variant`, e os dois convivem: a variante é a cor do
19
- // ponto, o estado é a condição universal. Quem passa `state` e não passa `variant` recebe a
20
- // cor derivada — `offline` fica com o ponto vazado que ele já tinha desde sempre, os outros
21
- // seis caem na gravidade. Quem passa os dois manda, porque só o consumidor sabe se aquele
22
- // "esperando" dele é grave. E o rótulo é o do consumidor, como sempre foi: a string universal
23
- // entra só quando não há filho, para o componente não passar a inventar texto.
24
- export function Status({ variant, state, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? (state === "offline" ? "offline" : stateSeverity(state)) : "neutral"); return _jsxs("span", { className: cx("status", v !== "neutral" && `status-${v}`, className), "data-state": state, ...props, children: [_jsx("i", { className: "status-dot", "aria-hidden": "true" }), _jsx("span", { className: "status-label", children: children ?? (state ? s.universalState[state] : null) })] }); }
25
- export function Alert({ variant, state, title, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("alert", `alert-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }); }
26
- export function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("banner", `banner-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [icon ? _jsx(Icon, { name: icon }) : _jsx("span", {}), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
27
- // A barra grampeava 0..100 e o `aria-valuenow` NÃO — medido ao publicar o contrato de API na
28
- // Parte E: com value=150 o desenho parava em 100% e o leitor de tela anunciava "150 de 100".
29
- // A causa é a de sempre: o grampo existia num lugar só. Agora é UMA expressão que serve os dois,
30
- // então não há como divergirem de novo.
31
- // SÓ DETERMINADA, de propósito: não há modo indeterminado nem `.progress` para ele no core. A
32
- // ficha dizia que havia e era falso — corrigido junto, porque contrato que promete o que não
33
- // existe custa mais que ausência.
34
- export function Progress({ value, label }) { const pct = Math.max(0, Math.min(100, value)); return _jsx("div", { children: _jsx("div", { className: "progress", role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": pct, children: _jsx("span", { style: { width: `${pct}%` } }) }) }); }
35
- export function Skeleton({ className, ...props }) { return _jsx("div", { className: cx("skeleton", className), "aria-hidden": "true", ...props }); }
36
- // Spinner (Lote 1 do BUILDING.md). O glifo `.spinner` existia no core desde sempre e era usado
37
- // SÓ por dentro do Button — nunca teve componente, então nunca teve papel acessível: quem
38
- // esperava carregando não ouvia nada. As três referências que o têm concordam em `role="status"`
39
- // com rótulo, e é o que faltava.
40
- // Sem escolha de "estilo" de spinner (ponto, barra, pinwheel): é UM glifo, e um sistema com
41
- // cinco spinners é um sistema sem spinner.
42
- // `decorative` existe para o caso em que ele vive DENTRO de um controle que já anuncia o estado
43
- // (o `Button loading` faz isso com aria-busy) — dois anúncios para o mesmo fato é ruído.
44
- // O default é `sm` e não `md` de propósito: o lugar natural do spinner é DENTRO de um controle,
45
- // ao lado de texto, e é o tamanho que ele sempre teve aqui. Um default maior mudaria a
46
- // aparência de todo botão em carregamento sem ninguém ter pedido.
47
- export function Spinner({ size = "sm", label, decorative, className, ...props }) { const s = useAureaStrings(); return _jsx("span", { className: cx("spinner", size !== "sm" && `spinner-${size}`, className), ...(decorative ? { "aria-hidden": true } : { role: "status", "aria-label": label ?? s.loading }), ...props }); }
48
- // titleAs: o nível do título é do DOCUMENTO, não do componente. Fixo em h3, um empty state
49
- // no alto de uma página vira h1→h3 e a hierarquia quebra (axe heading-order). Default h3
50
- // para não mexer em quem já consome; quem sabe o contexto passa o nível certo.
51
- // `state` aqui NÃO deriva cor nenhuma — o vazio já é neutro e um empty state colorido seria
52
- // alarme onde há ausência. O que ele faz é o marcador no DOM e a descrição padrão, para
53
- // "sem conexão" e "resultado parcial" não serem sete frases diferentes em sete telas.
54
- export function EmptyState({ icon = "document--blank", title, titleAs: TitleTag = "h3", description, action, state }) { const s = useAureaStrings(); const desc = description ?? (state ? s.universalState[state] : null); return _jsxs("div", { className: "empty-state", "data-state": state, children: [_jsx(Icon, { name: icon, size: "xl" }), _jsx(TitleTag, { className: "empty-title", children: title }), desc && _jsx("p", { className: "muted", children: desc }), action] }); }
55
- function groupNotifications(items) {
56
- const out = [];
57
- for (const it of items) {
58
- const last = out[out.length - 1];
59
- if (last && last.label === it.group)
60
- last.items.push(it);
61
- else
62
- out.push({ label: it.group, items: [it] });
63
- }
64
- return out;
65
- }
66
- export function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon = "notification", side = "bottom" }) {
67
- const s = useAureaStrings();
68
- const portal = usePortalContainer();
69
- const title = label ?? s.notificationsLabel;
70
- const unread = items.filter(i => !i.read).length;
71
- const groups = groupNotifications(items);
72
- const baseId = React.useId();
73
- const seen = React.useRef(undefined);
74
- const [announce, setAnnounce] = React.useState("");
75
- React.useEffect(() => {
76
- const ids = new Set(items.map(i => i.id));
77
- if (seen.current === undefined) {
78
- seen.current = ids;
79
- return;
80
- }
81
- const fresh = items.filter(i => !seen.current.has(i.id)).length;
82
- seen.current = ids;
83
- // Texto idêntico duas vezes seguidas não muta o DOM e o leitor silencia a 2ª
84
- // chegada (auditoria 18/07/2026, MÉDIO 4). Um NBSP alternado no fim força a
85
- // mutação sem mudar o que se ouve.
86
- if (fresh)
87
- setAnnounce(prev => { const text = `${fresh} ${s.notificationNew}`; return prev === text ? text + " " : text; });
88
- }, [items, s.notificationNew]);
89
- const renderRow = (it) => {
90
- const body = _jsxs(_Fragment, { children: [_jsx("span", { className: "notification-dot", "aria-hidden": "true" }), _jsxs("span", { className: "notification-item-title", children: [it.icon && _jsx(Icon, { name: it.icon, size: "sm" }), !it.read && _jsxs("span", { className: "sr-only", children: [s.notificationUnread, " "] }), it.title] }), it.time && _jsx("span", { className: "notification-time", children: it.time }), it.description && _jsx("span", { className: "notification-item-desc", children: it.description })] });
91
- return it.onClick || onItemClick
92
- ? _jsx("button", { type: "button", className: "notification-item", "data-read": it.read || undefined, onClick: () => { it.onClick?.(); onItemClick?.(it); }, children: body })
93
- : _jsx("div", { className: "notification-item", "data-read": it.read || undefined, children: body });
94
- };
95
- return _jsxs(BasePopover.Root, { children: [_jsxs("span", { className: "notification-trigger", children: [_jsx(BasePopover.Trigger, { render: _jsx(IconButton, { variant: "ghost", icon: icon, label: unread ? `${title} (${unread})` : title }) }), unread > 0 && _jsx("span", { className: "notification-count", "aria-hidden": "true", children: unread > 99 ? "99+" : unread })] }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { side: side, sideOffset: 8, children: _jsxs(BasePopover.Popup, { className: "popover notification-panel", "aria-label": title, children: [_jsxs("div", { className: "notification-head", children: [_jsx(BasePopover.Title, { render: _jsx("strong", {}), children: title }), unread > 0 && onMarkAllRead && _jsx(Button, { variant: "ghost", size: "sm", onClick: onMarkAllRead, children: s.notificationMarkAll })] }), items.length
96
- ? _jsx("div", { className: "notification-list", children: groups.map((g, gi) => {
97
- const gid = baseId + gi;
98
- return _jsxs(React.Fragment, { children: [g.label && _jsx("p", { className: "notification-group-label", id: gid, children: g.label }), _jsx("ul", { className: "notification-sublist", "aria-labelledby": g.label ? gid : undefined, children: g.items.map(it => _jsx("li", { children: renderRow(it) }, it.id)) })] }, gi);
99
- }) })
100
- : _jsx("p", { className: "notification-empty", children: s.notificationEmpty })] }) }) }), _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: announce })] });
101
- }
1
+ // VITRINE da categoria — é este arquivo que `@aurea-uds/react/feedback` 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 "./feedback-client.js";
5
+ export { Badge, Progress, Skeleton } from "./markup.js";
@@ -42,6 +42,21 @@ async function somaSha256(file) {
42
42
  return undefined;
43
43
  }
44
44
  }
45
+ // AUD-0006 (12/08/2026): um item restaurado NÃO tem os bytes — a plataforma não devolve `File` de
46
+ // um armazenamento —, então ele não está subindo e não vai subir. Persistido como
47
+ // `pending`/`uploading`/`paused`, ele mostrava Pausar/Retomar/Cancelar INERTES (não há controller
48
+ // em memória para abortar, e `retomar` exige `e.file`) e contava como "em voo", o que travava o
49
+ // recibo para sempre: `emVoo` nunca zerava.
50
+ //
51
+ // `canceled` é o único terminal HONESTO aqui. A transferência foi de fato abortada — pelo fim da
52
+ // página —, e não houve falha de servidor que justificasse `error`. `pending` seria mentira dupla:
53
+ // não está esperando nada e continuaria contando como em voo.
54
+ //
55
+ // E o ganho é que a marcação certa sai de graça: o ramo de `canceled` já esconde o botão de
56
+ // repetir quando não há `File` (era uma decisão do item G1) e já oferece só a remoção. Zero string
57
+ // nova, zero branch novo.
58
+ const ATIVOS = new Set(["pending", "uploading", "paused"]);
59
+ const restaurar = (q) => ({ ...q, restored: true, status: ATIVOS.has(q.status) ? "canceled" : q.status });
45
60
  function formatSize(bytes) {
46
61
  if (bytes < 1024)
47
62
  return `${bytes} B`;
@@ -60,12 +75,27 @@ export function FileInput({ accept, maxSize, multiple, onFilesChange, upload, la
60
75
  // G1: a fila restaurada entra como estado INICIAL, não como prop controlada — e a
61
76
  // palavra "inicial" é literal: um item restaurado não tem `File`, então controlá-lo
62
77
  // de fora seria prometer um round-trip que a plataforma não faz.
78
+ // AUD-0002 (12/08/2026): o contador SEMPRE começava em zero, e os ids restaurados vêm do
79
+ // consumidor — inclusive `f0`, que é exatamente o que ele gera. Restaurar `f0` e escolher um
80
+ // arquivo novo dava `id: "f0"` aos DOIS: remover um removia os dois (`filter` por id), `patch`
81
+ // atualizava os dois, e o React avisava chave duplicada. Perda de registro no fluxo oficial de
82
+ // persistência.
83
+ // O conjunto dos ids em uso faz o gerador PULAR, e por ser conjunto ele vale para id fora do
84
+ // padrão também — semear o contador com "o maior sufixo numérico" só resolveria `f<N>`.
63
85
  const nextId = React.useRef(0);
64
- const [files, setFiles] = React.useState(() => (initialQueue ?? []).map(q => ({ ...q, restored: true })));
86
+ const usados = React.useRef(null);
87
+ if (!usados.current)
88
+ usados.current = new Set((initialQueue ?? []).map(q => q.id));
89
+ const novoId = () => { let novo; do {
90
+ novo = `f${nextId.current++}`;
91
+ } while (usados.current.has(novo)); usados.current.add(novo); return novo; };
92
+ const [files, setFiles] = React.useState(() => (initialQueue ?? []).map(restaurar));
65
93
  const [rejected, setRejected] = React.useState([]);
66
94
  // a soma chega DEPOIS do envio começar (é assíncrona), então a comparação lê a
67
95
  // lista atual por ref em vez da closure do início do upload.
68
- const filesRef = React.useRef([]);
96
+ // semeada com a MESMA lista inicial do estado, e não com `[]`: com fila restaurada as duas
97
+ // divergiam até a primeira emissão, e é desta ref que o AUD-0009 abaixo lê a lista anterior.
98
+ const filesRef = React.useRef(files);
69
99
  // E guarda a PROMESSA, não só o valor: um arquivo pequeno com envio rápido termina
70
100
  // antes de o hash sair, e comparar contra `undefined` pularia a conferência em
71
101
  // silêncio — que é pior que não conferir, porque parece que conferiu. Quem achou
@@ -90,7 +120,25 @@ export function FileInput({ accept, maxSize, multiple, onFilesChange, upload, la
90
120
  // consumidor guarda; onde ele guarda — armazenamento local, servidor, nada — é
91
121
  // decisão dele, a mesma linha do estado da grade na URL (PLANO-1.0 F4).
92
122
  const paraFila = (l) => l.map(({ id, name, bytes, status, progress, restored, checksum: soma, finishedAt }) => ({ id, name, bytes, status, progress, ...(soma ? { checksum: soma } : {}), ...(finishedAt ? { finishedAt } : {}), ...(restored ? { restored: true } : {}) }));
93
- const emit = (next) => { filesRef.current = next; setFiles(next); onFilesChange?.(next.flatMap(f => f.file ? [f.file] : [])); onQueueChange?.(paraFila(next)); };
123
+ // AUD-0009 (12/08/2026): a lista PERDE itens por dois caminhos a troca em modo single e o
124
+ // "substituir" do conflito —, e a URL de objeto da prévia deles ficava viva até o desmonte. O
125
+ // navegador segura o blob inteiro nesse tempo, então trocar a mesma foto dez vezes acumulava dez
126
+ // imagens em memória com uma na tela.
127
+ // Revogado AQUI, num lugar só, porque `emit` é por onde toda substituição passa. Corrigir em
128
+ // cada chamador é exatamente o que a regra "quem MAIS tem esse problema?" existe para impedir —
129
+ // e por isso o `remove` deixou de revogar por conta própria: ele passa por aqui.
130
+ const emit = (next) => {
131
+ const idsNovos = new Set(next.map(e => e.id));
132
+ for (const e of filesRef.current)
133
+ if (e.preview && !idsNovos.has(e.id)) {
134
+ URL.revokeObjectURL(e.preview);
135
+ previas.current.delete(e.preview);
136
+ }
137
+ filesRef.current = next;
138
+ setFiles(next);
139
+ onFilesChange?.(next.flatMap(f => f.file ? [f.file] : []));
140
+ onQueueChange?.(paraFila(next));
141
+ };
94
142
  const patch = (fid, p) => setFiles(prev => { const next = prev.map(e => e.id === fid ? { ...e, ...p } : e); filesRef.current = next; onQueueChange?.(paraFila(next)); return next; });
95
143
  const abortId = (fid) => controllers.current.get(fid)?.abort();
96
144
  // Cada arquivo envia por conta própria (promessa independente + catch por arquivo):
@@ -149,7 +197,7 @@ export function FileInput({ accept, maxSize, multiple, onFilesChange, upload, la
149
197
  novosConflitos.push({ file, existente: jaTem.id });
150
198
  continue;
151
199
  }
152
- ok.push({ id: `f${nextId.current++}`, file, name: file.name, bytes: file.size, status: "pending", progress: 0 });
200
+ ok.push({ id: novoId(), file, name: file.name, bytes: file.size, status: "pending", progress: 0 });
153
201
  }
154
202
  if (novosConflitos.length)
155
203
  setConflitos(c => [...c, ...novosConflitos]);
@@ -192,7 +240,7 @@ export function FileInput({ accept, maxSize, multiple, onFilesChange, upload, la
192
240
  // ainda está em `files` quando esta função roda — o React não re-renderizou — e ler
193
241
  // a closure deixava os DOIS na fila. Foi o teste que achou.
194
242
  const aceitarConflito = (file, base = files) => {
195
- const e = { id: `f${nextId.current++}`, file, name: file.name, bytes: file.size, status: "pending", progress: 0 };
243
+ const e = { id: novoId(), file, name: file.name, bytes: file.size, status: "pending", progress: 0 };
196
244
  emit([...base, e]);
197
245
  if (preview && ehImagem(file)) {
198
246
  const u = URL.createObjectURL(file);
@@ -216,10 +264,10 @@ export function FileInput({ accept, maxSize, multiple, onFilesChange, upload, la
216
264
  }
217
265
  aceitarConflito(c.file);
218
266
  };
219
- const remove = (rid) => { const f = files.find(x => x.id === rid); if (f?.preview) {
220
- URL.revokeObjectURL(f.preview);
221
- previas.current.delete(f.preview);
222
- } abortId(rid); emit(files.filter(x => x.id !== rid)); setAnnounce(f ? `${s.fileRemoved}: ${f.name}` : ""); };
267
+ // a revogação da prévia saiu daqui: quem revoga é o `emit`, para todo item que a lista perder.
268
+ // Duplicar a regra em cada chamador é como o AUD-0009 nasceu — o `remove` revogava e os outros
269
+ // dois caminhos de substituição, não.
270
+ const remove = (rid) => { const f = files.find(x => x.id === rid); abortId(rid); emit(files.filter(x => x.id !== rid)); setAnnounce(f ? `${s.fileRemoved}: ${f.name}` : ""); };
223
271
  // G6: "terminou" é não haver mais nada em voo nem esperando — pausado conta como em
224
272
  // voo, porque a pessoa ainda vai retomar.
225
273
  const emVoo = files.some(f => f.status === "uploading" || f.status === "paused" || f.status === "pending");
@@ -0,0 +1,9 @@
1
+ import React, { type ReactNode } from "react";
2
+ import type { AvatarSize } from "./markup.js";
3
+ export declare function Avatar({ src, alt, fallback, size, className }: {
4
+ src?: string;
5
+ alt?: string;
6
+ fallback?: ReactNode;
7
+ size?: AvatarSize;
8
+ className?: string;
9
+ }): React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx } 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 { cx } from "./internal.js";
8
+ export function Avatar({ src, alt = "", fallback, size = "md", className }) {
9
+ const [falhou, setFalhou] = React.useState(false);
10
+ React.useEffect(() => { setFalhou(false); }, [src]);
11
+ return _jsx("span", { className: cx("avatar", size !== "md" && `avatar-${size}`, className), children: src && !falhou ? _jsx("img", { src: src, alt: alt, onError: () => setFalhou(true) }) : fallback });
12
+ }
@@ -1,17 +1,2 @@
1
- import React, { type ReactNode } from "react";
2
- export type AvatarSize = "sm" | "md" | "lg";
3
- export declare function Avatar({ src, alt, fallback, size, className }: {
4
- src?: string;
5
- alt?: string;
6
- fallback?: ReactNode;
7
- size?: AvatarSize;
8
- className?: string;
9
- }): React.JSX.Element;
10
- export declare function AvatarGroup({ children, max, total, label, size, className }: {
11
- children: ReactNode;
12
- max?: number;
13
- total?: number;
14
- label?: string;
15
- size?: AvatarSize;
16
- className?: string;
17
- }): React.JSX.Element;
1
+ export * from "./identity-client.js";
2
+ export { AvatarGroup, type AvatarSize } from "./markup.js";
package/dist/identity.js CHANGED
@@ -1,25 +1,5 @@
1
- "use client";
2
- import { 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 { cx } from "./internal.js";
8
- export function Avatar({ src, alt = "", fallback, size = "md", className }) {
9
- const [falhou, setFalhou] = React.useState(false);
10
- React.useEffect(() => { setFalhou(false); }, [src]);
11
- return _jsx("span", { className: cx("avatar", size !== "md" && `avatar-${size}`, className), children: src && !falhou ? _jsx("img", { src: src, alt: alt, onError: () => setFalhou(true) }) : fallback });
12
- }
13
- // AvatarGroup: a pilha sobreposta com o "+N" no fim. A sobreposição já existia no core desde
14
- // sempre (`.avatar-group .avatar`), e o container nunca teve regra — a peça filha ficou, a mãe
15
- // não, exatamente como no Stepper.
16
- // `max` corta a lista e o excedente vira UM avatar de contagem, que é onde as referências
17
- // convergem. Fora de propósito: o `renderSurplus` de uma delas (callback para desenhar o "+N"),
18
- // que é configuração para um problema que ninguém teve ainda.
19
- // `role="group"` + nome: uma fila de retratos sem nome não diz de quem é.
20
- export function AvatarGroup({ children, max, total, label, size = "md", className }) {
21
- const itens = React.Children.toArray(children);
22
- const mostra = max ? itens.slice(0, max) : itens;
23
- const resto = (total ?? itens.length) - mostra.length;
24
- return _jsxs("div", { role: "group", "aria-label": label, className: cx("avatar-group", className), children: [mostra, resto > 0 && _jsxs("span", { className: cx("avatar", "avatar-count", size !== "md" && `avatar-${size}`), "aria-hidden": "true", children: ["+", resto] })] });
25
- }
1
+ // VITRINE da categoria — é este arquivo que `@aurea-uds/react/identity` 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 "./identity-client.js";
5
+ export { AvatarGroup } from "./markup.js";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStateFromParams, universalStates, stateSeverity, type AureaStrings, type GridState, type UniversalState } from "./pure.js";
1
+ export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStateFromParams, screenStateToParams, screenStateFromParams, universalStates, stateSeverity, type AureaStrings, type GridState, type ScreenState, type UniversalState } from "./pure.js";
2
2
  export { useAureaStrings, useSpriteUrl } from "./internal.js";
3
+ export { Card, Stack, Cluster, Grid, KPI, DataList, Timeline, Prose, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Topbar, Kbd, Select, Textarea, Checkbox, Radio, Switch, Range, type BadgeVariant, type AvatarSize, type TopbarVariant } from "./markup.js";
3
4
  export * from "./system.js";
4
5
  export * from "./actions.js";
5
6
  export * from "./feedback.js";
package/dist/index.js CHANGED
@@ -8,11 +8,14 @@
8
8
  // porta de sempre: `import {Button} from "@aurea-uds/react"` continua funcionando e traz tudo
9
9
  // que não custa dependência.
10
10
  //
11
- // TRÊS COMPONENTES NÃO ENTRAM AQUI, e é o ponto da fase:
11
+ // SEIS MÓDULOS DE MOTOR OPCIONAL NÃO ENTRAM AQUI:
12
12
  // CodeEditor → "@aurea-uds/react/code-editor" (CodeMirror, 6 peers opcionais)
13
13
  // DataGrid → "@aurea-uds/react/data-grid" (@tanstack/react-table)
14
14
  // QRCode → "@aurea-uds/react/qrcode" (qr)
15
- // Se estivessem neste barril, importar um Button carregaria os três — e a dependência opcional
15
+ // Calendar → "@aurea-uds/react/calendar" (react-day-picker)
16
+ // Chart → "@aurea-uds/react/chart" (recharts)
17
+ // DependencyGraph → "@aurea-uds/react/graph" (@xyflow/react)
18
+ // Se estivessem neste barril, importar um Button carregaria um peer opcional — e a dependência
16
19
  // que não estivesse instalada quebraria a importação. O peso é real, então a fronteira é real.
17
20
  // SEM `"use client"`, e é o ponto da Parte A: este barril é um módulo de SERVIDOR. Reexportar
18
21
  // componente de um módulo com a diretiva é o caminho suportado — quem importa daqui de um
@@ -25,8 +28,21 @@
25
28
  // DataGrid, de propósito: quem lê a URL num framework de RSC é o SERVIDOR, e o
26
29
  // subpath tem a diretiva. Vindo daqui — via `pure.js`, que não tem — a função é
27
30
  // chamável dos dois lados. É a mesma razão do `cx`, uma camada acima.
28
- export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStateFromParams, universalStates, stateSeverity } from "./pure.js";
31
+ export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStateFromParams, screenStateToParams, screenStateFromParams, universalStates, stateSeverity } from "./pure.js";
29
32
  export { useAureaStrings, useSpriteUrl } from "./internal.js";
33
+ // ── A LINHA DO ITEM O1, e ela é o item inteiro ───────────────────────────────────────────────
34
+ // Estes 22 vêm do `markup.js`, que NÃO tem a diretiva, e por isso chegam como componentes de
35
+ // SERVIDOR. Vinham dos módulos de categoria abaixo, e de lá chegavam como cliente: `Card` é uma
36
+ // <div>, e importá-lo embarcava 9 módulos e 118,5 KB — `node scripts/measure-boundary.mjs`.
37
+ //
38
+ // ESTA LINHA PRECISA SER EXPLÍCITA, e o que acontece sem ela foi MEDIDO, não deduzido — a
39
+ // primeira versão deste comentário dizia outra coisa e a prova contra o defeito a derrubou.
40
+ // Tirando um nome daqui: o `tsc` fica VERDE, o componente continua existindo e os testes
41
+ // continuam passando, porque o módulo de categoria abaixo o reexporta (é o que mantém o subpath
42
+ // inteiro). O que muda é de ONDE ele vem — do módulo com a diretiva —, e aí ele volta a chegar
43
+ // como referência de CLIENTE e os 118,5 KB voltam com ele. Não é um erro: é o ganho do item O1
44
+ // se desfazendo em silêncio. É isso, exatamente isso, que o check 35 pega.
45
+ export { Card, Stack, Cluster, Grid, KPI, DataList, Timeline, Prose, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Topbar, Kbd, Select, Textarea, Checkbox, Radio, Switch, Range } from "./markup.js";
30
46
  export * from "./system.js";
31
47
  export * from "./actions.js";
32
48
  export * from "./feedback.js";
@@ -0,0 +1,105 @@
1
+ import React, { type HTMLAttributes, type InputHTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
+ import { type IconName } from "./system.js";
3
+ export { FileInput, matchesAccept, type FileRejection, type UploadContext, type UploadFn } from "./file-input.js";
4
+ export interface FieldProps extends HTMLAttributes<HTMLDivElement>, RefAttributes<HTMLDivElement> {
5
+ label: string;
6
+ hint?: ReactNode;
7
+ error?: ReactNode;
8
+ name?: string;
9
+ }
10
+ export declare function Field({ label, hint, error, children, className, id, name, ...props }: FieldProps): React.JSX.Element;
11
+ export declare function Form({ errors, onSubmit, children, className, ...props }: Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> & RefAttributes<HTMLFormElement> & {
12
+ errors?: Record<string, string | string[]>;
13
+ onSubmit?: (values: Record<string, unknown>) => void;
14
+ }): React.JSX.Element;
15
+ export interface InputProps extends InputHTMLAttributes<HTMLInputElement>, RefAttributes<HTMLInputElement> {
16
+ formatOnBlur?: (value: string) => string;
17
+ }
18
+ export declare const Input: React.ForwardRefExoticComponent<Omit<InputProps, "ref"> & RefAttributes<HTMLInputElement>>;
19
+ export declare function SearchField({ icon, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
20
+ icon?: IconName;
21
+ }): React.JSX.Element;
22
+ export declare function NumberField({ value, defaultValue, onValueChange, min, max, step, disabled, readOnly, required, label, id, name, format, locale, className }: {
23
+ value?: number | null;
24
+ defaultValue?: number;
25
+ onValueChange?: (v: number | null) => void;
26
+ min?: number;
27
+ max?: number;
28
+ step?: number;
29
+ disabled?: boolean;
30
+ readOnly?: boolean;
31
+ required?: boolean;
32
+ label?: string;
33
+ id?: string;
34
+ name?: string;
35
+ format?: Intl.NumberFormatOptions;
36
+ locale?: string;
37
+ className?: string;
38
+ }): React.JSX.Element;
39
+ export declare function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className }: {
40
+ length: number;
41
+ value?: string;
42
+ defaultValue?: string;
43
+ onValueChange?: (v: string) => void;
44
+ mask?: boolean;
45
+ disabled?: boolean;
46
+ required?: boolean;
47
+ label?: string;
48
+ id?: string;
49
+ className?: string;
50
+ }): React.JSX.Element;
51
+ export declare function SegmentedControl({ items, value, onChange, label }: {
52
+ items: Array<{
53
+ value: string;
54
+ label: ReactNode;
55
+ }>;
56
+ value: string;
57
+ onChange: (v: string) => void;
58
+ label?: string;
59
+ }): React.JSX.Element;
60
+ export interface ComboboxOption {
61
+ value: string;
62
+ label: string;
63
+ }
64
+ export declare function Combobox({ items, value, onValueChange, placeholder, label, disabled, id, className, "aria-describedby": descrito, "aria-invalid": invalido }: {
65
+ items: ComboboxOption[];
66
+ value?: ComboboxOption | null;
67
+ onValueChange?: (v: ComboboxOption | null) => void;
68
+ placeholder?: string;
69
+ label?: ReactNode;
70
+ disabled?: boolean;
71
+ id?: string;
72
+ className?: string;
73
+ "aria-describedby"?: string;
74
+ "aria-invalid"?: boolean | "true" | "false";
75
+ }): React.JSX.Element;
76
+ export interface ComboboxOptGroup {
77
+ label: string;
78
+ items: ComboboxOption[];
79
+ }
80
+ export declare function MultiCombobox({ items, value, onValueChange, onInputChange, loading, placeholder, label, disabled, id, className, "aria-describedby": descrito, "aria-invalid": invalido }: {
81
+ items: ComboboxOption[] | ComboboxOptGroup[];
82
+ value?: ComboboxOption[];
83
+ onValueChange?: (v: ComboboxOption[]) => void;
84
+ onInputChange?: (query: string) => void;
85
+ loading?: boolean;
86
+ placeholder?: string;
87
+ label?: ReactNode;
88
+ disabled?: boolean;
89
+ id?: string;
90
+ className?: string;
91
+ "aria-describedby"?: string;
92
+ "aria-invalid"?: boolean | "true" | "false";
93
+ }): React.JSX.Element;
94
+ export interface EditorBlock {
95
+ id: string;
96
+ kind?: ReactNode;
97
+ children: ReactNode;
98
+ }
99
+ export interface BlockEditorProps extends Omit<HTMLAttributes<HTMLOListElement>, "onReorder">, RefAttributes<HTMLOListElement> {
100
+ blocks: EditorBlock[];
101
+ onReorder: (from: number, to: number) => void;
102
+ onRemove?: (index: number) => void;
103
+ label?: string;
104
+ }
105
+ export declare function BlockEditor({ blocks, onReorder, onRemove, label, className, ...props }: BlockEditorProps): React.JSX.Element;