@seedgrid/fe-components 2026.8.8 → 2026.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/seedgrid-components.manifest.json +12 -0
- package/dist/generated/compiledStyles.d.ts +1 -1
- package/dist/generated/compiledStyles.d.ts.map +1 -1
- package/dist/generated/compiledStyles.js +1 -1
- package/dist/i18n/en-US.d.ts.map +1 -1
- package/dist/i18n/en-US.js +1 -0
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +1 -0
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +1 -0
- package/dist/i18n/pt-BR.d.ts.map +1 -1
- package/dist/i18n/pt-BR.js +1 -0
- package/dist/i18n/pt-PT.d.ts.map +1 -1
- package/dist/i18n/pt-PT.js +1 -0
- package/dist/layout/SgTreeView.d.ts +46 -0
- package/dist/layout/SgTreeView.d.ts.map +1 -1
- package/dist/layout/SgTreeView.js +72 -6
- package/dist/sandbox.cjs +39 -39
- package/dist/style.css +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { ChevronDown, ChevronUp, Search } from "lucide-react";
|
|
4
|
+
import { ChevronDown, ChevronUp, Search, Sparkles } from "lucide-react";
|
|
5
5
|
import { SgInputText } from "../inputs/SgInputText";
|
|
6
6
|
import { SgButton } from "../buttons/SgButton";
|
|
7
7
|
import { t, useComponentsI18n } from "../i18n";
|
|
@@ -141,6 +141,31 @@ function setBranchChecked(id, value, childrenById, checked) {
|
|
|
141
141
|
checked.delete(x);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
function computeCascadeValue(id, childrenById, valueById) {
|
|
145
|
+
const kids = childrenById.get(id) ?? [];
|
|
146
|
+
if (!kids.length)
|
|
147
|
+
return { mixed: false, value: valueById.get(id) };
|
|
148
|
+
let acc = null;
|
|
149
|
+
for (const k of kids) {
|
|
150
|
+
const r = computeCascadeValue(k, childrenById, valueById);
|
|
151
|
+
if (r.mixed)
|
|
152
|
+
return { mixed: true };
|
|
153
|
+
if (acc === null)
|
|
154
|
+
acc = r;
|
|
155
|
+
else if (acc.mixed || acc.value !== r.value)
|
|
156
|
+
return { mixed: true };
|
|
157
|
+
}
|
|
158
|
+
return acc ?? { mixed: false, value: valueById.get(id) };
|
|
159
|
+
}
|
|
160
|
+
function setBranchValue(id, value, childrenById, valueById) {
|
|
161
|
+
const all = [id, ...collectDescendants(childrenById, id)];
|
|
162
|
+
for (const x of all) {
|
|
163
|
+
if (value === undefined)
|
|
164
|
+
valueById.delete(x);
|
|
165
|
+
else
|
|
166
|
+
valueById.set(x, value);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
144
169
|
function useControllable(opts, fallback) {
|
|
145
170
|
const isControlled = "value" in opts && opts.value !== undefined;
|
|
146
171
|
const [inner, setInner] = React.useState("defaultValue" in opts && opts.defaultValue !== undefined ? opts.defaultValue : fallback);
|
|
@@ -176,6 +201,10 @@ const densityMap = {
|
|
|
176
201
|
normal: { rowPy: "py-1", rowPx: "px-2", gap: "gap-2", caret: "h-6 w-6", indent: 16 },
|
|
177
202
|
comfortable: { rowPy: "py-1.5", rowPx: "px-2.5", gap: "gap-2.5", caret: "h-7 w-7", indent: 18 }
|
|
178
203
|
};
|
|
204
|
+
// Largura fixa de cada coluna de trilha (checkbox ou select), usada tanto na linha quanto no
|
|
205
|
+
// cabecalho -- e' o que deixa o texto do cabecalho ("Prioridade", "Tipo de conta") alinhado com
|
|
206
|
+
// o controle por baixo dele, em vez de so' o controle estreito sem legenda nenhuma.
|
|
207
|
+
const TRACK_CELL_CLASS = "w-[110px] shrink-0";
|
|
179
208
|
function toneClasses(tone) {
|
|
180
209
|
if (tone === "muted") {
|
|
181
210
|
return {
|
|
@@ -224,6 +253,13 @@ export const SgTreeView = React.forwardRef(function SgTreeView(props, ref) {
|
|
|
224
253
|
const searchId = React.useId();
|
|
225
254
|
const expandedSet = React.useMemo(() => new Set(expandedIds), [expandedIds]);
|
|
226
255
|
const checkedSet = React.useMemo(() => new Set(effectiveCheckedIds), [effectiveCheckedIds]);
|
|
256
|
+
// Um Set/Map por trilha, montado uma vez por render (nao por no) -- mesma logica de
|
|
257
|
+
// `checkedSet` acima, só que uma cópia por track em vez de uma única.
|
|
258
|
+
const tracks = props.tracks;
|
|
259
|
+
const hasTracks = !!tracks && tracks.length > 0;
|
|
260
|
+
const trackData = React.useMemo(() => (tracks ?? []).map((track) => track.kind === "boolean"
|
|
261
|
+
? { track, checkedSet: new Set(track.checkedIds) }
|
|
262
|
+
: { track, valueById: new Map(Object.entries(track.valueByNodeId)) }), [tracks]);
|
|
227
263
|
const { filtered, matchIds } = React.useMemo(() => filterTreeByQuery(nodes, search), [nodes, search]);
|
|
228
264
|
React.useEffect(() => {
|
|
229
265
|
if (!expandMatchesOnSearch)
|
|
@@ -321,10 +357,40 @@ export const SgTreeView = React.forwardRef(function SgTreeView(props, ref) {
|
|
|
321
357
|
const isOpen = expandedSet.has(node.id);
|
|
322
358
|
const isDisabled = !!node.disabled;
|
|
323
359
|
const checkedState = checkable ? computeCheckedState(node.id, maps.childrenById, checkedSet) : "unchecked";
|
|
324
|
-
return (_jsxs("div", { children: [_jsxs("div", { className: cn("flex items-center rounded-md", DY.rowPx, DY.rowPy, DY.gap, T.rowHover, isDisabled && "opacity-60"), style: { paddingLeft: 8 + depth * DY.indent }, children: [_jsx("button", { type: "button", className: cn(DY.caret, "shrink-0 rounded-md flex items-center justify-center", "hover:bg-sg-hover2 hover:bg-foreground/10", "focus:outline-none focus:ring-2 focus:ring-sg-focus/30", !hasChildren && "opacity-0 pointer-events-none"), onClick: () => hasChildren && !isDisabled && toggleExpand(node), "aria-label": isOpen ? t(i18n, "components.actions.collapse") : t(i18n, "components.actions.expand"), disabled: isDisabled, children: _jsx("span", { className: cn("transition-transform", isOpen && "rotate-90"), children: ">" }) }), checkable &&
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
360
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: cn("flex items-center rounded-md", DY.rowPx, DY.rowPy, DY.gap, T.rowHover, isDisabled && "opacity-60"), style: { paddingLeft: 8 + depth * DY.indent }, children: [_jsx("button", { type: "button", className: cn(DY.caret, "shrink-0 rounded-md flex items-center justify-center", "hover:bg-sg-hover2 hover:bg-foreground/10", "focus:outline-none focus:ring-2 focus:ring-sg-focus/30", !hasChildren && "opacity-0 pointer-events-none"), onClick: () => hasChildren && !isDisabled && toggleExpand(node), "aria-label": isOpen ? t(i18n, "components.actions.collapse") : t(i18n, "components.actions.expand"), disabled: isDisabled, children: _jsx("span", { className: cn("transition-transform", isOpen && "rotate-90"), children: ">" }) }), checkable &&
|
|
361
|
+
(() => {
|
|
362
|
+
const checkboxEl = (_jsx("input", { type: "checkbox", className: cn(SZ.checkbox, "rounded border border-sg-border bg-sg-surface", "focus:outline-none focus:ring-2 focus:ring-sg-focus/30"), disabled: isDisabled, checked: checkedState === "checked", ref: (el) => {
|
|
363
|
+
if (el)
|
|
364
|
+
el.indeterminate = checkedState === "indeterminate";
|
|
365
|
+
}, onChange: () => toggleCheck(node) }));
|
|
366
|
+
// So' empacota (e reserva a largura da coluna) quando ha' cabecalho pra alinhar --
|
|
367
|
+
// sem `tracks`, o checkbox continua exatamente como sempre foi.
|
|
368
|
+
return hasTracks ? (_jsx("div", { className: cn(TRACK_CELL_CLASS, "flex items-center"), children: checkboxEl })) : (checkboxEl);
|
|
369
|
+
})(), trackData.map(({ track, ...data }) => {
|
|
370
|
+
const trackDisabled = isDisabled || !!track.disabled?.(node);
|
|
371
|
+
const suggested = !!track.suggestedIds?.includes(node.id);
|
|
372
|
+
const suggestedIndicator = suggested ? (_jsx("span", { title: t(i18n, "components.tree.suggested"), className: "shrink-0", children: track.suggestedIcon ?? (_jsx(Sparkles, { className: cn(SZ.icon, "text-[rgb(var(--sg-primary-600))]") })) })) : null;
|
|
373
|
+
if (track.kind === "boolean") {
|
|
374
|
+
const checkedSetTrack = data.checkedSet;
|
|
375
|
+
const state = computeCheckedState(node.id, maps.childrenById, checkedSetTrack);
|
|
376
|
+
return (_jsxs("div", { className: cn(TRACK_CELL_CLASS, "flex items-center gap-1"), children: [_jsx("input", { type: "checkbox", "aria-label": track.ariaLabel, className: cn(SZ.checkbox, "shrink-0 rounded border border-sg-border bg-sg-surface", "focus:outline-none focus:ring-2 focus:ring-sg-focus/30"), disabled: trackDisabled, checked: state === "checked", ref: (el) => {
|
|
377
|
+
if (el)
|
|
378
|
+
el.indeterminate = state === "indeterminate";
|
|
379
|
+
}, onChange: () => {
|
|
380
|
+
const next = new Set(checkedSetTrack);
|
|
381
|
+
setBranchChecked(node.id, state !== "checked", maps.childrenById, next);
|
|
382
|
+
track.onCheckedChange(Array.from(next));
|
|
383
|
+
} }), suggestedIndicator] }, track.id));
|
|
384
|
+
}
|
|
385
|
+
const valueById = data.valueById;
|
|
386
|
+
const result = computeCascadeValue(node.id, maps.childrenById, valueById);
|
|
387
|
+
return (_jsxs("div", { className: cn(TRACK_CELL_CLASS, "flex items-center gap-1"), children: [_jsxs("select", { "aria-label": track.id, disabled: trackDisabled, value: result.mixed ? "" : (result.value ?? ""), className: cn(SZ.text, "w-full rounded-md border border-sg-border bg-sg-surface px-1 py-0.5", "focus:outline-none focus:ring-2 focus:ring-sg-focus/30"), onChange: (e) => {
|
|
388
|
+
const v = e.target.value === "" ? undefined : e.target.value;
|
|
389
|
+
const nextValueById = new Map(valueById);
|
|
390
|
+
setBranchValue(node.id, v, maps.childrenById, nextValueById);
|
|
391
|
+
track.onChange(Object.fromEntries(nextValueById));
|
|
392
|
+
}, children: [_jsx("option", { value: "", children: result.mixed ? (track.mixedLabel ?? "— misto —") : (track.placeholder ?? "—") }), track.options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }), suggestedIndicator] }, track.id));
|
|
393
|
+
}), node.icon && (_jsx("div", { className: cn("shrink-0", SZ.icon, iconTone === "primary" ? "text-[rgb(var(--sg-primary-600))]" : ""), children: node.icon })), _jsx("button", { type: "button", className: cn("flex-1 text-left rounded-md", SZ.text, isDisabled ? "cursor-not-allowed" : "cursor-pointer", "focus:outline-none focus:ring-2 focus:ring-sg-focus/30"), onClick: () => {
|
|
328
394
|
if (isDisabled)
|
|
329
395
|
return;
|
|
330
396
|
if (!hasChildren) {
|
|
@@ -337,6 +403,6 @@ export const SgTreeView = React.forwardRef(function SgTreeView(props, ref) {
|
|
|
337
403
|
}, disabled: isDisabled, children: node.label })] }), hasChildren && isOpen && _jsx("div", { children: node.children.map((c) => renderNode(c, depth + 1)) })] }, node.id));
|
|
338
404
|
};
|
|
339
405
|
const hasAny = filtered.length > 0;
|
|
340
|
-
return (_jsxs("div", { className: cn("w-full", className), style: style, children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [searchable && (_jsx(SgInputText, { id: searchId, labelText: searchPlaceholder, placeholder: searchPlaceholder, prefixIcon: _jsx(Search, { size: 16 }), clearButton: true, filled: true, inputProps: { value: search }, onChange: (value) => setSearch(value) })), _jsx(SgButton, { severity: "secondary", appearance: "ghost", size: "sm", shape: "rounded", onClick: expandAll, leftIcon: _jsx(ChevronDown, { className: "size-4" }), children: t(i18n, "components.actions.expandAll") }), _jsx(SgButton, { severity: "secondary", appearance: "ghost", size: "sm", shape: "rounded", onClick: collapseAll, leftIcon: _jsx(ChevronUp, { className: "size-4" }), children: t(i18n, "components.actions.collapseAll") }), checkable && (_jsx(SgButton, { severity: "danger", appearance: "outline", size: "sm", shape: "rounded", onClick: clearChecked, title: t(i18n, "components.actions.clearSelection"), children: t(i18n, "components.actions.clear") }))] }), _jsxs("div", { className: cn("rounded-sg border border-sg-border", T.panelBg, "text-sg-text"), children: [_jsx("div", { className: cn("overflow-auto", maxHeightClassName), children: _jsx("div", { className: "p-2", children: !hasAny ? (_jsx("div", { className: cn("p-6 text-center", SZ.text, T.subtle), children: emptyText })) : (filtered.map((n) => renderNode(n, 0))) }) }), confirmEnabled && confirmCfg && (_jsx("div", { className: cn("border-t border-sg-border px-3 py-2", (confirmCfg.sticky ?? true) && "sticky bottom-0 bg-sg-surface/95 bg-background/95 backdrop-blur"), children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("div", { className: cn("text-xs", T.subtle), children: confirmCfg.hint ?? (_jsxs(_Fragment, { children: [t(i18n, "components.tree.selected", { count: confirmSelectedIds.length }), confirmSelection === "leafOnly" ? t(i18n, "components.tree.selectedLeafs") : ""] })) }), _jsxs("div", { className: "flex items-center gap-2", children: [(confirmCfg.showCancel ?? false) && (_jsx("button", { type: "button", onClick: onCancel, className: cn("rounded-md border border-sg-border", T.panelBg, "text-sg-text hover:bg-sg-hover", "focus:outline-none focus:ring-2 focus:ring-sg-focus/30", SZ.btn), children: confirmCfg.cancelLabel ?? t(i18n, "components.actions.cancel") })), _jsx(SgButton, { severity: "secondary", appearance: "outline", size: "md", shape: "rounded", onClick: clearChecked, disabled: !!confirmDisabled, children: t(i18n, "components.actions.clear") }), _jsx(SgButton, { severity: "primary", appearance: "solid", elevation: "sm", size: "md", shape: "rounded", disabled: !!confirmDisabled, onClick: onConfirm, children: confirmCfg.label ?? t(i18n, "components.actions.confirm") })] })] }) }))] })] }));
|
|
406
|
+
return (_jsxs("div", { className: cn("w-full", className), style: style, children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [searchable && (_jsx(SgInputText, { id: searchId, labelText: searchPlaceholder, placeholder: searchPlaceholder, prefixIcon: _jsx(Search, { size: 16 }), clearButton: true, filled: true, inputProps: { value: search }, onChange: (value) => setSearch(value) })), _jsx(SgButton, { severity: "secondary", appearance: "ghost", size: "sm", shape: "rounded", onClick: expandAll, leftIcon: _jsx(ChevronDown, { className: "size-4" }), children: t(i18n, "components.actions.expandAll") }), _jsx(SgButton, { severity: "secondary", appearance: "ghost", size: "sm", shape: "rounded", onClick: collapseAll, leftIcon: _jsx(ChevronUp, { className: "size-4" }), children: t(i18n, "components.actions.collapseAll") }), checkable && (_jsx(SgButton, { severity: "danger", appearance: "outline", size: "sm", shape: "rounded", onClick: clearChecked, title: t(i18n, "components.actions.clearSelection"), children: t(i18n, "components.actions.clear") }))] }), _jsxs("div", { className: cn("rounded-sg border border-sg-border", T.panelBg, "text-sg-text"), children: [hasTracks && (_jsxs("div", { className: cn("flex items-center border-b border-sg-border", DY.rowPx, DY.rowPy, DY.gap, SZ.text, "font-semibold text-sg-muted"), children: [_jsx("span", { className: cn(DY.caret, "shrink-0") }), checkable && (_jsx("div", { className: cn(TRACK_CELL_CLASS, "truncate"), title: props.checkableLabel, children: props.checkableLabel })), (tracks ?? []).map((track) => (_jsx("div", { className: cn(TRACK_CELL_CLASS, "truncate"), title: track.label, children: track.label }, track.id)))] })), _jsx("div", { className: cn("overflow-auto", maxHeightClassName), children: _jsx("div", { className: "p-2", children: !hasAny ? (_jsx("div", { className: cn("p-6 text-center", SZ.text, T.subtle), children: emptyText })) : (filtered.map((n) => renderNode(n, 0))) }) }), confirmEnabled && confirmCfg && (_jsx("div", { className: cn("border-t border-sg-border px-3 py-2", (confirmCfg.sticky ?? true) && "sticky bottom-0 bg-sg-surface/95 bg-background/95 backdrop-blur"), children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("div", { className: cn("text-xs", T.subtle), children: confirmCfg.hint ?? (_jsxs(_Fragment, { children: [t(i18n, "components.tree.selected", { count: confirmSelectedIds.length }), confirmSelection === "leafOnly" ? t(i18n, "components.tree.selectedLeafs") : ""] })) }), _jsxs("div", { className: "flex items-center gap-2", children: [(confirmCfg.showCancel ?? false) && (_jsx("button", { type: "button", onClick: onCancel, className: cn("rounded-md border border-sg-border", T.panelBg, "text-sg-text hover:bg-sg-hover", "focus:outline-none focus:ring-2 focus:ring-sg-focus/30", SZ.btn), children: confirmCfg.cancelLabel ?? t(i18n, "components.actions.cancel") })), _jsx(SgButton, { severity: "secondary", appearance: "outline", size: "md", shape: "rounded", onClick: clearChecked, disabled: !!confirmDisabled, children: t(i18n, "components.actions.clear") }), _jsx(SgButton, { severity: "primary", appearance: "solid", elevation: "sm", size: "md", shape: "rounded", disabled: !!confirmDisabled, onClick: onConfirm, children: confirmCfg.label ?? t(i18n, "components.actions.confirm") })] })] }) }))] })] }));
|
|
341
407
|
});
|
|
342
408
|
SgTreeView.displayName = "SgTreeView";
|