@aurea-uds/react 0.8.7 → 0.8.10
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/data-grid.js +20 -0
- package/dist/graph.d.ts +1 -0
- package/dist/graph.js +64 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inputs-client.d.ts +35 -2
- package/dist/inputs-client.js +42 -5
- package/dist/inputs.d.ts +1 -1
- package/dist/inputs.js +1 -1
- package/dist/layout-client.d.ts +6 -3
- package/dist/layout-client.js +37 -3
- package/dist/markup.d.ts +1 -5
- package/dist/markup.js +0 -1
- package/dist/navigation-client.d.ts +1 -1
- package/dist/navigation-client.js +23 -2
- package/package.json +1 -1
package/dist/data-grid.js
CHANGED
|
@@ -149,6 +149,9 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
|
|
|
149
149
|
id: "detail", enableSorting: false, enableHiding: false, header: () => null,
|
|
150
150
|
cell: ({ row }) => _jsx(IconButton, { icon: "chevron--right", label: s.dataGridDetails, size: "sm", variant: "ghost", "aria-expanded": detailRowId === row.id, onClick: () => setDetailRowId(detailRowId === row.id ? null : row.id) }),
|
|
151
151
|
}] : allColumns, [allColumns, renderDetail, detailRowId, s]);
|
|
152
|
+
// A função acima precisa da tabela, e a tabela é o que esta chamada devolve: a referência fecha o
|
|
153
|
+
// círculo. Ela é lida só quando o motor filtra, que é sempre depois da primeira montagem.
|
|
154
|
+
const tabelaRef = React.useRef(null);
|
|
152
155
|
const table = useReactTable({
|
|
153
156
|
data, columns: colunas, getRowId,
|
|
154
157
|
state: { sorting, globalFilter, rowSelection, columnFilters, columnVisibility, columnSizing, ...(paginated ? { pagination } : {}) },
|
|
@@ -174,6 +177,22 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
|
|
|
174
177
|
getCoreRowModel: getCoreRowModel(),
|
|
175
178
|
getSortedRowModel: getSortedRowModel(),
|
|
176
179
|
getFilteredRowModel: getFilteredRowModel(),
|
|
180
|
+
// A BUSCA NÃO DEPENDE DE QUEM VEM PRIMEIRO (A-11, 23/09/2026). A regra do motor (TanStack v8,
|
|
181
|
+
// `GlobalFiltering.js`, `getColumnCanGlobalFilter`) olha SÓ a primeira linha: se o valor dela
|
|
182
|
+
// não for texto nem número, a coluna sai da busca. Medido num app com 298 endereços: o primeiro
|
|
183
|
+
// não tinha fabricante, e procurar "Hikvision" dava zero onde havia 17. A regra daqui é a mesma
|
|
184
|
+
// pergunta — "esta coluna guarda texto ou número?" — respondida pelo primeiro valor PRESENTE,
|
|
185
|
+
// não pela primeira linha. `enableGlobalFilter:false` na coluna continua mandando: o motor
|
|
186
|
+
// confere essa chave antes de chamar esta função.
|
|
187
|
+
getColumnCanGlobalFilter: coluna => {
|
|
188
|
+
for (const linha of tabelaRef.current?.getCoreRowModel().flatRows ?? []) {
|
|
189
|
+
const v = linha.getValue(coluna.id);
|
|
190
|
+
if (v == null || v === "")
|
|
191
|
+
continue;
|
|
192
|
+
return typeof v === "string" || typeof v === "number";
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
},
|
|
177
196
|
enableRowSelection: !!selectable,
|
|
178
197
|
...(paginated ? { getPaginationRowModel: getPaginationRowModel() } : {}),
|
|
179
198
|
// só quando há faceta: os dois varrem os dados para montar o mapa de valores
|
|
@@ -183,6 +202,7 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
|
|
|
183
202
|
// longe daqui, em `columns` e em `row.original`.
|
|
184
203
|
...(facetIds.size ? { getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues() } : {}),
|
|
185
204
|
});
|
|
205
|
+
tabelaRef.current = table;
|
|
186
206
|
const rows = table.getRowModel().rows;
|
|
187
207
|
// O TAB STOP ROVING, e o `clamp` não é zelo: filtrar, ordenar ou paginar troca as linhas sob
|
|
188
208
|
// o foco. Sem ele a grade inteira ficaria `tabIndex=-1` e sumiria da ordem do Tab — é o mesmo
|
package/dist/graph.d.ts
CHANGED
package/dist/graph.js
CHANGED
|
@@ -39,7 +39,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
39
39
|
// Quem importa a folha é o CONSUMIDOR, uma vez, como já importa o `@aurea-uds/core/css`: um
|
|
40
40
|
// `import "…css"` dentro deste módulo quebraria em Node puro e o build daqui é `tsc` só.
|
|
41
41
|
import React from "react";
|
|
42
|
-
import { ReactFlow, ReactFlowProvider, Background, Handle, Position, applyNodeChanges } from "@xyflow/react";
|
|
42
|
+
import { ReactFlow, ReactFlowProvider, Background, BaseEdge, Handle, Position, applyNodeChanges, getBezierPath } from "@xyflow/react";
|
|
43
43
|
import { cx, useAureaStrings } from "./internal.js";
|
|
44
44
|
// ── O layout ─────────────────────────────────────────────────────────────────────────────
|
|
45
45
|
// O motor recebe `x`/`y` PRONTOS: ele não posiciona nada. Sem isto, um grafo sem coordenadas
|
|
@@ -53,6 +53,17 @@ import { cx, useAureaStrings } from "./internal.js";
|
|
|
53
53
|
// subida é o `elkjs`, que é o que o langfuse usa, e ele é DEPENDÊNCIA NOVA: entra com
|
|
54
54
|
// autorização, não de contrabando.
|
|
55
55
|
const LARGURA = 180, ALTURA = 52, VAO_X = 90, VAO_Y = 24;
|
|
56
|
+
// O VÃO ENTRE COLUNAS CRESCE COM O RÓTULO (A-09, 23/09/2026). Com o rótulo legível, a primeira
|
|
57
|
+
// imagem mostrou o que o preto escondia: "Te1/1/1 ↔ Te1/0/1" é mais largo que 90px e entrava por
|
|
58
|
+
// baixo dos nós. O layout roda também no servidor, onde não há texto para medir, então a largura
|
|
59
|
+
// é ESTIMADA com números medidos, não chutados: o rótulo é `--text-xs` (0.8125rem = 13px com a
|
|
60
|
+
// raiz de 16px), e no Chromium, com a IBM Plex Sans 400 carregada, o algarismo mede 0,600 da
|
|
61
|
+
// altura da letra e o texto de enlace ("Te1/1/1 ↔ Te1/0/1") mede 0,513 — o algarismo é o teto
|
|
62
|
+
// do texto comum, e é ele que entra. Mais os 2 × 4px do fundo do rótulo (padrão do motor) e uma
|
|
63
|
+
// folga de 16px de cada lado para a linha aparecer antes e depois dele. Nunca encolhe abaixo de
|
|
64
|
+
// `VAO_X`: grafo sem rótulo sai idêntico ao de antes.
|
|
65
|
+
const FONTE_DO_ROTULO = 13, GLIFO_DO_ROTULO = 0.6, FUNDO_X = 8, FOLGA_X = 32;
|
|
66
|
+
const vaoEntreColunas = (edges) => Math.max(VAO_X, ...edges.map(e => e.label ? Math.ceil(e.label.length * FONTE_DO_ROTULO * GLIFO_DO_ROTULO) + FUNDO_X + FOLGA_X : 0));
|
|
56
67
|
function dispor(nodes, edges) {
|
|
57
68
|
const entram = new Map();
|
|
58
69
|
for (const n of nodes)
|
|
@@ -79,13 +90,14 @@ function dispor(nodes, edges) {
|
|
|
79
90
|
};
|
|
80
91
|
for (const n of nodes)
|
|
81
92
|
calcular(n.id);
|
|
93
|
+
const vaoX = vaoEntreColunas(edges);
|
|
82
94
|
const ocupacao = new Map();
|
|
83
95
|
const posicoes = new Map();
|
|
84
96
|
for (const n of nodes) {
|
|
85
97
|
const d = profundidade.get(n.id) ?? 0;
|
|
86
98
|
const linha = ocupacao.get(d) ?? 0;
|
|
87
99
|
ocupacao.set(d, linha + 1);
|
|
88
|
-
posicoes.set(n.id, { x: d * (LARGURA +
|
|
100
|
+
posicoes.set(n.id, { x: d * (LARGURA + vaoX), y: linha * (ALTURA + VAO_Y) });
|
|
89
101
|
}
|
|
90
102
|
// A EXTENSÃO existe para o SERVIDOR: sem ela o `fitView` não tem viewport para caber, e o
|
|
91
103
|
// grafo renderizado fora do navegador sai ancorado no canto, com metade dos nós do lado de
|
|
@@ -97,7 +109,7 @@ function dispor(nodes, edges) {
|
|
|
97
109
|
// interno o acionava sem ser prop de ninguém.
|
|
98
110
|
const colunas = Math.max(...[...ocupacao.keys()], 0) + 1;
|
|
99
111
|
const linhas = Math.max(...[...ocupacao.values()], 1);
|
|
100
|
-
return { posicoes, extensao: { larg: colunas * LARGURA + (colunas - 1) *
|
|
112
|
+
return { posicoes, extensao: { larg: colunas * LARGURA + (colunas - 1) * vaoX, alt: linhas * ALTURA + (linhas - 1) * VAO_Y } };
|
|
101
113
|
}
|
|
102
114
|
// O nó é NOSSO, e é o ponto em que este componente deixa de ser "React Flow com outra cor".
|
|
103
115
|
// O `GraphNode.tsx` do langfuse declara o motivo em uma linha — *"Real-HTML accessibility (the
|
|
@@ -115,6 +127,47 @@ function NoAurea({ data }) {
|
|
|
115
127
|
: _jsx("span", { className: "graph-node-body", children: corpo }), d.conectavel && _jsx(Handle, { type: "source", position: Position.Right, isConnectable: true, className: "graph-handle" })] });
|
|
116
128
|
}
|
|
117
129
|
const TIPOS = { aurea: NoAurea };
|
|
130
|
+
// ── A ARESTA, e por que ela também é nossa (A-09 e A-10, 23/09/2026) ─────────────────────────
|
|
131
|
+
// A-09 · O RÓTULO SAÍA PRETO SOBRE PRETO no tema escuro. O core declara `--xy-edge-label-color` e
|
|
132
|
+
// `--xy-edge-label-background-color` desde o H14, e NINGUÉM AS LIA: quem consome essas duas é o
|
|
133
|
+
// `style.css` do motor — a identidade dele, que esta casa proíbe —, e não o `base.css` que o
|
|
134
|
+
// consumidor importa. Medido em 23/09 num app que instalou a 0.8.7: `fill: rgb(0,0,0)` no texto e
|
|
135
|
+
// no fundo. As variáveis continuam sendo a fonte, e agora quem as lê é o próprio rótulo.
|
|
136
|
+
//
|
|
137
|
+
// A-10 · ARESTAS PARALELAS. Com `id` distinto as duas existem, mas o motor desenha as duas no
|
|
138
|
+
// MESMO caminho e os rótulos se sobrepõem. Cada uma sai de uma altura própria do nó: as `n`
|
|
139
|
+
// paralelas dividem a altura do nó em `n + 1` partes iguais. Uma aresta só fica no meio, que é o
|
|
140
|
+
// desenho de antes. O número vem de `ALTURA`, a do próprio nó — não há distância inventada aqui.
|
|
141
|
+
const ROTULO = { fill: "var(--xy-edge-label-color)", fontSize: "var(--text-xs)" };
|
|
142
|
+
const FUNDO_DO_ROTULO = { fill: "var(--xy-edge-label-background-color)" };
|
|
143
|
+
function ArestaAurea({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, label, data, markerEnd, style }) {
|
|
144
|
+
const dy = data?.deslocamento ?? 0;
|
|
145
|
+
const [caminho, lx, ly] = getBezierPath({ sourceX, sourceY: sourceY + dy, sourcePosition, targetX, targetY: targetY + dy, targetPosition });
|
|
146
|
+
return _jsx(BaseEdge, { id: id, path: caminho, labelX: lx, labelY: ly, label: label, markerEnd: markerEnd, style: style, labelStyle: ROTULO, labelShowBg: true, labelBgStyle: FUNDO_DO_ROTULO });
|
|
147
|
+
}
|
|
148
|
+
const TIPOS_DE_ARESTA = { aurea: ArestaAurea };
|
|
149
|
+
/** Identidade e deslocamento de cada aresta — interno; o teste lê o resultado no DOM. */
|
|
150
|
+
function arestasParalelas(edges) {
|
|
151
|
+
const total = new Map();
|
|
152
|
+
for (const e of edges) {
|
|
153
|
+
const k = `${e.from}->${e.to}`;
|
|
154
|
+
total.set(k, (total.get(k) ?? 0) + 1);
|
|
155
|
+
}
|
|
156
|
+
const vista = new Map();
|
|
157
|
+
const usados = new Set();
|
|
158
|
+
return edges.map(e => {
|
|
159
|
+
const par = `${e.from}->${e.to}`;
|
|
160
|
+
const k = vista.get(par) ?? 0;
|
|
161
|
+
vista.set(par, k + 1);
|
|
162
|
+
const n = total.get(par);
|
|
163
|
+
// Sem `id`, a primeira do par mantém o id de sempre; a segunda em diante ganha o índice.
|
|
164
|
+
let id = e.id ?? (k === 0 ? par : `${par}#${k}`);
|
|
165
|
+
while (usados.has(id))
|
|
166
|
+
id = `${id}#`;
|
|
167
|
+
usados.add(id);
|
|
168
|
+
return { id, deslocamento: n > 1 ? ALTURA * ((k + 1) / (n + 1) - 1 / 2) : 0 };
|
|
169
|
+
});
|
|
170
|
+
}
|
|
118
171
|
// DependencyGraph — quem depende de quem.
|
|
119
172
|
//
|
|
120
173
|
// O vocabulário da API é NOSSO — `{from,to}`, não `{source,target}`. Não é preciosismo: é o que
|
|
@@ -150,10 +203,13 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
|
|
|
150
203
|
// medido em 29/08/2026, a prop do fluxo não tira a classe `.selectable` da aresta, que é onde
|
|
151
204
|
// o `base.css` do motor põe `cursor:pointer`. A mão de ponteiro prometia um clique que a API
|
|
152
205
|
// da Aurea não atende — `onSelect` é do NÓ, e quem o atende é o <button> do corpo dele.
|
|
153
|
-
const arestasDoMotor = React.useMemo(() =>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
206
|
+
const arestasDoMotor = React.useMemo(() => {
|
|
207
|
+
const ident = arestasParalelas(edges);
|
|
208
|
+
return edges.map((e, i) => ({
|
|
209
|
+
id: ident[i].id, type: "aurea", source: e.from, target: e.to, label: e.label, animated: e.animated,
|
|
210
|
+
data: { deslocamento: ident[i].deslocamento }, selectable: false,
|
|
211
|
+
}));
|
|
212
|
+
}, [edges]);
|
|
157
213
|
// ── A MEDIDA TEM DE PODER VOLTAR, e isto foi achado NO NAVEGADOR ───────────────────────
|
|
158
214
|
// A primeira versão passava `nodes` e nenhum `onNodesChange`. O motor aceita calado e o
|
|
159
215
|
// resultado é um grafo INVISÍVEL: ele só tira o `visibility:hidden` do nó depois de gravar
|
|
@@ -178,6 +234,6 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
|
|
|
178
234
|
// que o `fitView` usa ENQUANTO ninguém mediu. Com o prefixo `initial`, o navegador sobrescreve
|
|
179
235
|
// na primeira medição — passar `width`/`height` direto no ReactFlow fixaria o tamanho e mataria
|
|
180
236
|
// a fluidez. O valor é a extensão do próprio layout, então no servidor o grafo cabe exato.
|
|
181
|
-
return _jsx("div", { className: cx("dependency-graph", className), style: height ? { blockSize: height } : undefined, role: "group", "aria-label": label ?? s.graphLabel, ...props, children: _jsx(ReactFlowProvider, { initialNodes: nosDoMotor, initialEdges: arestasDoMotor, fitView: true, initialWidth: automatico.extensao.larg, initialHeight: automatico.extensao.alt, children: _jsx(ReactFlow, { nodes: nosVivos, onNodesChange: aoMudarNos, edges: arestasDoMotor, nodeTypes: TIPOS, fitView: true, proOptions: { hideAttribution: false }, nodesDraggable: !!onNodeMove, nodesConnectable: !!connectable, elementsSelectable: false, onNodeDragStop: onNodeMove ? (_, no) => onNodeMove(no.id, no.position) : undefined, onConnect: onConnect ? c => { if (c.source && c.target)
|
|
237
|
+
return _jsx("div", { className: cx("dependency-graph", className), style: height ? { blockSize: height } : undefined, role: "group", "aria-label": label ?? s.graphLabel, ...props, children: _jsx(ReactFlowProvider, { initialNodes: nosDoMotor, initialEdges: arestasDoMotor, fitView: true, initialWidth: automatico.extensao.larg, initialHeight: automatico.extensao.alt, children: _jsx(ReactFlow, { nodes: nosVivos, onNodesChange: aoMudarNos, edges: arestasDoMotor, nodeTypes: TIPOS, edgeTypes: TIPOS_DE_ARESTA, fitView: true, proOptions: { hideAttribution: false }, nodesDraggable: !!onNodeMove, nodesConnectable: !!connectable, elementsSelectable: false, onNodeDragStop: onNodeMove ? (_, no) => onNodeMove(no.id, no.position) : undefined, onConnect: onConnect ? c => { if (c.source && c.target)
|
|
182
238
|
onConnect({ from: c.source, to: c.target }); } : undefined, children: _jsx(Background, {}) }) }) });
|
|
183
239
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStat
|
|
|
2
2
|
export { useAureaStrings, useSpriteUrl, useTheme, useDensity } from "./internal.js";
|
|
3
3
|
export { type Responsive, type Breakpoint, type ContainerBreakpoint, type Orientation, ESCALA, ESCALA_CONTAINER } from "./pure.js";
|
|
4
4
|
export { ContainerScope, useValorResponsivo } from "./responsivo-runtime.js";
|
|
5
|
-
export { AspectRatio, InputGroup, InputGroupAddon, Label, Card, Stack, Cluster, Grid, KPI, DataList, Timeline, Prose, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Topbar, Kbd,
|
|
5
|
+
export { AspectRatio, InputGroup, InputGroupAddon, Label, Card, Stack, Cluster, Grid, KPI, DataList, Timeline, Prose, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Topbar, Kbd, Textarea, Checkbox, Radio, Switch, Range, formatBadgeCount, type BadgeVariant, type BadgeEmphasis, type BadgeSize, type BadgePlacement, type BadgeProps, type AvatarSize, type TopbarVariant } from "./markup.js";
|
|
6
6
|
export * from "./system.js";
|
|
7
7
|
export * from "./actions.js";
|
|
8
8
|
export * from "./feedback.js";
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ export { ContainerScope, useValorResponsivo } from "./responsivo-runtime.js";
|
|
|
51
51
|
// inteiro). O que muda é de ONDE ele vem — do módulo com a diretiva —, e aí ele volta a chegar
|
|
52
52
|
// como referência de CLIENTE e os 118,5 KB voltam com ele. Não é um erro: é o ganho do item O1
|
|
53
53
|
// se desfazendo em silêncio. É isso, exatamente isso, que o check 35 pega.
|
|
54
|
-
export { AspectRatio, InputGroup, InputGroupAddon, Label, Card, Stack, Cluster, Grid, KPI, DataList, Timeline, Prose, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Topbar, Kbd,
|
|
54
|
+
export { AspectRatio, InputGroup, InputGroupAddon, Label, Card, Stack, Cluster, Grid, KPI, DataList, Timeline, Prose, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Topbar, Kbd, Textarea, Checkbox, Radio, Switch, Range, formatBadgeCount } from "./markup.js";
|
|
55
55
|
export * from "./system.js";
|
|
56
56
|
export * from "./actions.js";
|
|
57
57
|
export * from "./feedback.js";
|
package/dist/inputs-client.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import React, { type HTMLAttributes, type InputHTMLAttributes, type RefAttributes, type ReactNode } from "react";
|
|
1
|
+
import React, { type ButtonHTMLAttributes, type HTMLAttributes, type InputHTMLAttributes, type RefAttributes, type ReactNode } from "react";
|
|
2
2
|
import { type Responsive } from "./pure.js";
|
|
3
3
|
import { type FieldSize } from "./markup.js";
|
|
4
4
|
import { type IconName } from "./system.js";
|
|
5
5
|
export { FileInput, matchesAccept, type FileRejection, type UploadContext, type UploadFn } from "./file-input.js";
|
|
6
6
|
export type FieldOrientation = "vertical" | "horizontal";
|
|
7
|
+
export type FieldLabelWidth = "auto" | (string & {});
|
|
7
8
|
export interface FieldProps extends HTMLAttributes<HTMLDivElement>, RefAttributes<HTMLDivElement> {
|
|
8
9
|
label: string;
|
|
9
10
|
hint?: ReactNode;
|
|
10
11
|
error?: ReactNode;
|
|
11
12
|
name?: string;
|
|
12
13
|
orientation?: Responsive<FieldOrientation>;
|
|
14
|
+
labelWidth?: FieldLabelWidth;
|
|
13
15
|
}
|
|
14
|
-
export declare function Field({ label, hint, error, children, className, id, name, orientation, ...props }: FieldProps): React.JSX.Element;
|
|
16
|
+
export declare function Field({ label, hint, error, children, className, id, name, orientation, labelWidth, style, ...props }: FieldProps): React.JSX.Element;
|
|
15
17
|
export declare function Form({ errors, onSubmit, children, className, ...props }: Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> & RefAttributes<HTMLFormElement> & {
|
|
16
18
|
errors?: Record<string, string | string[]>;
|
|
17
19
|
onSubmit?: (values: Record<string, unknown>) => void;
|
|
@@ -103,6 +105,37 @@ export declare function MultiCombobox({ items, value, onValueChange, onInputChan
|
|
|
103
105
|
"aria-describedby"?: string;
|
|
104
106
|
"aria-invalid"?: boolean | "true" | "false";
|
|
105
107
|
}): React.JSX.Element;
|
|
108
|
+
export interface SelectOption {
|
|
109
|
+
value: string;
|
|
110
|
+
label: ReactNode;
|
|
111
|
+
disabled?: boolean;
|
|
112
|
+
}
|
|
113
|
+
export interface SelectOptGroup {
|
|
114
|
+
label: ReactNode;
|
|
115
|
+
items: SelectOption[];
|
|
116
|
+
}
|
|
117
|
+
type SelectChange = {
|
|
118
|
+
target: {
|
|
119
|
+
value: string;
|
|
120
|
+
name?: string;
|
|
121
|
+
};
|
|
122
|
+
currentTarget: {
|
|
123
|
+
value: string;
|
|
124
|
+
name?: string;
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
export interface SelectProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "value" | "defaultValue" | "onChange" | "name">, RefAttributes<HTMLButtonElement> {
|
|
128
|
+
items?: SelectOption[] | SelectOptGroup[];
|
|
129
|
+
value?: string;
|
|
130
|
+
defaultValue?: string;
|
|
131
|
+
onChange?: (e: SelectChange) => void;
|
|
132
|
+
onValueChange?: (value: string) => void;
|
|
133
|
+
placeholder?: ReactNode;
|
|
134
|
+
name?: string;
|
|
135
|
+
required?: boolean;
|
|
136
|
+
size?: Responsive<FieldSize>;
|
|
137
|
+
}
|
|
138
|
+
export declare const Select: React.ForwardRefExoticComponent<Omit<SelectProps, "ref"> & RefAttributes<HTMLButtonElement>>;
|
|
106
139
|
export interface EditorBlock {
|
|
107
140
|
id: string;
|
|
108
141
|
kind?: ReactNode;
|
package/dist/inputs-client.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
6
6
|
import React, { forwardRef } from "react";
|
|
7
7
|
import { valorBase, peleDoEixo, soOValor } from "./pure.js";
|
|
8
8
|
import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
|
|
9
|
+
import { Select as BaseSelect } from "@base-ui/react/select";
|
|
9
10
|
import { Form as BaseForm } from "@base-ui/react/form";
|
|
10
11
|
import { Field as BaseField } from "@base-ui/react/field";
|
|
11
12
|
import { NumberField as BaseNumberField } from "@base-ui/react/number-field";
|
|
@@ -17,7 +18,7 @@ import { cx, useAureaStrings, usePortalContainer, useReorder } from "./internal.
|
|
|
17
18
|
// diretiva — são marcação, e quem escuta `onChange` é o consumidor. Voltam IMPORTADOS e não só
|
|
18
19
|
// reexportados porque o `ehControle` do `Field` compara por IDENTIDADE de referência: dois
|
|
19
20
|
// objetos diferentes com o mesmo nome quebrariam o rótulo do campo em silêncio.
|
|
20
|
-
import {
|
|
21
|
+
import { Textarea, Checkbox, Radio, Switch, Range, InputGroup, InputGroupAddon } from "./markup.js";
|
|
21
22
|
import { Icon } from "./system.js";
|
|
22
23
|
import { IconButton } from "./actions.js";
|
|
23
24
|
// FileInput mora em arquivo próprio (132 linhas — upload real, aborto, progresso) e é público
|
|
@@ -78,7 +79,7 @@ function elementoRotulavel(tipo) {
|
|
|
78
79
|
// regra que os outros: recebe `id` e espalha `...props` no `Input`, que é o nó focal.
|
|
79
80
|
return [Input, Select, Textarea, SearchField, PasswordField, Checkbox, Radio, Switch, Range, Combobox, MultiCombobox].includes(tipo);
|
|
80
81
|
}
|
|
81
|
-
export function Field({ label, hint, error, children, className, id, name, orientation, ...props }) {
|
|
82
|
+
export function Field({ label, hint, error, children, className, id, name, orientation, labelWidth, style, ...props }) {
|
|
82
83
|
const auto = React.useId();
|
|
83
84
|
// Fragment não cria nó no DOM. Um Fragment com um só controle é só sintaxe; com vários,
|
|
84
85
|
// Field precisa voltar para grupo em vez de pendurar `htmlFor` num id que nunca existirá.
|
|
@@ -122,7 +123,8 @@ export function Field({ label, hint, error, children, className, id, name, orien
|
|
|
122
123
|
// embrulho o `Field.Root` recebe o erro e não tem a quem entregá-lo, e o campo fica válido na
|
|
123
124
|
// árvore de acessibilidade enquanto a frase de erro aparece na tela. Pior que não mostrar.
|
|
124
125
|
const controleFinal = name ? _jsx(BaseField.Control, { render: controle }) : controle;
|
|
125
|
-
const
|
|
126
|
+
const estilo = labelWidth ? { ...style, "--field-label-width": labelWidth === "auto" ? "max-content" : labelWidth } : style;
|
|
127
|
+
const corpo = _jsxs("div", { className: cx("field", peleDoEixo("field", orientation, "vertical", "field"), className), ...(nomeAqui ? {} : { role: "group", "aria-labelledby": idRotulo, ...(rotulavel ? {} : { "aria-describedby": descrito }) }), style: estilo, ...props, children: [_jsxs("span", { className: "label", children: [nomeAqui ? _jsx("label", { htmlFor: idControle, children: label }) : _jsx("span", { id: idRotulo, children: label }), hint && _jsx("span", { className: "hint", id: idHint, children: hint })] }), controleFinal, error && _jsx("span", { className: "field-error", id: idErro, children: error }), name && _jsx(BaseField.Error, { className: "field-error" })] });
|
|
126
128
|
// `Field.Root` do motor entra SÓ quando há `name`: é ele que liga o campo à chave de `errors`
|
|
127
129
|
// do formulário. Ele não desenha nada — é contexto — então a pele não muda.
|
|
128
130
|
return name ? _jsx(BaseField.Root, { name: name, invalid: error ? true : undefined, render: corpo }) : corpo;
|
|
@@ -236,7 +238,11 @@ export function SegmentedControl({ items, value, onChange, label }) {
|
|
|
236
238
|
const ehAgrupado = (items) => items != null && items.length > 0 && typeof items[0] === "object" && items[0] != null && "items" in items[0];
|
|
237
239
|
// UM renderizador e UMA lista para os dois combobox: duas cópias da mesma árvore é como a
|
|
238
240
|
// segunda fica para trás na mudança seguinte.
|
|
239
|
-
|
|
241
|
+
// A COLUNA DO CHECK EXISTE SEMPRE (A-01, 23/09/2026). O `ItemIndicator` do Base UI não põe
|
|
242
|
+
// NADA no DOM quando o item não está escolhido; com um filho só, o texto caía na primeira coluna
|
|
243
|
+
// da grade — a de 16px — e quebrava em duas ou três linhas. O invólucro fica montado sempre, como
|
|
244
|
+
// o `.menu-mark` do `Menu` já faz, e o indicador aparece e some DENTRO dele.
|
|
245
|
+
const renderComboboxItem = (item) => _jsxs(BaseCombobox.Item, { value: item, className: "menu-item combobox-item", children: [_jsx("span", { className: "combobox-check", "aria-hidden": "true", children: _jsx(BaseCombobox.ItemIndicator, { children: _jsx(Icon, { name: "checkmark", size: "sm" }) }) }), _jsx("span", { children: item.label })] }, item.value);
|
|
240
246
|
const listaCombobox = (items) => _jsx(BaseCombobox.List, { children: ehAgrupado(items)
|
|
241
247
|
? (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: renderComboboxItem })] }, group.label)
|
|
242
248
|
: renderComboboxItem });
|
|
@@ -266,10 +272,41 @@ export function MultiCombobox({ items, value, onValueChange, onInputChange, load
|
|
|
266
272
|
const portal = usePortalContainer();
|
|
267
273
|
const autoId = React.useId();
|
|
268
274
|
const inputId = id ?? autoId;
|
|
269
|
-
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);
|
|
270
275
|
return _jsxs(BaseCombobox.Root, { multiple: true, items: items, value: value, onValueChange: soOValor(onValueChange), disabled: disabled, itemToStringLabel: (i) => i.label, filter: onInputChange ? null : undefined, onInputValueChange: onInputChange ? ((v, d) => { if (d.reason !== "item-press")
|
|
271
276
|
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", "aria-describedby": descrito, "aria-invalid": invalido })] }) }) }), _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 : (empty ?? s.comboboxEmpty) }), listaCombobox(items)] }) }) })] });
|
|
272
277
|
}
|
|
278
|
+
const ehGrupoDoSelect = (i) => typeof i === "object" && i != null && "items" in i;
|
|
279
|
+
// Lê `<option>`/`<optgroup>` como o navegador lê: `value` ausente vale o TEXTO da opção.
|
|
280
|
+
function opcoesDosFilhos(children) {
|
|
281
|
+
const lista = [];
|
|
282
|
+
React.Children.forEach(children, filho => {
|
|
283
|
+
if (!React.isValidElement(filho))
|
|
284
|
+
return;
|
|
285
|
+
const pr = filho.props;
|
|
286
|
+
if (filho.type === "option")
|
|
287
|
+
lista.push({ value: String(pr.value ?? React.Children.toArray(pr.children).join("")), label: pr.children, disabled: pr.disabled });
|
|
288
|
+
else if (filho.type === "optgroup")
|
|
289
|
+
lista.push({ label: pr.label, items: opcoesDosFilhos(pr.children) });
|
|
290
|
+
else if (filho.type === React.Fragment)
|
|
291
|
+
lista.push(...opcoesDosFilhos(pr.children));
|
|
292
|
+
});
|
|
293
|
+
return lista;
|
|
294
|
+
}
|
|
295
|
+
const itemDoSelect = (o) => _jsxs(BaseSelect.Item, { value: o.value, disabled: o.disabled, className: "menu-item combobox-item", children: [_jsx("span", { className: "combobox-check", "aria-hidden": "true", children: _jsx(BaseSelect.ItemIndicator, { children: _jsx(Icon, { name: "checkmark", size: "sm" }) }) }), _jsx(BaseSelect.ItemText, { children: o.label })] }, o.value);
|
|
296
|
+
export const Select = forwardRef(function Select({ items, children, value, defaultValue, onChange, onValueChange, placeholder, name, required, disabled, size, className, ...props }, ref) {
|
|
297
|
+
const portal = usePortalContainer();
|
|
298
|
+
const lista = items ?? opcoesDosFilhos(children);
|
|
299
|
+
const planas = lista.flatMap(i => ehGrupoDoSelect(i) ? i.items : [i]);
|
|
300
|
+
const inicial = value === undefined && defaultValue === undefined && placeholder === undefined ? planas.find(o => !o.disabled)?.value : defaultValue;
|
|
301
|
+
const mudar = (v) => {
|
|
302
|
+
const valor = v ?? "";
|
|
303
|
+
onValueChange?.(valor);
|
|
304
|
+
onChange?.({ target: { value: valor, name }, currentTarget: { value: valor, name } });
|
|
305
|
+
};
|
|
306
|
+
return _jsxs(BaseSelect.Root, { items: planas.map(o => ({ value: o.value, label: o.label })), ...(value !== undefined ? { value } : { defaultValue: inicial }), onValueChange: v => mudar(v), name: name, required: required, disabled: disabled, children: [_jsx(BaseSelect.Trigger, { ref: ref, className: cx("select", "select-trigger", peleDoEixo("select", size), className), ...props, children: _jsx(BaseSelect.Value, { placeholder: placeholder, className: "select-value" }) }), _jsx(BaseSelect.Portal, { container: portal, children: _jsx(BaseSelect.Positioner, { sideOffset: 6, alignItemWithTrigger: false, children: _jsx(BaseSelect.Popup, { className: "menu combobox-popup", children: _jsx(BaseSelect.List, { children: lista.map((i, n) => ehGrupoDoSelect(i)
|
|
307
|
+
? _jsxs(BaseSelect.Group, { className: "combobox-section", children: [_jsx(BaseSelect.GroupLabel, { className: "combobox-group-label", children: i.label }), i.items.map(itemDoSelect)] }, n)
|
|
308
|
+
: itemDoSelect(i)) }) }) }) })] });
|
|
309
|
+
});
|
|
273
310
|
export function BlockEditor({ blocks, onReorder, onRemove, label, className, ...props }) {
|
|
274
311
|
const s = useAureaStrings();
|
|
275
312
|
const bid = React.useId();
|
package/dist/inputs.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from "./inputs-client.js";
|
|
2
|
-
export {
|
|
2
|
+
export { Textarea, Checkbox, Radio, Switch, Range } from "./markup.js";
|
|
3
3
|
export { Label, InputGroup, InputGroupAddon, type AddonSide, type AddonLayout, type FieldSize } from "./markup.js";
|
package/dist/inputs.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// `ehControle` do `Field` compara por IDENTIDADE de referência, e dois objetos com o mesmo nome
|
|
7
7
|
// quebrariam o rótulo do campo em silêncio. Um caminho de import, um objeto.
|
|
8
8
|
export * from "./inputs-client.js";
|
|
9
|
-
export {
|
|
9
|
+
export { Textarea, Checkbox, Radio, Switch, Range } from "./markup.js";
|
|
10
10
|
// PORTADOS no merge de 28/08/2026, da linhagem da ATIVIDADE-2 — não existiam na `main`.
|
|
11
11
|
// Saem do `markup` pela mesma razão dos seis acima: nenhum usa hook, então chegam como
|
|
12
12
|
// componente de SERVIDOR e não arrastam a fronteira de cliente com eles.
|
package/dist/layout-client.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
|
|
2
2
|
import { type Orientation, type Responsive } from "./pure.js";
|
|
3
|
-
import { type TopbarVariant, type SidebarVariant } from "./navigation.js";
|
|
4
|
-
export declare function AppShell({ brand, navigation, topbar, topbarVariant, sidebarVariant, sidebarCollapsed, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
3
|
+
import { type TopbarVariant, type SidebarVariant, type SidebarItem } from "./navigation.js";
|
|
4
|
+
export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, sidebarVariant, sidebarCollapsed, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
5
5
|
brand: ReactNode;
|
|
6
|
-
navigation
|
|
6
|
+
navigation?: ReactNode;
|
|
7
|
+
navItems?: SidebarItem[];
|
|
8
|
+
currentNavId?: string;
|
|
9
|
+
navLabel?: string;
|
|
7
10
|
topbar?: ReactNode;
|
|
8
11
|
topbarVariant?: Exclude<TopbarVariant, "pill">;
|
|
9
12
|
sidebarVariant?: SidebarVariant;
|
package/dist/layout-client.js
CHANGED
|
@@ -3,10 +3,11 @@ 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 { useRef } from "react";
|
|
6
|
+
import { useEffect, useRef } from "react";
|
|
7
7
|
import { Separator as BaseSeparator } from "@base-ui/react/separator";
|
|
8
8
|
import { cx, useAureaStrings } from "./internal.js";
|
|
9
9
|
import { useValorResponsivo } from "./responsivo-runtime.js";
|
|
10
|
+
import { ESCALA } from "./escala.js";
|
|
10
11
|
import { IconButton } from "./actions.js";
|
|
11
12
|
import { Sidebar, Topbar } from "./navigation.js";
|
|
12
13
|
// ── Auxiliar de topo: mora ANTES do primeiro export, e a posição é obrigatória ───────────────
|
|
@@ -83,9 +84,42 @@ function focoDaGaveta(e) {
|
|
|
83
84
|
// então id fixo é estável e não colide. Sem alvo estável não há como o link existir.
|
|
84
85
|
const SHELL_MAIN_ID = "aurea-shell-main";
|
|
85
86
|
const SHELL_NAV_ID = "aurea-shell-nav";
|
|
86
|
-
|
|
87
|
+
// A GAVETA NÃO SOBREVIVE À SUBIDA PARA O DESKTOP (A-07, 23/09/2026). Aberta em tela estreita e
|
|
88
|
+
// alargada a janela, ela ficava no top layer — que é estado do DOM, e nenhuma media query tira um
|
|
89
|
+
// elemento de lá —, flutuando fora de posição, sem véu, e com o botão que a fecharia escondido pelo
|
|
90
|
+
// CSS acima de lg. Girar um tablet ou encaixar a janela de lado no Windows bastava. O número é o
|
|
91
|
+
// `ESCALA.lg`, gerado dos tokens pela mesma leitura que escreve o `@media (max-width:1023px)` do
|
|
92
|
+
// core: um número só, e o `consumidores-lote1.test` cobra que o do `aurea.js` é o mesmo.
|
|
93
|
+
function useGavetaFechaNoDesktop() {
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function")
|
|
96
|
+
return;
|
|
97
|
+
const mq = window.matchMedia(`(min-width: ${ESCALA.lg}px)`);
|
|
98
|
+
const aoMudar = (e) => {
|
|
99
|
+
if (!e.matches)
|
|
100
|
+
return;
|
|
101
|
+
const gaveta = document.getElementById(SHELL_NAV_ID);
|
|
102
|
+
try {
|
|
103
|
+
if (gaveta?.matches(":popover-open"))
|
|
104
|
+
gaveta.hidePopover();
|
|
105
|
+
}
|
|
106
|
+
catch { /* motor sem popover */ }
|
|
107
|
+
};
|
|
108
|
+
mq.addEventListener?.("change", aoMudar);
|
|
109
|
+
return () => mq.removeEventListener?.("change", aoMudar);
|
|
110
|
+
}, []);
|
|
111
|
+
}
|
|
112
|
+
// A LISTA DA LATERAL ENTRA PELO SHELL (A-03, 24/09/2026). O shell cria o `<Sidebar>` e só
|
|
113
|
+
// repassava `children`; quem queria a lista declarativa (`items` + `current`) tinha de passar OUTRO
|
|
114
|
+
// `<Sidebar>` como `navigation`, e saíam dois `<aside class="sidebar">` aninhados — duas bordas,
|
|
115
|
+
// dois fundos, dois raios de 22px. Com a escala de letras da 0.8.8 a caixa de dentro passou a CORTAR
|
|
116
|
+
// texto ("8.174" virava "8.17"), medido num app real a 1400px. `navItems`/`currentNavId` vão para o
|
|
117
|
+
// Sidebar que o shell já cria, e a caixa dupla deixa de ser o único caminho. `navigation` continua
|
|
118
|
+
// valendo para quem escreve a navegação à mão, e as duas se somam: a lista vem antes.
|
|
119
|
+
export function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant = "floating", sidebarVariant, sidebarCollapsed, children, className, ...props }) {
|
|
87
120
|
const s = useAureaStrings();
|
|
88
|
-
|
|
121
|
+
useGavetaFechaNoDesktop();
|
|
122
|
+
return _jsxs("div", { className: cx("app-shell", topbarVariant === "flush" && "app-shell-flush", className), ...props, children: [_jsx("a", { className: "skip-link", href: `#${SHELL_MAIN_ID}`, children: s.skipToContent }), _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", variant: sidebarVariant, collapsed: sidebarCollapsed, items: navItems, current: currentNavId, label: navLabel, onToggle: focoDaGaveta, children: navigation }), _jsx("main", { id: SHELL_MAIN_ID, tabIndex: -1, className: "content", children: children })] });
|
|
89
123
|
}
|
|
90
124
|
// ── Separator ────────────────────────────────────────────────────────────────────────────────
|
|
91
125
|
// A LINHA DO SISTEMA, e ela é de CLIENTE por necessidade, não por vizinhança.
|
package/dist/markup.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { type HTMLAttributes, type InputHTMLAttributes, type ReactElement, type ReactNode, type RefAttributes, type
|
|
1
|
+
import React, { type HTMLAttributes, type InputHTMLAttributes, type ReactElement, type ReactNode, type RefAttributes, type TextareaHTMLAttributes } from "react";
|
|
2
2
|
import { type Responsive } from "./pure.js";
|
|
3
3
|
import type { ComponentSize } from "./actions.js";
|
|
4
4
|
export type BadgeVariant = "neutral" | "primary" | "info" | "success" | "warning" | "danger" | "running" | "paused" | "offline" | "review";
|
|
@@ -92,10 +92,6 @@ export declare function Topbar({ variant, brand, children, className, ...props }
|
|
|
92
92
|
brand?: ReactNode;
|
|
93
93
|
}): React.JSX.Element;
|
|
94
94
|
export declare function Kbd({ children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement>): React.JSX.Element;
|
|
95
|
-
export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "size">, RefAttributes<HTMLSelectElement> {
|
|
96
|
-
size?: Responsive<FieldSize>;
|
|
97
|
-
}
|
|
98
|
-
export declare const Select: React.ForwardRefExoticComponent<Omit<SelectProps, "ref"> & RefAttributes<HTMLSelectElement>>;
|
|
99
95
|
export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement>, RefAttributes<HTMLTextAreaElement> {
|
|
100
96
|
size?: Responsive<FieldSize>;
|
|
101
97
|
}
|
package/dist/markup.js
CHANGED
|
@@ -127,7 +127,6 @@ export function Topbar({ variant = "floating", brand, children, className, ...pr
|
|
|
127
127
|
// morava fora da casa dele (no `internal.tsx`, por causa do `Button`); agora mora aqui, pelo
|
|
128
128
|
// mesmo motivo elevado a regra.
|
|
129
129
|
export function Kbd({ children, className, ...props }) { return _jsx("kbd", { className: cx("kbd", className), ...props, children: children }); }
|
|
130
|
-
export const Select = forwardRef(function Select({ className, size, ...props }, ref) { return _jsx("select", { ref: ref, className: cx("select", peleDoEixo("select", size), className), ...props }); });
|
|
131
130
|
export const Textarea = forwardRef(function Textarea({ className, size, ...props }, ref) { return _jsx("textarea", { ref: ref, className: cx("textarea", peleDoEixo("textarea", size), className), ...props }); });
|
|
132
131
|
// `size` nos três marcáveis pela mesma razão do campo: uma linha de formulário com um campo `sm`
|
|
133
132
|
// e um checkbox de tamanho fixo ao lado desalinha. O degrau vai na MARCA, não no rótulo.
|
|
@@ -72,7 +72,7 @@ export interface SidebarItem {
|
|
|
72
72
|
items?: SidebarItem[];
|
|
73
73
|
}
|
|
74
74
|
export type SidebarVariant = "floating" | "flush";
|
|
75
|
-
export declare function Sidebar({ items, current, collapsed, variant, label, children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement> & {
|
|
75
|
+
export declare function Sidebar({ items, current, collapsed, variant, label, children, className, onClick, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement> & {
|
|
76
76
|
items?: SidebarItem[];
|
|
77
77
|
current?: string;
|
|
78
78
|
collapsed?: boolean;
|
|
@@ -193,10 +193,31 @@ function sidebarList(items, ctx, sub, labelledBy) {
|
|
|
193
193
|
return _jsxs("li", { children: [ctx.collapsed ? _jsx(Tooltip, { content: it.label, side: "right", children: alvo }) : alvo, filhos && sidebarList(filhos, ctx, true, lid)] }, it.id);
|
|
194
194
|
}) });
|
|
195
195
|
}
|
|
196
|
-
|
|
196
|
+
// A GAVETA FECHA AO ESCOLHER (A-06, 23/09/2026). Abaixo de lg a lateral do `AppShell` é popover,
|
|
197
|
+
// e a biblioteca a abria e NUNCA a fechava por código: `hidePopover` tinha zero ocorrências. O
|
|
198
|
+
// usuário tocava "Filmes", a página trocava por baixo e a gaveta ficava na frente dela. Material 3,
|
|
199
|
+
// Fluent 2 e o guia de gaveta do iOS fecham ao escolher. O fechamento mora NA LATERAL, e não no
|
|
200
|
+
// item, para valer também para a navegação que o consumidor escreve e passa como `children` — o
|
|
201
|
+
// catálogo é um desses. Fora do popover (desktop, lateral solta) o teste de `:popover-open` falha e
|
|
202
|
+
// nada acontece. O `try` é porque `:popover-open` é seletor desconhecido em motor antigo, e lá o
|
|
203
|
+
// `matches` lança em vez de devolver falso.
|
|
204
|
+
function fecharGavetaAoEscolher(e) {
|
|
205
|
+
if (e.defaultPrevented)
|
|
206
|
+
return;
|
|
207
|
+
const gaveta = e.currentTarget;
|
|
208
|
+
const alvo = e.target?.closest?.("a[href], .sidebar-item");
|
|
209
|
+
if (!alvo || !gaveta.contains(alvo))
|
|
210
|
+
return;
|
|
211
|
+
try {
|
|
212
|
+
if (gaveta.matches(":popover-open"))
|
|
213
|
+
gaveta.hidePopover();
|
|
214
|
+
}
|
|
215
|
+
catch { /* motor sem popover: não há gaveta */ }
|
|
216
|
+
}
|
|
217
|
+
export function Sidebar({ items, current, collapsed, variant = "floating", label, children, className, onClick, ...props }) {
|
|
197
218
|
const s = useAureaStrings();
|
|
198
219
|
const baseId = React.useId();
|
|
199
|
-
return _jsxs("aside", { className: cx("sidebar", variant !== "floating" && `sidebar-${variant}`, collapsed && "sidebar-collapsed", className), ...props, children: [items && items.length > 0 && _jsx("nav", { className: "sidebar-nav", "aria-label": label ?? s.sidebarLabel, children: sidebarList(items, { baseId, current, collapsed }) }), children] });
|
|
220
|
+
return _jsxs("aside", { className: cx("sidebar", variant !== "floating" && `sidebar-${variant}`, collapsed && "sidebar-collapsed", className), onClick: e => { onClick?.(e); fecharGavetaAoEscolher(e); }, ...props, children: [items && items.length > 0 && _jsx("nav", { className: "sidebar-nav", "aria-label": label ?? s.sidebarLabel, children: sidebarList(items, { baseId, current, collapsed }) }), children] });
|
|
200
221
|
}
|
|
201
222
|
const LEGADO = {
|
|
202
223
|
flat: ["edge", "none"], surface: ["floating", "circle"], pill: ["floating", "pill"], dock: ["floating", "circle"]
|