@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
package/README.md
CHANGED
|
@@ -35,14 +35,46 @@ the system without the rest:
|
|
|
35
35
|
import {Button} from "@aurea-uds/react/actions";
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
Six modules carry an optional engine and are therefore **outside the barrel** — importing a
|
|
39
|
+
Button must never pull in a peer you did not install:
|
|
40
40
|
|
|
41
41
|
| Import | Needs you to install |
|
|
42
42
|
|---|---|
|
|
43
43
|
| `@aurea-uds/react/code-editor` | `codemirror` and its `@codemirror/*` packages |
|
|
44
44
|
| `@aurea-uds/react/data-grid` | `@tanstack/react-table` |
|
|
45
45
|
| `@aurea-uds/react/qrcode` | `qr` |
|
|
46
|
+
| `@aurea-uds/react/calendar` | `react-day-picker` |
|
|
47
|
+
| `@aurea-uds/react/chart` | `recharts` |
|
|
48
|
+
| `@aurea-uds/react/graph` | `@xyflow/react` |
|
|
49
|
+
|
|
50
|
+
## Hooks
|
|
51
|
+
|
|
52
|
+
Four of them are public, and they are the part of the API that has **no page in the catalogue** —
|
|
53
|
+
the catalogue documents components, and a hook is not one. Until that changes, this is where they
|
|
54
|
+
are written down.
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
import {AureaProvider, useToast} from "@aurea-uds/react";
|
|
58
|
+
|
|
59
|
+
function SaveButton() {
|
|
60
|
+
const toast = useToast(); // inside AureaProvider
|
|
61
|
+
return <Button onClick={() => toast.add({
|
|
62
|
+
title: "Saved",
|
|
63
|
+
description: "Two files uploaded.",
|
|
64
|
+
type: "success", // info | success | warning | danger
|
|
65
|
+
})}>Save</Button>;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The stack renders itself: `AureaProvider` already mounts the viewport, so there is no `<Toaster/>`
|
|
70
|
+
to place and no second provider to install.
|
|
71
|
+
|
|
72
|
+
| Hook | What it gives you |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `useToast()` | `add({title, description, type})`, plus `close(id)` and the live list. The queue is Base UI's |
|
|
75
|
+
| `useAureaTheme()` | reads and sets **both** axes on `<html>`: `theme` (`dark`/`light`) and `density`. `theme` is `null` on the server — do not draw theme-dependent UI until it isn't |
|
|
76
|
+
| `useAureaStrings()` | the label dictionary, merged with what you passed to the provider |
|
|
77
|
+
| `useSpriteUrl()` | where the icon sprite is being loaded from |
|
|
46
78
|
|
|
47
79
|
## Requirements
|
|
48
80
|
|
package/dist/actions.d.ts
CHANGED
|
@@ -10,22 +10,40 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, Re
|
|
|
10
10
|
trailingIcon?: IconName;
|
|
11
11
|
href?: string;
|
|
12
12
|
fullWidth?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Use `<Toggle>` instead. Pesquisado em 18/08/2026 nas doze referências (MUI,
|
|
15
|
+
* Fluent 2, React Aria, Spectrum, Carbon, Radix/Base UI, shadcn, ReUI, PrimeReact, HeroUI,
|
|
16
|
+
* Cedar, APG): **nenhuma** põe o estado de pressionado no botão comum — todas têm um
|
|
17
|
+
* componente separado, e o nosso é o `Toggle`. Manter os dois é dois caminhos para a mesma
|
|
18
|
+
* coisa, que é o "qual eu uso?" que denuncia recurso duplicado.
|
|
19
|
+
* A diferença de verdade: aqui VOCÊ guarda o estado e isto só pinta e anuncia; o `Toggle`
|
|
20
|
+
* guarda o estado (motor Base UI), devolve `onPressedChange` e cobra nome acessível quando
|
|
21
|
+
* é só ícone.
|
|
22
|
+
* @deprecatedSince 0.4.0 — sai na `1.0`. A Aurea está em `0.x`, onde o semver permite
|
|
23
|
+
* quebrar, e por isso este é o momento mais barato que vai existir.
|
|
24
|
+
*/
|
|
13
25
|
pressed?: boolean;
|
|
14
26
|
kbd?: string;
|
|
15
27
|
}
|
|
16
28
|
export declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & RefAttributes<HTMLButtonElement>>;
|
|
17
|
-
|
|
29
|
+
interface ToggleBase {
|
|
18
30
|
pressed?: boolean;
|
|
19
31
|
defaultPressed?: boolean;
|
|
20
32
|
onPressedChange?: (pressed: boolean) => void;
|
|
21
33
|
icon?: IconName;
|
|
22
|
-
label?: string;
|
|
23
34
|
size?: Extract<ComponentSize, "sm" | "md" | "lg">;
|
|
24
35
|
disabled?: boolean;
|
|
25
36
|
id?: string;
|
|
26
37
|
className?: string;
|
|
27
|
-
children?: React.ReactNode;
|
|
28
38
|
}
|
|
39
|
+
type ToggleChildren = Exclude<React.ReactNode, boolean | null | undefined>;
|
|
40
|
+
export type ToggleProps = ToggleBase & ({
|
|
41
|
+
children: ToggleChildren;
|
|
42
|
+
label?: string;
|
|
43
|
+
} | {
|
|
44
|
+
children?: never;
|
|
45
|
+
label: string;
|
|
46
|
+
});
|
|
29
47
|
export declare function Toggle({ pressed, defaultPressed, onPressedChange, icon, label, size, disabled, id, className, children }: ToggleProps): React.JSX.Element;
|
|
30
48
|
export interface IconButtonProps extends Omit<ButtonProps, "children"> {
|
|
31
49
|
label: string;
|
|
@@ -44,3 +62,4 @@ export declare function ToolbarGroup({ label, className, ...props }: HTMLAttribu
|
|
|
44
62
|
}): React.JSX.Element;
|
|
45
63
|
export declare function ToolbarSeparator({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): React.JSX.Element;
|
|
46
64
|
export declare const ToolbarButton: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & RefAttributes<HTMLButtonElement>>;
|
|
65
|
+
export {};
|
package/dist/actions.js
CHANGED
|
@@ -3,26 +3,82 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
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
|
-
import { forwardRef } from "react";
|
|
6
|
+
import React, { forwardRef } from "react";
|
|
7
7
|
import { Toolbar as BaseToolbar } from "@base-ui/react/toolbar";
|
|
8
8
|
import { Toggle as BaseToggle } from "@base-ui/react/toggle";
|
|
9
|
-
import { cx, useAureaStrings
|
|
9
|
+
import { cx, useAureaStrings } from "./internal.js";
|
|
10
|
+
import { Kbd } from "./markup.js";
|
|
10
11
|
import { Icon } from "./system.js";
|
|
11
12
|
// type="button" por default: o default do HTML é submit, e um "Cancelar"/"Remover"
|
|
12
13
|
// dentro de <form> dispararia a ação principal (auditoria 18/07/2026, ALTO 1).
|
|
13
14
|
// Quem quer submeter passa type="submit" explícito (como o MessageComposer faz).
|
|
14
|
-
export const Button = forwardRef(function Button({ variant = "secondary", size = "md", loading, leadingIcon, trailingIcon, className, children, disabled, type = "button", href, fullWidth, pressed, kbd, ...props }, ref) {
|
|
15
|
+
export const Button = forwardRef(function Button({ variant = "secondary", size = "md", loading, leadingIcon, trailingIcon, className, children, disabled, type = "button", href, fullWidth, pressed, kbd, onClick, onClickCapture, "aria-disabled": ariaDisabled, ...props }, ref) {
|
|
15
16
|
const cls = cx("btn", `btn-${variant}`, size !== "md" && `btn-${size}`, fullWidth && "btn-block", className);
|
|
16
17
|
// kbd dentro do botão: mostra o atalho E o anuncia (aria-keyshortcuts), senão é enfeite.
|
|
17
18
|
const inner = _jsxs(_Fragment, { children: [loading && _jsx("span", { className: "spinner" }), leadingIcon && _jsx(Icon, { name: leadingIcon }), _jsx("span", { children: children }), kbd && _jsx(Kbd, { children: kbd }), trailingIcon && _jsx(Icon, { name: trailingIcon })] });
|
|
19
|
+
// INERTE ≠ DESABILITADO, e a diferença é medida (M4, 13/08/2026): `disabled` tira o botão da
|
|
20
|
+
// ordem de foco, então quem navega por teclado nunca alcança a explicação de POR QUE não dá — e
|
|
21
|
+
// "não dá porque você não tem permissão" é justamente o caso em que a explicação é tudo. O
|
|
22
|
+
// embrulho de <span> que o MUI documenta resolve o ponteiro e não resolve o teclado (span nasce
|
|
23
|
+
// com tabIndex -1, medido). Com `aria-disabled` o botão continua focável e anunciado como
|
|
24
|
+
// desabilitado, e é ESTE componente que tem de barrar a ativação — o atributo é só semântica.
|
|
25
|
+
// Mesmo remendo do AUD-0004, que já barrava o link desabilitado; aqui ele alcança o <button>.
|
|
26
|
+
const inerte = ariaDisabled === true || ariaDisabled === "true";
|
|
18
27
|
const off = disabled || loading;
|
|
19
28
|
const shared = { "aria-keyshortcuts": kbd || undefined, "aria-busy": loading || undefined };
|
|
29
|
+
// AUD-0004 (12/08/2026): o ramo de LINK desabilitado tirava o `href` e punha `aria-disabled`, e
|
|
30
|
+
// deixava o `onClick` passar intacto — então um link "desabilitado" continuava executando a ação
|
|
31
|
+
// ao ser clicado, e o `loading` também. Não há `disabled` em `<a>`: quem tem de barrar a ativação
|
|
32
|
+
// é este componente. O ramo de `<button>` nunca teve o defeito, porque `disabled` no elemento
|
|
33
|
+
// nativo já barra o evento — por isso a correção mora só aqui.
|
|
34
|
+
// Os dois handlers de click saem de `props`: no React, onClickCapture roda antes do onClick e
|
|
35
|
+
// também precisa ser barrado. `stopPropagation` evita o handler de bolha em um ancestral.
|
|
36
|
+
const bloqueia = (e) => { e.preventDefault(); e.stopPropagation(); };
|
|
37
|
+
const eventos = (off || inerte) ? { onClick: bloqueia, onClickCapture: bloqueia } : { onClick: onClick, onClickCapture: onClickCapture };
|
|
20
38
|
if (href !== undefined)
|
|
21
|
-
return _jsx("a", { ref: ref, className: cls, ...(off ? { "aria-disabled": true } : { href }), ...
|
|
22
|
-
return _jsx("button", { ref: ref, type: type, className: cls, disabled: off, "aria-pressed": pressed, ...shared, ...props, children: inner });
|
|
39
|
+
return _jsx("a", { ref: ref, className: cls, ...shared, ...props, ...(off ? { "aria-disabled": true } : { href }), ...eventos, children: inner });
|
|
40
|
+
return _jsx("button", { ref: ref, type: type, className: cls, disabled: off, "aria-disabled": inerte || undefined, "aria-pressed": pressed, ...shared, ...(inerte ? { onClick: bloqueia, onClickCapture: bloqueia } : { onClick, onClickCapture }), ...props, children: inner });
|
|
23
41
|
});
|
|
42
|
+
function temConteudoVisivel(children) {
|
|
43
|
+
const itens = React.Children.toArray(children);
|
|
44
|
+
return itens.some(item => {
|
|
45
|
+
if (typeof item === "string")
|
|
46
|
+
return item.trim().length > 0;
|
|
47
|
+
if (typeof item === "number" || typeof item === "bigint")
|
|
48
|
+
return true;
|
|
49
|
+
if (!React.isValidElement(item))
|
|
50
|
+
return false;
|
|
51
|
+
const p = item.props;
|
|
52
|
+
if (p["aria-hidden"] === true || p["aria-hidden"] === "true")
|
|
53
|
+
return false;
|
|
54
|
+
if (typeof p["aria-label"] === "string" && p["aria-label"].trim())
|
|
55
|
+
return true;
|
|
56
|
+
if (typeof p.alt === "string" && p.alt.trim())
|
|
57
|
+
return true;
|
|
58
|
+
// Inspecionar `children` também cobre Fragment e elementos formatadores. Componente arbitrário
|
|
59
|
+
// sem conteúdo inspecionável é tratado de modo conservador: precisa fornecer `label`.
|
|
60
|
+
return temConteudoVisivel(p.children);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// ESTE é o toggle da Aurea. O `pressed` do `Button` está DEPRECIADO em favor dele (0.4.0,
|
|
64
|
+
// sai na 1.0): as doze referências pesquisadas em 18/08/2026 têm componente separado, e
|
|
65
|
+
// nenhuma põe o estado no botão comum.
|
|
66
|
+
// E a REGRA QUE FALTAVA ESTAR ESCRITA AQUI, do Adobe Spectrum e do APG: **o rótulo não muda
|
|
67
|
+
// entre os estados**. Se o texto vira "Mute"/"Unmute" ou "Play"/"Pause", não é toggle — é
|
|
68
|
+
// botão de ação, porque quem lê tela ouve o rótulo NOVO e o estado ao mesmo tempo e não sabe
|
|
69
|
+
// se o botão descreve o que é ou o que fará.
|
|
24
70
|
export function Toggle({ pressed, defaultPressed, onPressedChange, icon, label, size = "md", disabled, id, className, children }) {
|
|
25
|
-
|
|
71
|
+
// O tipo barra o consumidor TypeScript; este aviso barra o de JavaScript, que não tem tipo nenhum.
|
|
72
|
+
// Sem gate por NODE_ENV de propósito: o `dist` é saída de `tsc`, então `process` não existe no
|
|
73
|
+
// navegador e a referência quebraria o render — e um controle sem nome merece aparecer em produção
|
|
74
|
+
// também. Segue o idioma do próprio motor, que usa `console.error` para invariante violada.
|
|
75
|
+
// O tipo barra false/null/undefined diretos. Runtime ainda precisa cobrir os vazios que o tipo não
|
|
76
|
+
// consegue expressar: string em branco, array/Fragment vazio e elemento só decorativo.
|
|
77
|
+
const textoVisivel = temConteudoVisivel(children);
|
|
78
|
+
const rotuloVisivel = typeof label === "string" && label.trim().length > 0;
|
|
79
|
+
if (!textoVisivel && !rotuloVisivel)
|
|
80
|
+
console.error("Aurea: <Toggle> sem `children` visível e sem `label` não tem nome acessível — quem usa leitor de tela encontra um botão anônimo. Passe `label` quando o toggle for só ícone.");
|
|
81
|
+
return _jsxs(BaseToggle, { id: id, disabled: disabled, pressed: pressed, defaultPressed: defaultPressed, onPressedChange: onPressedChange, "aria-label": textoVisivel ? undefined : (rotuloVisivel ? label : undefined), className: cx("btn", "btn-ghost", size !== "md" && `btn-${size}`, !textoVisivel && "btn-icon", "toggle", className), children: [icon && _jsx(Icon, { name: icon }), children] });
|
|
26
82
|
}
|
|
27
83
|
// default ghost (não secondary): um ícone-ação solto — hambúrguer, tema, fechar — é sem
|
|
28
84
|
// caixa por convenção (pedido do Victor: hambúrguer sem borda). Quem quer a caixa passa
|
|
@@ -0,0 +1,24 @@
|
|
|
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 { cx, useAureaStrings } from "./internal.js";
|
|
7
|
+
import { Icon } from "./system.js";
|
|
8
|
+
// copyable: o gatilho é marcado com data-aurea-copy e o COMPORTAMENTO mora no aurea.js do
|
|
9
|
+
// core (uma implementação para React e para HTML puro). O onClick daqui atende quem só
|
|
10
|
+
// carrega o pacote React; ele para a propagação pro core não copiar duas vezes.
|
|
11
|
+
export function CodeBlock({ children, language = "text", copyable, className }) {
|
|
12
|
+
const s = useAureaStrings();
|
|
13
|
+
// tabIndex no <pre>: `.code-block` é `overflow:auto` por construção, então é REGIÃO ROLÁVEL
|
|
14
|
+
// e não tem nada focável dentro — quem navega por teclado não alcança o código que passa da
|
|
15
|
+
// largura. É a regra `scrollable-region-focusable` do axe, e o mesmo defeito que o painel de
|
|
16
|
+
// demo do catálogo já tinha corrigido em 30/07/2026 no lado dele; aqui, na biblioteca, ele
|
|
17
|
+
// seguia aberto e só não aparecia porque nenhuma linha era comprida o bastante. Quem o achou
|
|
18
|
+
// foi o `catalog-sweep` em 10/08/2026, quando o bloco do I1 fez a linha de import crescer.
|
|
19
|
+
const pre = _jsx("pre", { className: cx("code-block", !copyable && className), "data-language": language, tabIndex: 0, children: _jsx("code", { children: children }) });
|
|
20
|
+
if (!copyable)
|
|
21
|
+
return pre;
|
|
22
|
+
const onCopy = (e) => { e.stopPropagation(); window.Aurea?.copy?.(e.currentTarget) ?? navigator.clipboard?.writeText(children); };
|
|
23
|
+
return _jsxs("div", { className: cx("code-block-wrap", className), "data-aurea-copy-scope": true, children: [_jsxs("button", { type: "button", className: "btn btn-icon btn-ghost copy-code", "data-aurea-copy": true, "aria-label": s.copyCode, onClick: onCopy, children: [_jsx(Icon, { name: "copy", size: "sm", className: "c-copy" }), _jsx(Icon, { name: "checkmark", size: "sm", className: "c-done" })] }), pre] });
|
|
24
|
+
}
|
package/dist/code.d.ts
CHANGED
|
@@ -1,13 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
language?: string;
|
|
4
|
-
copyable?: boolean;
|
|
5
|
-
className?: string;
|
|
6
|
-
}): import("react").JSX.Element;
|
|
7
|
-
export declare function LogStream({ lines }: {
|
|
8
|
-
lines: Array<{
|
|
9
|
-
time?: string;
|
|
10
|
-
level?: string;
|
|
11
|
-
text: string;
|
|
12
|
-
}>;
|
|
13
|
-
}): import("react").JSX.Element;
|
|
1
|
+
export * from "./code-client.js";
|
|
2
|
+
export { LogStream } from "./markup.js";
|
package/dist/code.js
CHANGED
|
@@ -1,34 +1,5 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { cx, useAureaStrings } from "./internal.js";
|
|
7
|
-
import { Icon } from "./system.js";
|
|
8
|
-
// copyable: o gatilho é marcado com data-aurea-copy e o COMPORTAMENTO mora no aurea.js do
|
|
9
|
-
// core (uma implementação para React e para HTML puro). O onClick daqui atende quem só
|
|
10
|
-
// carrega o pacote React; ele para a propagação pro core não copiar duas vezes.
|
|
11
|
-
export function CodeBlock({ children, language = "text", copyable, className }) {
|
|
12
|
-
const s = useAureaStrings();
|
|
13
|
-
// tabIndex no <pre>: `.code-block` é `overflow:auto` por construção, então é REGIÃO ROLÁVEL
|
|
14
|
-
// e não tem nada focável dentro — quem navega por teclado não alcança o código que passa da
|
|
15
|
-
// largura. É a regra `scrollable-region-focusable` do axe, e o mesmo defeito que o painel de
|
|
16
|
-
// demo do catálogo já tinha corrigido em 30/07/2026 no lado dele; aqui, na biblioteca, ele
|
|
17
|
-
// seguia aberto e só não aparecia porque nenhuma linha era comprida o bastante. Quem o achou
|
|
18
|
-
// foi o `catalog-sweep` em 10/08/2026, quando o bloco do I1 fez a linha de import crescer.
|
|
19
|
-
const pre = _jsx("pre", { className: cx("code-block", !copyable && className), "data-language": language, tabIndex: 0, children: _jsx("code", { children: children }) });
|
|
20
|
-
if (!copyable)
|
|
21
|
-
return pre;
|
|
22
|
-
const onCopy = (e) => { e.stopPropagation(); window.Aurea?.copy?.(e.currentTarget) ?? navigator.clipboard?.writeText(children); };
|
|
23
|
-
return _jsxs("div", { className: cx("code-block-wrap", className), "data-aurea-copy-scope": true, children: [_jsxs("button", { type: "button", className: "btn btn-icon btn-ghost copy-code", "data-aurea-copy": true, "aria-label": s.copyCode, onClick: onCopy, children: [_jsx(Icon, { name: "copy", size: "sm", className: "c-copy" }), _jsx(Icon, { name: "checkmark", size: "sm", className: "c-done" })] }), pre] });
|
|
24
|
-
}
|
|
25
|
-
// A pele do log é de TRÊS colunas — hora, nível, texto — e o componente emitia DUAS. Medido no
|
|
26
|
-
// item E13 (07/08/2026), com só o core: o texto caía na coluna do NÍVEL, 72px de largura, e a
|
|
27
|
-
// prop `level` não pintava nada, porque o core estiliza `.log-level.error` (um elemento) e o
|
|
28
|
-
// componente escrevia `log-error` (no container). Gate nenhum via: o check 18 só enxerga classe
|
|
29
|
-
// LITERAL, e `log-${level}` é template; o check 15 dava a classe por produzível pelo mesmo
|
|
30
|
-
// motivo, via prefixo. É o achado A6 outra vez — a regra do core servia a `apps/docs/index.html`,
|
|
31
|
-
// escrito à mão com as três partes, e a biblioteca pagava a conta.
|
|
32
|
-
// A célula do nível é SEMPRE renderizada, mesmo vazia: sem ela a linha sem `level` volta a ter
|
|
33
|
-
// dois filhos e o texto volta para a coluna estreita.
|
|
34
|
-
export function LogStream({ lines }) { return _jsx("div", { className: "log-stream", role: "log", "aria-live": "polite", children: lines.map((l, n) => _jsxs("div", { className: cx("log-line", l.level && `log-${l.level}`), children: [_jsx("time", { className: "log-time", children: l.time }), _jsx("span", { className: "log-level", children: l.level }), _jsx("span", { children: l.text })] }, n)) }); }
|
|
1
|
+
// VITRINE da categoria — é este arquivo que `@aurea-uds/react/code` resolve. Sem `"use client"`
|
|
2
|
+
// de propósito: a diretiva contamina o módulo inteiro e faria a marcação pura chegar como cliente
|
|
3
|
+
// por vizinhança (ADR-0026; o check 26b reprova quem a puser de volta aqui).
|
|
4
|
+
export * from "./code-client.js";
|
|
5
|
+
export { LogStream } from "./markup.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
|
|
2
|
+
export declare function Table({ caption, children, className, ...props }: HTMLAttributes<HTMLTableElement> & RefAttributes<HTMLTableElement> & {
|
|
3
|
+
caption?: ReactNode;
|
|
4
|
+
}): React.JSX.Element;
|
|
5
|
+
export interface SortableItem {
|
|
6
|
+
id: string;
|
|
7
|
+
label: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export interface SortableListProps extends Omit<HTMLAttributes<HTMLUListElement>, "onReorder">, RefAttributes<HTMLUListElement> {
|
|
10
|
+
items: SortableItem[];
|
|
11
|
+
onReorder: (from: number, to: number) => void;
|
|
12
|
+
label?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function SortableList({ items, onReorder, label, className, ...props }: SortableListProps): React.JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
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 { cx, useAureaStrings, useReorder } from "./internal.js";
|
|
8
|
+
import { Icon } from "./system.js";
|
|
9
|
+
export function Table({ caption, children, className, ...props }) { const s = useAureaStrings(); return _jsx("div", { className: "table-region", role: "region", "aria-label": typeof caption === "string" ? caption : s.tableLabel, tabIndex: 0, children: _jsxs("table", { className: cx("table", className), ...props, children: [caption && _jsx("caption", { children: caption }), children] }) }); }
|
|
10
|
+
// O protocolo de teclado e de ponteiro saiu daqui para o `useReorder` do `internal` no item N1,
|
|
11
|
+
// quando o `BlockEditor` virou o segundo dono dele. A DOM abaixo não mudou uma vírgula na
|
|
12
|
+
// extração — é o que o gate de pixel e o `skin.spec` continuam medindo.
|
|
13
|
+
export function SortableList({ items, onReorder, label, className, ...props }) {
|
|
14
|
+
const s = useAureaStrings();
|
|
15
|
+
const bid = React.useId();
|
|
16
|
+
const r = useReorder({ count: items.length, order: items, onReorder,
|
|
17
|
+
rowSelector: ".sortable-item", handleSelector: ".sortable-handle" });
|
|
18
|
+
return _jsxs(_Fragment, { children: [_jsx("ul", { ref: r.ref, className: cx("sortable-list", className), "aria-label": label ?? s.sortableLabel, ...props, children: items.map((it, i) => {
|
|
19
|
+
const lid = `${bid}l${i}`, hid = `${bid}h${i}`;
|
|
20
|
+
return _jsxs("li", { className: "sortable-item", "data-grabbed": r.pego === i || undefined, children: [_jsxs("button", { type: "button", id: hid, className: "sortable-handle", "aria-labelledby": `${hid} ${lid}`, "aria-describedby": `${bid}ajuda`, "aria-pressed": r.pego === i, onKeyDown: e => r.teclado(e, i), onPointerDown: e => r.ponteiroBaixo(e, i), onPointerMove: r.ponteiroMove, onPointerUp: r.ponteiroSolta, onPointerCancel: r.ponteiroSolta, children: [_jsx(Icon, { name: "drag--horizontal" }), _jsx("span", { className: "sr-only", children: s.sortableHandle })] }), _jsx("span", { id: lid, className: "sortable-label", children: it.label })] }, it.id);
|
|
21
|
+
}) }), _jsx("span", { id: `${bid}ajuda`, className: "sr-only", children: s.sortableHelp }), _jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: r.aviso })] });
|
|
22
|
+
}
|
package/dist/data-display.d.ts
CHANGED
|
@@ -1,23 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export { Kbd } from "./
|
|
3
|
-
export declare function KPI({ label, value, trend, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
4
|
-
label: ReactNode;
|
|
5
|
-
value: ReactNode;
|
|
6
|
-
trend?: ReactNode;
|
|
7
|
-
}): React.JSX.Element;
|
|
8
|
-
export declare function DataList({ items }: {
|
|
9
|
-
items: Array<{
|
|
10
|
-
term: ReactNode;
|
|
11
|
-
value: ReactNode;
|
|
12
|
-
}>;
|
|
13
|
-
}): React.JSX.Element;
|
|
14
|
-
export declare function Timeline({ items }: {
|
|
15
|
-
items: Array<{
|
|
16
|
-
title: ReactNode;
|
|
17
|
-
description?: ReactNode;
|
|
18
|
-
time?: ReactNode;
|
|
19
|
-
}>;
|
|
20
|
-
}): React.JSX.Element;
|
|
21
|
-
export declare function Table({ caption, children, className, ...props }: HTMLAttributes<HTMLTableElement> & RefAttributes<HTMLTableElement> & {
|
|
22
|
-
caption?: ReactNode;
|
|
23
|
-
}): React.JSX.Element;
|
|
1
|
+
export * from "./data-display-client.js";
|
|
2
|
+
export { Kbd, KPI, DataList, Timeline, Prose } from "./markup.js";
|
package/dist/data-display.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { cx, useAureaStrings } from "./internal.js";
|
|
8
|
-
import { Card } from "./layout.js";
|
|
9
|
-
// Kbd é implementado em ./internal (o Button depende dele); a casa PÚBLICA dele é esta.
|
|
10
|
-
export { Kbd } from "./internal.js";
|
|
11
|
-
export function KPI({ label, value, trend, className, ...props }) { return _jsxs(Card, { className: cx("kpi", className), ...props, children: [_jsx("span", { className: "muted", children: label }), _jsx("strong", { children: value }), trend && _jsx("small", { children: trend })] }); }
|
|
12
|
-
export function DataList({ items }) { return _jsx("dl", { className: "data-list", children: items.map((i, n) => _jsxs(React.Fragment, { children: [_jsx("dt", { children: i.term }), _jsx("dd", { children: i.value })] }, n)) }); }
|
|
13
|
-
export function Timeline({ items }) { return _jsx("ol", { className: "timeline", children: items.map((i, n) => _jsxs("li", { children: [_jsx("span", { className: "timeline-dot" }), _jsxs("div", { children: [_jsx("strong", { children: i.title }), i.description && _jsx("p", { children: i.description }), i.time && _jsx("small", { className: "muted", children: i.time })] })] }, n)) }); }
|
|
14
|
-
export function Table({ caption, children, className, ...props }) { const s = useAureaStrings(); return _jsx("div", { className: "table-region", role: "region", "aria-label": typeof caption === "string" ? caption : s.tableLabel, tabIndex: 0, children: _jsxs("table", { className: cx("table", className), ...props, children: [caption && _jsx("caption", { children: caption }), children] }) }); }
|
|
1
|
+
// VITRINE da categoria — é este arquivo que `@aurea-uds/react/data-display` 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 "./data-display-client.js";
|
|
5
|
+
export { Kbd, KPI, DataList, Timeline, Prose } from "./markup.js";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
|
|
2
|
+
import { type UniversalState } from "./internal.js";
|
|
3
|
+
import { type IconName } from "./system.js";
|
|
4
|
+
import { type OverlaySide } from "./overlays.js";
|
|
5
|
+
export type StatusVariant = "neutral" | "online" | "offline" | "busy" | "away" | "running" | "success" | "warning" | "danger" | "info";
|
|
6
|
+
export declare function Status({ variant, state, children, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
|
|
7
|
+
variant?: StatusVariant;
|
|
8
|
+
state?: UniversalState;
|
|
9
|
+
}): React.JSX.Element;
|
|
10
|
+
export type AlertVariant = "info" | "success" | "warning" | "danger";
|
|
11
|
+
export declare function Alert({ variant, state, title, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
12
|
+
variant?: AlertVariant;
|
|
13
|
+
state?: UniversalState;
|
|
14
|
+
title?: ReactNode;
|
|
15
|
+
}): React.JSX.Element;
|
|
16
|
+
export type BannerVariant = AlertVariant;
|
|
17
|
+
export declare function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
18
|
+
variant?: BannerVariant;
|
|
19
|
+
state?: UniversalState;
|
|
20
|
+
title?: ReactNode;
|
|
21
|
+
icon?: IconName;
|
|
22
|
+
onDismiss?: () => void;
|
|
23
|
+
}): React.JSX.Element;
|
|
24
|
+
export declare function Spinner({ size, label, decorative, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
|
|
25
|
+
size?: "sm" | "md" | "lg";
|
|
26
|
+
label?: string;
|
|
27
|
+
decorative?: boolean;
|
|
28
|
+
}): React.JSX.Element;
|
|
29
|
+
export type DataStateValue = "loading" | "error" | "empty" | UniversalState;
|
|
30
|
+
export declare function DataState({ state, message, skeleton, emptyTitle, emptyIcon, action, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
31
|
+
state?: DataStateValue;
|
|
32
|
+
message?: ReactNode;
|
|
33
|
+
skeleton?: ReactNode;
|
|
34
|
+
emptyTitle?: ReactNode;
|
|
35
|
+
emptyIcon?: IconName;
|
|
36
|
+
action?: ReactNode;
|
|
37
|
+
children: ReactNode | (() => ReactNode);
|
|
38
|
+
}): React.JSX.Element;
|
|
39
|
+
export declare function EmptyState({ icon, title, titleAs: TitleTag, description, action, state }: {
|
|
40
|
+
icon?: IconName;
|
|
41
|
+
title: ReactNode;
|
|
42
|
+
titleAs?: "h2" | "h3" | "h4" | "p";
|
|
43
|
+
description?: ReactNode;
|
|
44
|
+
action?: ReactNode;
|
|
45
|
+
state?: UniversalState;
|
|
46
|
+
}): React.JSX.Element;
|
|
47
|
+
export interface NotificationItem {
|
|
48
|
+
id: string;
|
|
49
|
+
title: ReactNode;
|
|
50
|
+
description?: ReactNode;
|
|
51
|
+
time?: ReactNode;
|
|
52
|
+
icon?: IconName;
|
|
53
|
+
read?: boolean;
|
|
54
|
+
group?: string;
|
|
55
|
+
onClick?: () => void;
|
|
56
|
+
}
|
|
57
|
+
export declare function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon, side }: {
|
|
58
|
+
items: NotificationItem[];
|
|
59
|
+
onItemClick?: (item: NotificationItem) => void;
|
|
60
|
+
onMarkAllRead?: () => void;
|
|
61
|
+
label?: string;
|
|
62
|
+
icon?: IconName;
|
|
63
|
+
side?: OverlaySide;
|
|
64
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
|
|
4
|
+
// do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
|
|
5
|
+
// internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
8
|
+
import { cx, useAureaStrings, usePortalContainer, stateSeverity } from "./internal.js";
|
|
9
|
+
import { Icon } from "./system.js";
|
|
10
|
+
import { Button, IconButton } from "./actions.js";
|
|
11
|
+
// `oracle` saiu daqui (achado M9, 26/07/2026): estava no tipo e no CSS, e NÃO estava na
|
|
12
|
+
// ficha — ou seja, era superfície pública que o contrato não declarava. E é vocabulário do
|
|
13
|
+
// app de origem (papel de agente), não do sistema, como as classes de domínio que o achado
|
|
14
|
+
// A6 mapeou. As classes `.badge-oracle`/`.btn-oracle` seguem no core porque o
|
|
15
|
+
// `apps/docs/index.html` escrito à mão as usa; saem junto com ele, na Fase 4 do plano.
|
|
16
|
+
// `.btn-oracle` nunca foi alcançável pelo React — `ButtonVariant` não tem `oracle`.
|
|
17
|
+
import { Skeleton } from "./markup.js";
|
|
18
|
+
// Status (DIRECTION §3.6): condição OPERACIONAL — ponto + rótulo. Não é Badge: Badge é
|
|
19
|
+
// metadado curto num pill; Status diz em que estado a coisa está. Reusa o .status-dot que
|
|
20
|
+
// já existia solto. A variante colore só o PONTO (currentColor); o rótulo fica legível em
|
|
21
|
+
// --foreground. Cor não é o único sinal — quem diz o estado é o texto (WCAG 1.4.1); o
|
|
22
|
+
// ponto é decorativo e sai do leitor de tela. ponytail: rótulo é do consumidor (sem i18n
|
|
23
|
+
// nova) — a variante é só a cor.
|
|
24
|
+
// `state` (Parte J) é EIXO À PARTE de `variant`, e os dois convivem: a variante é a cor do
|
|
25
|
+
// ponto, o estado é a condição universal. Quem passa `state` e não passa `variant` recebe a
|
|
26
|
+
// cor derivada — `offline` fica com o ponto vazado que ele já tinha desde sempre, os outros
|
|
27
|
+
// seis caem na gravidade. Quem passa os dois manda, porque só o consumidor sabe se aquele
|
|
28
|
+
// "esperando" dele é grave. E o rótulo é o do consumidor, como sempre foi: a string universal
|
|
29
|
+
// entra só quando não há filho, para o componente não passar a inventar texto.
|
|
30
|
+
export function Status({ variant, state, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? (state === "offline" ? "offline" : stateSeverity(state)) : "neutral"); return _jsxs("span", { className: cx("status", v !== "neutral" && `status-${v}`, className), "data-state": state, ...props, children: [_jsx("i", { className: "status-dot", "aria-hidden": "true" }), _jsx("span", { className: "status-label", children: children ?? (state ? s.universalState[state] : null) })] }); }
|
|
31
|
+
export function Alert({ variant, state, title, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("alert", `alert-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }); }
|
|
32
|
+
export function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("banner", `banner-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [icon ? _jsx(Icon, { name: icon }) : _jsx("span", {}), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
|
|
33
|
+
// A barra grampeava 0..100 e o `aria-valuenow` NÃO — medido ao publicar o contrato de API na
|
|
34
|
+
// Parte E: com value=150 o desenho parava em 100% e o leitor de tela anunciava "150 de 100".
|
|
35
|
+
// A causa é a de sempre: o grampo existia num lugar só. Agora é UMA expressão que serve os dois,
|
|
36
|
+
// então não há como divergirem de novo.
|
|
37
|
+
// SÓ DETERMINADA, de propósito: não há modo indeterminado nem `.progress` para ele no core. A
|
|
38
|
+
// ficha dizia que havia e era falso — corrigido junto, porque contrato que promete o que não
|
|
39
|
+
// existe custa mais que ausência.
|
|
40
|
+
export function Spinner({ size = "sm", label, decorative, className, ...props }) { const s = useAureaStrings(); return _jsx("span", { className: cx("spinner", size !== "sm" && `spinner-${size}`, className), ...(decorative ? { "aria-hidden": true } : { role: "status", "aria-label": label ?? s.loading }), ...props }); }
|
|
41
|
+
// `children` como FUNÇÃO é de propósito para o caso `loading`: assim o consumidor não paga o
|
|
42
|
+
// render do conteúdo enquanto ele não existe. Aceita nó também, porque a maioria das telas já
|
|
43
|
+
// tem o conteúdo pronto e obrigar função seria cerimônia.
|
|
44
|
+
export function DataState({ state, message, skeleton, emptyTitle, emptyIcon, action, children, className, ...props }) {
|
|
45
|
+
const s = useAureaStrings();
|
|
46
|
+
const conteudo = () => typeof children === "function" ? children() : children;
|
|
47
|
+
const caixa = (inner, ocupado) => _jsx("div", { className: cx("data-state", className), "aria-busy": ocupado || undefined, "data-state": state, ...props, children: inner });
|
|
48
|
+
if (state === "loading")
|
|
49
|
+
return caixa(skeleton ?? _jsx(Skeleton, { style: { height: "var(--space-8)" } }), true);
|
|
50
|
+
if (state === "error")
|
|
51
|
+
return caixa(_jsx(Alert, { variant: "danger", children: message ?? s.dataError }));
|
|
52
|
+
// `titleAs="p"` e não o `h3` padrão do EmptyState: aqui o vazio é estado de uma REGIÃO, não
|
|
53
|
+
// seção do documento. Injetar um h3 no meio do conteúdo do consumidor salta nível de título —
|
|
54
|
+
// o gate de hierarquia pegou (`salto h1 → h3`) e o axe repetiu como `heading-order`.
|
|
55
|
+
if (state === "empty")
|
|
56
|
+
return caixa(_jsx(EmptyState, { icon: emptyIcon, titleAs: "p", title: emptyTitle ?? s.dataEmpty, description: message, action: action }));
|
|
57
|
+
// Os universais NÃO substituem o conteúdo: eles o acompanham. É a regra do DataGrid, e o
|
|
58
|
+
// motivo é o mesmo — a pessoa precisa do dado E do aviso, não de um no lugar do outro.
|
|
59
|
+
if (state)
|
|
60
|
+
return caixa(_jsxs(_Fragment, { children: [_jsx(Alert, { variant: stateSeverity(state), state: state, children: message }), conteudo()] }));
|
|
61
|
+
return caixa(conteudo());
|
|
62
|
+
}
|
|
63
|
+
export function EmptyState({ icon = "document--blank", title, titleAs: TitleTag = "h3", description, action, state }) { const s = useAureaStrings(); const desc = description ?? (state ? s.universalState[state] : null); return _jsxs("div", { className: "empty-state", "data-state": state, children: [_jsx(Icon, { name: icon, size: "xl" }), _jsx(TitleTag, { className: "empty-title", children: title }), desc && _jsx("p", { className: "muted", children: desc }), action] }); }
|
|
64
|
+
function groupNotifications(items) {
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const it of items) {
|
|
67
|
+
const last = out[out.length - 1];
|
|
68
|
+
if (last && last.label === it.group)
|
|
69
|
+
last.items.push(it);
|
|
70
|
+
else
|
|
71
|
+
out.push({ label: it.group, items: [it] });
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
export function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon = "notification", side = "bottom" }) {
|
|
76
|
+
const s = useAureaStrings();
|
|
77
|
+
const portal = usePortalContainer();
|
|
78
|
+
const title = label ?? s.notificationsLabel;
|
|
79
|
+
const unread = items.filter(i => !i.read).length;
|
|
80
|
+
const groups = groupNotifications(items);
|
|
81
|
+
const baseId = React.useId();
|
|
82
|
+
const seen = React.useRef(undefined);
|
|
83
|
+
const [announce, setAnnounce] = React.useState("");
|
|
84
|
+
React.useEffect(() => {
|
|
85
|
+
const ids = new Set(items.map(i => i.id));
|
|
86
|
+
if (seen.current === undefined) {
|
|
87
|
+
seen.current = ids;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const fresh = items.filter(i => !seen.current.has(i.id)).length;
|
|
91
|
+
seen.current = ids;
|
|
92
|
+
// Texto idêntico duas vezes seguidas não muta o DOM e o leitor silencia a 2ª
|
|
93
|
+
// chegada (auditoria 18/07/2026, MÉDIO 4). Um NBSP alternado no fim força a
|
|
94
|
+
// mutação sem mudar o que se ouve.
|
|
95
|
+
if (fresh)
|
|
96
|
+
setAnnounce(prev => { const text = `${fresh} ${s.notificationNew}`; return prev === text ? text + " " : text; });
|
|
97
|
+
}, [items, s.notificationNew]);
|
|
98
|
+
const renderRow = (it) => {
|
|
99
|
+
const body = _jsxs(_Fragment, { children: [_jsx("span", { className: "notification-dot", "aria-hidden": "true" }), _jsxs("span", { className: "notification-item-title", children: [it.icon && _jsx(Icon, { name: it.icon, size: "sm" }), !it.read && _jsxs("span", { className: "sr-only", children: [s.notificationUnread, " "] }), it.title] }), it.time && _jsx("span", { className: "notification-time", children: it.time }), it.description && _jsx("span", { className: "notification-item-desc", children: it.description })] });
|
|
100
|
+
return it.onClick || onItemClick
|
|
101
|
+
? _jsx("button", { type: "button", className: "notification-item", "data-read": it.read || undefined, onClick: () => { it.onClick?.(); onItemClick?.(it); }, children: body })
|
|
102
|
+
: _jsx("div", { className: "notification-item", "data-read": it.read || undefined, children: body });
|
|
103
|
+
};
|
|
104
|
+
return _jsxs(BasePopover.Root, { children: [_jsxs("span", { className: "notification-trigger", children: [_jsx(BasePopover.Trigger, { render: _jsx(IconButton, { variant: "ghost", icon: icon, label: unread ? `${title} (${unread})` : title }) }), unread > 0 && _jsx("span", { className: "notification-count", "aria-hidden": "true", children: unread > 99 ? "99+" : unread })] }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { side: side, sideOffset: 8, children: _jsxs(BasePopover.Popup, { className: "popover notification-panel", "aria-label": title, children: [_jsxs("div", { className: "notification-head", children: [_jsx(BasePopover.Title, { render: _jsx("strong", {}), children: title }), unread > 0 && onMarkAllRead && _jsx(Button, { variant: "ghost", size: "sm", onClick: onMarkAllRead, children: s.notificationMarkAll })] }), items.length
|
|
105
|
+
? _jsx("div", { className: "notification-list", children: groups.map((g, gi) => {
|
|
106
|
+
const gid = baseId + gi;
|
|
107
|
+
return _jsxs(React.Fragment, { children: [g.label && _jsx("p", { className: "notification-group-label", id: gid, children: g.label }), _jsx("ul", { className: "notification-sublist", "aria-labelledby": g.label ? gid : undefined, children: g.items.map(it => _jsx("li", { children: renderRow(it) }, it.id)) })] }, gi);
|
|
108
|
+
}) })
|
|
109
|
+
: _jsx("p", { className: "notification-empty", children: s.notificationEmpty })] }) }) }), _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: announce })] });
|
|
110
|
+
}
|
package/dist/feedback.d.ts
CHANGED
|
@@ -1,63 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { type IconName } from "./system.js";
|
|
4
|
-
import { type OverlaySide } from "./overlays.js";
|
|
5
|
-
export type BadgeVariant = "neutral" | "primary" | "info" | "success" | "warning" | "danger" | "running" | "paused" | "offline" | "review";
|
|
6
|
-
export declare function Badge({ variant, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
|
|
7
|
-
variant?: BadgeVariant;
|
|
8
|
-
}): React.JSX.Element;
|
|
9
|
-
export type StatusVariant = "neutral" | "online" | "offline" | "busy" | "away" | "running" | "success" | "warning" | "danger" | "info";
|
|
10
|
-
export declare function Status({ variant, state, children, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
|
|
11
|
-
variant?: StatusVariant;
|
|
12
|
-
state?: UniversalState;
|
|
13
|
-
}): React.JSX.Element;
|
|
14
|
-
export type AlertVariant = "info" | "success" | "warning" | "danger";
|
|
15
|
-
export declare function Alert({ variant, state, title, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
16
|
-
variant?: AlertVariant;
|
|
17
|
-
state?: UniversalState;
|
|
18
|
-
title?: ReactNode;
|
|
19
|
-
}): React.JSX.Element;
|
|
20
|
-
export type BannerVariant = AlertVariant;
|
|
21
|
-
export declare function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
22
|
-
variant?: BannerVariant;
|
|
23
|
-
state?: UniversalState;
|
|
24
|
-
title?: ReactNode;
|
|
25
|
-
icon?: IconName;
|
|
26
|
-
onDismiss?: () => void;
|
|
27
|
-
}): React.JSX.Element;
|
|
28
|
-
export declare function Progress({ value, label }: {
|
|
29
|
-
value: number;
|
|
30
|
-
label?: string;
|
|
31
|
-
}): React.JSX.Element;
|
|
32
|
-
export declare function Skeleton({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): React.JSX.Element;
|
|
33
|
-
export declare function Spinner({ size, label, decorative, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
|
|
34
|
-
size?: "sm" | "md" | "lg";
|
|
35
|
-
label?: string;
|
|
36
|
-
decorative?: boolean;
|
|
37
|
-
}): React.JSX.Element;
|
|
38
|
-
export declare function EmptyState({ icon, title, titleAs: TitleTag, description, action, state }: {
|
|
39
|
-
icon?: IconName;
|
|
40
|
-
title: ReactNode;
|
|
41
|
-
titleAs?: "h2" | "h3" | "h4" | "p";
|
|
42
|
-
description?: ReactNode;
|
|
43
|
-
action?: ReactNode;
|
|
44
|
-
state?: UniversalState;
|
|
45
|
-
}): React.JSX.Element;
|
|
46
|
-
export interface NotificationItem {
|
|
47
|
-
id: string;
|
|
48
|
-
title: ReactNode;
|
|
49
|
-
description?: ReactNode;
|
|
50
|
-
time?: ReactNode;
|
|
51
|
-
icon?: IconName;
|
|
52
|
-
read?: boolean;
|
|
53
|
-
group?: string;
|
|
54
|
-
onClick?: () => void;
|
|
55
|
-
}
|
|
56
|
-
export declare function NotificationCenter({ items, onItemClick, onMarkAllRead, label, icon, side }: {
|
|
57
|
-
items: NotificationItem[];
|
|
58
|
-
onItemClick?: (item: NotificationItem) => void;
|
|
59
|
-
onMarkAllRead?: () => void;
|
|
60
|
-
label?: string;
|
|
61
|
-
icon?: IconName;
|
|
62
|
-
side?: OverlaySide;
|
|
63
|
-
}): React.JSX.Element;
|
|
1
|
+
export * from "./feedback-client.js";
|
|
2
|
+
export { Badge, formatBadgeCount, Progress, Skeleton, type BadgeVariant, type BadgeEmphasis, type BadgeSize, type BadgePlacement, type BadgeProps } from "./markup.js";
|