@seedgrid/fe-components 2026.3.19 → 2026.3.26
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/buttons/SgFloatActionButton.d.ts.map +1 -1
- package/dist/buttons/SgFloatActionButton.js +5 -26
- package/dist/buttons/SgSplitButton.d.ts.map +1 -1
- package/dist/buttons/SgSplitButton.js +3 -1
- package/dist/buttons/fab-helpers.d.ts +6 -0
- package/dist/buttons/fab-helpers.d.ts.map +1 -0
- package/dist/buttons/fab-helpers.js +29 -0
- package/dist/commons/SgAvatar.d.ts.map +1 -1
- package/dist/commons/SgAvatar.js +6 -3
- package/dist/commons/SgBadge.d.ts.map +1 -1
- package/dist/commons/SgBadge.js +5 -2
- package/dist/commons/SgToast.d.ts.map +1 -1
- package/dist/commons/SgToast.js +3 -1
- package/dist/commons/SgToaster.d.ts.map +1 -1
- package/dist/commons/SgToaster.js +3 -1
- package/dist/environment/SgEnvironmentProvider.d.ts.map +1 -1
- package/dist/environment/SgEnvironmentProvider.js +10 -15
- package/dist/environment/persistent-state.d.ts +22 -0
- package/dist/environment/persistent-state.d.ts.map +1 -0
- package/dist/environment/persistent-state.js +33 -0
- package/dist/gadgets/calendar/SgCalendar.d.ts.map +1 -1
- package/dist/gadgets/calendar/SgCalendar.js +5 -23
- package/dist/gadgets/clock/SgClock.d.ts.map +1 -1
- package/dist/gadgets/clock/SgClock.js +12 -10
- package/dist/gadgets/clock/themes/SgClockThemePicker.d.ts +2 -1
- package/dist/gadgets/clock/themes/SgClockThemePicker.d.ts.map +1 -1
- package/dist/gadgets/clock/themes/SgClockThemePicker.js +23 -28
- package/dist/gadgets/clock/themes/search.d.ts +9 -0
- package/dist/gadgets/clock/themes/search.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/search.js +15 -0
- package/dist/gadgets/gauge/SgLinearGauge.d.ts.map +1 -1
- package/dist/gadgets/gauge/SgLinearGauge.js +39 -28
- package/dist/gadgets/gauge/SgRadialGauge.d.ts.map +1 -1
- package/dist/gadgets/gauge/SgRadialGauge.js +44 -37
- package/dist/gadgets/gauge/math.d.ts +90 -0
- package/dist/gadgets/gauge/math.d.ts.map +1 -0
- package/dist/gadgets/gauge/math.js +81 -0
- package/dist/gadgets/qr-code/SgQRCode.d.ts.map +1 -1
- package/dist/gadgets/qr-code/SgQRCode.js +3 -1
- package/dist/i18n/en-US.d.ts.map +1 -1
- package/dist/i18n/en-US.js +97 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +153 -57
- package/dist/i18n/fr.d.ts +3 -0
- package/dist/i18n/fr.d.ts.map +1 -0
- package/dist/i18n/fr.js +206 -0
- package/dist/i18n/index.d.ts +5 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +50 -14
- package/dist/i18n/pt-BR.d.ts.map +1 -1
- package/dist/i18n/pt-BR.js +97 -1
- package/dist/i18n/pt-PT.d.ts.map +1 -1
- package/dist/i18n/pt-PT.js +97 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/inputs/SgAutocomplete.d.ts +1 -1
- package/dist/inputs/SgAutocomplete.d.ts.map +1 -1
- package/dist/inputs/SgAutocomplete.js +7 -4
- package/dist/inputs/SgCheckboxGroup.d.ts +2 -6
- package/dist/inputs/SgCheckboxGroup.d.ts.map +1 -1
- package/dist/inputs/SgCheckboxGroup.js +6 -6
- package/dist/inputs/SgCombobox.d.ts.map +1 -1
- package/dist/inputs/SgCombobox.js +11 -2
- package/dist/inputs/SgInputBirthDate.d.ts.map +1 -1
- package/dist/inputs/SgInputBirthDate.js +6 -1
- package/dist/inputs/SgInputCNPJ.d.ts +3 -1
- package/dist/inputs/SgInputCNPJ.d.ts.map +1 -1
- package/dist/inputs/SgInputCNPJ.js +4 -3
- package/dist/inputs/SgInputCPF.d.ts +3 -1
- package/dist/inputs/SgInputCPF.d.ts.map +1 -1
- package/dist/inputs/SgInputCPF.js +8 -3
- package/dist/inputs/SgInputCPFCNPJ.d.ts +3 -1
- package/dist/inputs/SgInputCPFCNPJ.d.ts.map +1 -1
- package/dist/inputs/SgInputCPFCNPJ.js +8 -3
- package/dist/inputs/SgInputCurrency.d.ts +3 -7
- package/dist/inputs/SgInputCurrency.d.ts.map +1 -1
- package/dist/inputs/SgInputCurrency.js +5 -2
- package/dist/inputs/SgInputDate.d.ts.map +1 -1
- package/dist/inputs/SgInputDate.js +6 -1
- package/dist/inputs/SgInputEmail.d.ts.map +1 -1
- package/dist/inputs/SgInputEmail.js +1 -1
- package/dist/inputs/SgInputNumber.d.ts +3 -7
- package/dist/inputs/SgInputNumber.d.ts.map +1 -1
- package/dist/inputs/SgInputNumber.js +5 -2
- package/dist/inputs/SgInputOTP.d.ts +5 -12
- package/dist/inputs/SgInputOTP.d.ts.map +1 -1
- package/dist/inputs/SgInputOTP.js +7 -4
- package/dist/inputs/SgInputPassword.d.ts.map +1 -1
- package/dist/inputs/SgInputPassword.js +1 -1
- package/dist/inputs/SgInputPhone.d.ts +3 -1
- package/dist/inputs/SgInputPhone.d.ts.map +1 -1
- package/dist/inputs/SgInputPhone.js +2 -1
- package/dist/inputs/SgInputPostalCode.d.ts.map +1 -1
- package/dist/inputs/SgInputPostalCode.js +2 -1
- package/dist/inputs/SgInputSelect.d.ts +4 -2
- package/dist/inputs/SgInputSelect.d.ts.map +1 -1
- package/dist/inputs/SgInputSelect.js +38 -3
- package/dist/inputs/SgInputText.d.ts +3 -7
- package/dist/inputs/SgInputText.d.ts.map +1 -1
- package/dist/inputs/SgInputText.js +5 -2
- package/dist/inputs/SgInputTextArea.d.ts +4 -2
- package/dist/inputs/SgInputTextArea.d.ts.map +1 -1
- package/dist/inputs/SgInputTextArea.js +37 -2
- package/dist/inputs/SgOrderList.d.ts +3 -1
- package/dist/inputs/SgOrderList.d.ts.map +1 -1
- package/dist/inputs/SgOrderList.js +19 -3
- package/dist/inputs/SgPickList.d.ts +3 -1
- package/dist/inputs/SgPickList.d.ts.map +1 -1
- package/dist/inputs/SgPickList.js +20 -4
- package/dist/inputs/SgRadioGroup.d.ts +2 -6
- package/dist/inputs/SgRadioGroup.d.ts.map +1 -1
- package/dist/inputs/SgRadioGroup.js +6 -6
- package/dist/inputs/SgRating.d.ts +2 -10
- package/dist/inputs/SgRating.d.ts.map +1 -1
- package/dist/inputs/SgRating.js +6 -3
- package/dist/inputs/SgSlider.d.ts +8 -2
- package/dist/inputs/SgSlider.d.ts.map +1 -1
- package/dist/inputs/SgSlider.js +62 -10
- package/dist/inputs/SgStepperInput.d.ts +8 -2
- package/dist/inputs/SgStepperInput.d.ts.map +1 -1
- package/dist/inputs/SgStepperInput.js +62 -8
- package/dist/inputs/SgTextEditor.d.ts +3 -1
- package/dist/inputs/SgTextEditor.d.ts.map +1 -1
- package/dist/inputs/SgTextEditor.js +24 -11
- package/dist/inputs/SgToggleSwitch.d.ts +3 -7
- package/dist/inputs/SgToggleSwitch.d.ts.map +1 -1
- package/dist/inputs/SgToggleSwitch.js +6 -3
- package/dist/layout/SgBreadcrumb.d.ts.map +1 -1
- package/dist/layout/SgBreadcrumb.js +7 -3
- package/dist/layout/SgCard.d.ts.map +1 -1
- package/dist/layout/SgCard.js +3 -1
- package/dist/layout/SgCarousel.d.ts.map +1 -1
- package/dist/layout/SgCarousel.js +3 -1
- package/dist/layout/SgExpandablePanel.d.ts.map +1 -1
- package/dist/layout/SgExpandablePanel.js +3 -1
- package/dist/layout/SgMenu.d.ts.map +1 -1
- package/dist/layout/SgMenu.js +173 -297
- package/dist/layout/SgPageControl.d.ts.map +1 -1
- package/dist/layout/SgPageControl.js +7 -3
- package/dist/layout/SgToolBar.d.ts.map +1 -1
- package/dist/layout/SgToolBar.js +19 -55
- package/dist/layout/SgTreeView.d.ts.map +1 -1
- package/dist/layout/SgTreeView.js +7 -3
- package/dist/layout/drag-position.d.ts +7 -0
- package/dist/layout/drag-position.d.ts.map +1 -0
- package/dist/layout/drag-position.js +30 -0
- package/dist/layout/menu-logic.d.ts +187 -0
- package/dist/layout/menu-logic.d.ts.map +1 -0
- package/dist/layout/menu-logic.js +349 -0
- package/dist/layout/toolbar-logic.d.ts +26 -0
- package/dist/layout/toolbar-logic.d.ts.map +1 -0
- package/dist/layout/toolbar-logic.js +38 -0
- package/dist/menus/SgDockMenu.d.ts.map +1 -1
- package/dist/menus/SgDockMenu.js +44 -120
- package/dist/menus/dock-menu-logic.d.ts +50 -0
- package/dist/menus/dock-menu-logic.d.ts.map +1 -0
- package/dist/menus/dock-menu-logic.js +113 -0
- package/dist/overlay/SgDialog.d.ts.map +1 -1
- package/dist/overlay/SgDialog.js +4 -2
- package/dist/overlay/SgPopup.d.ts.map +1 -1
- package/dist/overlay/SgPopup.js +4 -1
- package/dist/rhf.d.ts +8 -3
- package/dist/rhf.d.ts.map +1 -1
- package/dist/rhf.js +18 -1
- package/dist/sandbox.cjs +60 -60
- package/dist/wizard/SgWizard.d.ts.map +1 -1
- package/dist/wizard/SgWizard.js +20 -32
- package/dist/wizard/logic.d.ts +9 -0
- package/dist/wizard/logic.d.ts.map +1 -0
- package/dist/wizard/logic.js +20 -0
- package/package.json +78 -76
package/dist/layout/SgMenu.js
CHANGED
|
@@ -9,6 +9,8 @@ import { SgAutocomplete } from "../inputs/SgAutocomplete";
|
|
|
9
9
|
import { SgCard } from "./SgCard";
|
|
10
10
|
import { useSgDockLayout } from "./SgDockLayout";
|
|
11
11
|
import { useHasSgEnvironmentProvider, useSgPersistence } from "../environment/SgEnvironmentProvider";
|
|
12
|
+
import { t, useComponentsI18n } from "../i18n";
|
|
13
|
+
import { buildMenuMaps, collectMenuSearchEntries, collectParentChain, computeActiveSets, filterMenuNodes, flattenVisibleNodes, mergeExpandedIdsForActivePath, resolveEffectiveActiveId, resolveExpandedIdsToggle, resolveHorizontalDockAlign, resolveMegaMenuActiveNode, resolveMegaMenuHoverActiveId, resolveMegaMenuInteraction, resolveMenuAutocompleteItems, resolveMenuHintPosition as resolveMenuHintPositionHelper, resolveMenuKeyboardAction, resolveMenuLayoutState, resolveMenuNodeActionIntent, resolveMenuSearchSelectionState, resolveTieredActiveState, resolveTieredHoverIntent, resolveTieredNodeClickIntent } from "./menu-logic";
|
|
12
14
|
const ROOT_PARENT_ID = "__sg_menu_root__";
|
|
13
15
|
function cn(...parts) {
|
|
14
16
|
return parts.filter(Boolean).join(" ");
|
|
@@ -66,81 +68,6 @@ function useControllableState(value, defaultValue, onChange) {
|
|
|
66
68
|
}, [isControlled, onChange]);
|
|
67
69
|
return [current, setValue];
|
|
68
70
|
}
|
|
69
|
-
function buildMenuMaps(menu) {
|
|
70
|
-
const parentById = new Map();
|
|
71
|
-
const nodeById = new Map();
|
|
72
|
-
const childrenByParent = new Map();
|
|
73
|
-
const firstByUrl = new Map();
|
|
74
|
-
const walk = (nodes, parentId) => {
|
|
75
|
-
childrenByParent.set(parentId, nodes.map((node) => node.id));
|
|
76
|
-
for (const node of nodes) {
|
|
77
|
-
parentById.set(node.id, parentId);
|
|
78
|
-
nodeById.set(node.id, node);
|
|
79
|
-
if (node.url && !firstByUrl.has(node.url))
|
|
80
|
-
firstByUrl.set(node.url, node.id);
|
|
81
|
-
if (node.children?.length)
|
|
82
|
-
walk(node.children, node.id);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
walk(menu, ROOT_PARENT_ID);
|
|
86
|
-
return { parentById, nodeById, childrenByParent, firstByUrl };
|
|
87
|
-
}
|
|
88
|
-
function filterMenuNodes(nodes, query) {
|
|
89
|
-
const q = query.trim().toLowerCase();
|
|
90
|
-
if (!q)
|
|
91
|
-
return nodes;
|
|
92
|
-
const walk = (list) => {
|
|
93
|
-
const out = [];
|
|
94
|
-
for (const node of list) {
|
|
95
|
-
const selfMatch = node.label.toLowerCase().includes(q);
|
|
96
|
-
const children = node.children?.length ? walk(node.children) : [];
|
|
97
|
-
if (selfMatch || children.length > 0) {
|
|
98
|
-
out.push({
|
|
99
|
-
...node,
|
|
100
|
-
children: children.length > 0 ? children : undefined
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return out;
|
|
105
|
-
};
|
|
106
|
-
return walk(nodes);
|
|
107
|
-
}
|
|
108
|
-
function collectMenuSearchEntries(nodes, trail = [], out = []) {
|
|
109
|
-
for (const node of nodes) {
|
|
110
|
-
const nextTrail = [...trail, node.label];
|
|
111
|
-
out.push({
|
|
112
|
-
id: node.id,
|
|
113
|
-
label: node.label,
|
|
114
|
-
path: nextTrail.join(" > "),
|
|
115
|
-
group: trail[0] ?? node.label
|
|
116
|
-
});
|
|
117
|
-
if (node.children?.length)
|
|
118
|
-
collectMenuSearchEntries(node.children, nextTrail, out);
|
|
119
|
-
}
|
|
120
|
-
return out;
|
|
121
|
-
}
|
|
122
|
-
function flattenVisibleNodes(nodes, expandedSet, collapsed, forceExpand, depth = 0, parentId = ROOT_PARENT_ID, out = []) {
|
|
123
|
-
for (const node of nodes) {
|
|
124
|
-
out.push({ node, depth, parentId });
|
|
125
|
-
if (collapsed)
|
|
126
|
-
continue;
|
|
127
|
-
const hasChildren = !!node.children?.length;
|
|
128
|
-
const isOpen = forceExpand || expandedSet.has(node.id);
|
|
129
|
-
if (hasChildren && isOpen) {
|
|
130
|
-
flattenVisibleNodes(node.children, expandedSet, collapsed, forceExpand, depth + 1, node.id, out);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return out;
|
|
134
|
-
}
|
|
135
|
-
function collectParentChain(parentById, id) {
|
|
136
|
-
const out = [];
|
|
137
|
-
let current = parentById.get(id);
|
|
138
|
-
while (current && current !== ROOT_PARENT_ID) {
|
|
139
|
-
out.push(current);
|
|
140
|
-
current = parentById.get(current);
|
|
141
|
-
}
|
|
142
|
-
return out;
|
|
143
|
-
}
|
|
144
71
|
function firstChars(value, amount = 2) {
|
|
145
72
|
const normalized = value.trim().replace(/\s+/g, " ");
|
|
146
73
|
if (!normalized)
|
|
@@ -162,27 +89,6 @@ function sameStringArray(a, b) {
|
|
|
162
89
|
}
|
|
163
90
|
return true;
|
|
164
91
|
}
|
|
165
|
-
function computeActiveSets(nodes, activeId, activeUrl) {
|
|
166
|
-
const exact = new Set();
|
|
167
|
-
const branch = new Set();
|
|
168
|
-
const walk = (list) => {
|
|
169
|
-
let found = false;
|
|
170
|
-
for (const node of list) {
|
|
171
|
-
const childFound = node.children?.length ? walk(node.children) : false;
|
|
172
|
-
const selfActive = (activeId ? node.id === activeId : false) ||
|
|
173
|
-
(activeUrl ? node.url === activeUrl : false);
|
|
174
|
-
if (selfActive)
|
|
175
|
-
exact.add(node.id);
|
|
176
|
-
if (childFound)
|
|
177
|
-
branch.add(node.id);
|
|
178
|
-
if (selfActive || childFound)
|
|
179
|
-
found = true;
|
|
180
|
-
}
|
|
181
|
-
return found;
|
|
182
|
-
};
|
|
183
|
-
walk(nodes);
|
|
184
|
-
return { exact, branch };
|
|
185
|
-
}
|
|
186
92
|
function elevationClass(elevation) {
|
|
187
93
|
if (elevation === "sm")
|
|
188
94
|
return "shadow-sm";
|
|
@@ -194,7 +100,9 @@ function resolveIcon(node) {
|
|
|
194
100
|
return node.icon ?? null;
|
|
195
101
|
}
|
|
196
102
|
export function SgMenu(props) {
|
|
197
|
-
const
|
|
103
|
+
const i18n = useComponentsI18n();
|
|
104
|
+
const { id, menu, selection, brand, user, userMenu, menuStyle = "sidebar", menuVariantStyle = "panel", position = "left", density = "comfortable", indent = 16, collapsedWidth = 72, expandedWidth = 280, overlaySize, overlayBackdrop, dockable = false, dockZone, draggable = false, orientationDirection, mode = "multiple", expandedIds: expandedIdsProp, defaultExpandedIds = [], onExpandedIdsChange, collapsed, defaultCollapsed = false, onCollapsedChange, showCollapseButton = false, open, defaultOpen = false, onOpenChange, closeOnNavigate, pinned, defaultPinned = false, onPinnedChange, showPinButton = false, onNavigate, onAction, onItemClick, ariaLabel, keyboardNavigation = true, openSubmenuOnHover = false, search, elevation = "none", border = true, className, style, userSectionClassName, userSectionStyle, footer } = props;
|
|
105
|
+
const resolvedAriaLabel = ariaLabel ?? t(i18n, "components.menu.ariaLabel");
|
|
198
106
|
const densityCfg = density === "compact"
|
|
199
107
|
? {
|
|
200
108
|
row: "min-h-8 px-2 py-1 text-xs",
|
|
@@ -298,54 +206,47 @@ export function SgMenu(props) {
|
|
|
298
206
|
const dockDragMovedRef = React.useRef(false);
|
|
299
207
|
const dockHoverZoneRef = React.useRef(null);
|
|
300
208
|
const searchEntries = React.useMemo(() => collectMenuSearchEntries(menu), [menu]);
|
|
301
|
-
const autocompleteSource = React.useCallback((query) =>
|
|
302
|
-
const q = query.trim().toLowerCase();
|
|
303
|
-
const matches = !q
|
|
304
|
-
? searchEntries
|
|
305
|
-
: searchEntries.filter((entry) => entry.label.toLowerCase().includes(q) ||
|
|
306
|
-
entry.path.toLowerCase().includes(q));
|
|
307
|
-
return matches.slice(0, 120).map((entry) => ({
|
|
308
|
-
id: entry.id,
|
|
309
|
-
label: entry.label,
|
|
310
|
-
value: entry.path,
|
|
311
|
-
group: entry.group,
|
|
312
|
-
data: { nodeId: entry.id, path: entry.path }
|
|
313
|
-
}));
|
|
314
|
-
}, [searchEntries]);
|
|
209
|
+
const autocompleteSource = React.useCallback((query) => resolveMenuAutocompleteItems(searchEntries, query), [searchEntries]);
|
|
315
210
|
const filteredMenu = React.useMemo(() => (searchEnabled ? filterMenuNodes(menu, searchValue) : menu), [menu, searchEnabled, searchValue]);
|
|
316
211
|
const hasSearch = searchEnabled && searchValue.trim().length > 0;
|
|
317
212
|
const effectiveMenuStyle = isCollapsed || hasSearch ? "panel" : resolvedMenuStyle;
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
213
|
+
const layoutState = React.useMemo(() => resolveMenuLayoutState({
|
|
214
|
+
dockMode,
|
|
215
|
+
effectiveDockZone,
|
|
216
|
+
orientationDirection,
|
|
217
|
+
position,
|
|
218
|
+
horizontalDockAlign,
|
|
219
|
+
isCollapsed,
|
|
220
|
+
menuStyle,
|
|
221
|
+
effectiveMenuStyle,
|
|
222
|
+
pinnedState,
|
|
223
|
+
expandedWidthCss,
|
|
224
|
+
collapsedWidthCss
|
|
225
|
+
}), [
|
|
226
|
+
collapsedWidthCss,
|
|
227
|
+
dockMode,
|
|
228
|
+
effectiveDockZone,
|
|
229
|
+
effectiveMenuStyle,
|
|
230
|
+
expandedWidthCss,
|
|
231
|
+
horizontalDockAlign,
|
|
232
|
+
isCollapsed,
|
|
233
|
+
menuStyle,
|
|
234
|
+
orientationDirection,
|
|
235
|
+
pinnedState,
|
|
236
|
+
position
|
|
237
|
+
]);
|
|
238
|
+
const { resolvedPosition, isHorizontalDockZone, isVerticalDockZone, tieredOpenToLeft, isMegaMenuStyle, dockAlign, sidebarWidthCss } = layoutState;
|
|
331
239
|
const hideMenuHint = React.useCallback(() => {
|
|
332
240
|
menuHintAnchorRef.current = null;
|
|
333
241
|
setMenuHint(null);
|
|
334
242
|
}, []);
|
|
335
243
|
const resolveMenuHintPosition = React.useCallback((anchor) => {
|
|
336
|
-
if (!anchor.target.isConnected)
|
|
337
|
-
return null;
|
|
338
244
|
const rect = anchor.target.getBoundingClientRect();
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
return {
|
|
346
|
-
x: rect.left + (rect.width / 2),
|
|
347
|
-
y: rect.top - 8
|
|
348
|
-
};
|
|
245
|
+
return resolveMenuHintPositionHelper({
|
|
246
|
+
isConnected: anchor.target.isConnected,
|
|
247
|
+
rect,
|
|
248
|
+
placement: anchor.placement
|
|
249
|
+
});
|
|
349
250
|
}, []);
|
|
350
251
|
const showMenuHint = React.useCallback((target, text, placement = "top") => {
|
|
351
252
|
if (!text)
|
|
@@ -427,9 +328,12 @@ export function SgMenu(props) {
|
|
|
427
328
|
React.useEffect(() => () => {
|
|
428
329
|
clearDockDragVisual();
|
|
429
330
|
}, [clearDockDragVisual]);
|
|
430
|
-
const effectiveActiveId =
|
|
431
|
-
|
|
432
|
-
|
|
331
|
+
const effectiveActiveId = resolveEffectiveActiveId({
|
|
332
|
+
activeId: selection?.activeId,
|
|
333
|
+
activeUrl: selection?.activeUrl,
|
|
334
|
+
localActiveId,
|
|
335
|
+
firstByUrl: maps.firstByUrl
|
|
336
|
+
});
|
|
433
337
|
const effectiveActiveUrl = selection?.activeUrl;
|
|
434
338
|
const activeSets = React.useMemo(() => computeActiveSets(menu, effectiveActiveId, effectiveActiveUrl), [menu, effectiveActiveId, effectiveActiveUrl]);
|
|
435
339
|
React.useEffect(() => {
|
|
@@ -439,21 +343,20 @@ export function SgMenu(props) {
|
|
|
439
343
|
if (chain.length === 0)
|
|
440
344
|
return;
|
|
441
345
|
setExpandedIds((prev) => {
|
|
442
|
-
const next =
|
|
346
|
+
const next = mergeExpandedIdsForActivePath(prev, chain);
|
|
443
347
|
return sameStringArray(prev, next) ? prev : next;
|
|
444
348
|
});
|
|
445
349
|
}, [effectiveActiveId, maps.parentById, setExpandedIds]);
|
|
446
350
|
React.useEffect(() => {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}, [effectiveActiveId, maps.nodeById, maps.parentById, menu]);
|
|
351
|
+
const nextState = resolveTieredActiveState({
|
|
352
|
+
effectiveActiveId,
|
|
353
|
+
maps,
|
|
354
|
+
menu,
|
|
355
|
+
rootParentId: ROOT_PARENT_ID
|
|
356
|
+
});
|
|
357
|
+
setTieredPath(nextState.tieredPath);
|
|
358
|
+
setMegaActiveId(nextState.megaActiveId);
|
|
359
|
+
}, [effectiveActiveId, maps, menu]);
|
|
457
360
|
React.useEffect(() => {
|
|
458
361
|
if (effectiveMenuStyle !== "tiered")
|
|
459
362
|
return;
|
|
@@ -503,24 +406,14 @@ export function SgMenu(props) {
|
|
|
503
406
|
hideMenuHint();
|
|
504
407
|
}, [filteredMenu, effectiveMenuStyle, isCollapsed, drawerOpen, pinnedState, hideMenuHint]);
|
|
505
408
|
const toggleExpanded = React.useCallback((nodeId) => {
|
|
506
|
-
setExpandedIds((prev) => {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const parentId = maps.parentById.get(nodeId) ?? ROOT_PARENT_ID;
|
|
515
|
-
const siblings = maps.childrenByParent.get(parentId) ?? [];
|
|
516
|
-
for (const siblingId of siblings) {
|
|
517
|
-
if (siblingId !== nodeId)
|
|
518
|
-
next.delete(siblingId);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
next.add(nodeId);
|
|
522
|
-
return Array.from(next);
|
|
523
|
-
});
|
|
409
|
+
setExpandedIds((prev) => resolveExpandedIdsToggle({
|
|
410
|
+
currentIds: prev,
|
|
411
|
+
nodeId,
|
|
412
|
+
mode,
|
|
413
|
+
parentById: maps.parentById,
|
|
414
|
+
childrenByParent: maps.childrenByParent,
|
|
415
|
+
rootParentId: ROOT_PARENT_ID
|
|
416
|
+
}));
|
|
524
417
|
}, [maps.childrenByParent, maps.parentById, mode, setExpandedIds]);
|
|
525
418
|
const activateNode = React.useCallback((node) => {
|
|
526
419
|
if (node.disabled)
|
|
@@ -615,7 +508,7 @@ export function SgMenu(props) {
|
|
|
615
508
|
const zoneEl = dock.getZoneElement(zone);
|
|
616
509
|
if (zoneEl) {
|
|
617
510
|
const zoneRect = zoneEl.getBoundingClientRect();
|
|
618
|
-
setHorizontalDockAlign(upEvent.clientX
|
|
511
|
+
setHorizontalDockAlign(resolveHorizontalDockAlign(upEvent.clientX, zoneRect));
|
|
619
512
|
}
|
|
620
513
|
}
|
|
621
514
|
else {
|
|
@@ -651,62 +544,30 @@ export function SgMenu(props) {
|
|
|
651
544
|
const onListKeyDown = React.useCallback((event) => {
|
|
652
545
|
if (!keyboardNavigation)
|
|
653
546
|
return;
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (
|
|
665
|
-
event.preventDefault();
|
|
666
|
-
const next = visibleNodes[Math.min(activeIndex + 1, visibleNodes.length - 1)];
|
|
667
|
-
focusById(next?.node.id);
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
if (event.key === "ArrowUp") {
|
|
671
|
-
event.preventDefault();
|
|
672
|
-
const next = visibleNodes[Math.max(activeIndex - 1, 0)];
|
|
673
|
-
focusById(next?.node.id);
|
|
547
|
+
const action = resolveMenuKeyboardAction({
|
|
548
|
+
key: event.key,
|
|
549
|
+
visibleNodes,
|
|
550
|
+
focusedId,
|
|
551
|
+
expandedIds: expandedSet,
|
|
552
|
+
hasSearch,
|
|
553
|
+
isCollapsed,
|
|
554
|
+
parentById: maps.parentById,
|
|
555
|
+
rootParentId: ROOT_PARENT_ID
|
|
556
|
+
});
|
|
557
|
+
if (action.type === "noop")
|
|
674
558
|
return;
|
|
675
|
-
|
|
676
|
-
if (
|
|
677
|
-
|
|
678
|
-
event.preventDefault();
|
|
679
|
-
toggleExpanded(node.id);
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
if (hasChildren && !isCollapsed) {
|
|
683
|
-
event.preventDefault();
|
|
684
|
-
const next = visibleNodes[activeIndex + 1];
|
|
685
|
-
if (next && next.depth > current.depth)
|
|
686
|
-
focusById(next.node.id);
|
|
687
|
-
}
|
|
559
|
+
event.preventDefault();
|
|
560
|
+
if (action.type === "focus") {
|
|
561
|
+
focusById(action.id);
|
|
688
562
|
return;
|
|
689
563
|
}
|
|
690
|
-
if (
|
|
691
|
-
|
|
692
|
-
event.preventDefault();
|
|
693
|
-
toggleExpanded(node.id);
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
event.preventDefault();
|
|
697
|
-
const parentId = maps.parentById.get(node.id);
|
|
698
|
-
if (parentId && parentId !== ROOT_PARENT_ID)
|
|
699
|
-
focusById(parentId);
|
|
564
|
+
if (action.type === "toggle") {
|
|
565
|
+
toggleExpanded(action.id);
|
|
700
566
|
return;
|
|
701
567
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
if (hasChildren && !node.url && !node.onClick && !isCollapsed) {
|
|
705
|
-
toggleExpanded(node.id);
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
568
|
+
const node = maps.nodeById.get(action.id);
|
|
569
|
+
if (node)
|
|
708
570
|
activateNode(node);
|
|
709
|
-
}
|
|
710
571
|
}, [
|
|
711
572
|
activateNode,
|
|
712
573
|
expandedSet,
|
|
@@ -715,6 +576,7 @@ export function SgMenu(props) {
|
|
|
715
576
|
hasSearch,
|
|
716
577
|
isCollapsed,
|
|
717
578
|
keyboardNavigation,
|
|
579
|
+
maps.nodeById,
|
|
718
580
|
maps.parentById,
|
|
719
581
|
toggleExpanded,
|
|
720
582
|
visibleNodes
|
|
@@ -740,12 +602,18 @@ export function SgMenu(props) {
|
|
|
740
602
|
}, "data-sg-menu-node": node.id, type: "button", "aria-disabled": disabled || undefined, "aria-current": isExactActive ? "page" : undefined, onFocus: () => setFocusedId(node.id), onClick: () => {
|
|
741
603
|
if (disabled)
|
|
742
604
|
return;
|
|
743
|
-
|
|
605
|
+
const intent = resolveMenuNodeActionIntent({
|
|
606
|
+
variant: "panel",
|
|
607
|
+
hasChildren,
|
|
608
|
+
hasActionTarget: Boolean(node.url || node.onClick),
|
|
609
|
+
isCollapsed
|
|
610
|
+
});
|
|
611
|
+
if (intent === "toggle") {
|
|
744
612
|
toggleExpanded(node.id);
|
|
745
613
|
return;
|
|
746
614
|
}
|
|
747
615
|
activateNode(node);
|
|
748
|
-
}, className: cn("min-w-0 flex-1 rounded-md", "flex items-center text-left", densityCfg.gap, "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35"), children: [_jsx("span", { className: cn("inline-flex shrink-0 items-center justify-center rounded", densityCfg.icon, iconNode ? "" : "bg-muted text-[10px] font-semibold"), children: iconNode ?? firstChars(node.label, 1) }), !isCollapsed ? _jsx("span", { className: "min-w-0 flex-1 truncate", children: node.label }) : null, !isCollapsed && node.badge !== undefined ? (_jsx("span", { className: cn("inline-flex shrink-0 items-center justify-center rounded-full border border-border bg-muted text-muted-foreground", densityCfg.badge), children: node.badge })) : null] }), !isCollapsed && hasChildren ? (_jsx("button", { type: "button", "aria-label": openNow ?
|
|
616
|
+
}, className: cn("min-w-0 flex-1 rounded-md", "flex items-center text-left", densityCfg.gap, "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35"), children: [_jsx("span", { className: cn("inline-flex shrink-0 items-center justify-center rounded", densityCfg.icon, iconNode ? "" : "bg-muted text-[10px] font-semibold"), children: iconNode ?? firstChars(node.label, 1) }), !isCollapsed ? _jsx("span", { className: "min-w-0 flex-1 truncate", children: node.label }) : null, !isCollapsed && node.badge !== undefined ? (_jsx("span", { className: cn("inline-flex shrink-0 items-center justify-center rounded-full border border-border bg-muted text-muted-foreground", densityCfg.badge), children: node.badge })) : null] }), !isCollapsed && hasChildren ? (_jsx("button", { type: "button", "aria-label": openNow ? t(i18n, "components.actions.collapse") : t(i18n, "components.actions.expand"), onClick: () => !disabled && toggleExpanded(node.id), className: cn("inline-flex size-7 shrink-0 items-center justify-center rounded", "hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35"), children: openNow ? _jsx(ChevronDown, { className: "size-4" }) : _jsx(ChevronRight, { className: "size-4" }) })) : null] }), !hideChildren && hasChildren && openNow ? (_jsx("div", { className: "mt-0.5 space-y-0.5", children: node.children?.map((child) => renderNode(child, depth + 1)) })) : null] }, node.id));
|
|
749
617
|
}, [
|
|
750
618
|
activateNode,
|
|
751
619
|
activeSets.branch,
|
|
@@ -770,7 +638,12 @@ export function SgMenu(props) {
|
|
|
770
638
|
return (_jsxs("button", { type: "button", disabled: node.disabled, "aria-current": isExactActive ? "page" : undefined, onMouseEnter: node.hint ? (event) => showMenuHint(event.currentTarget, node.hint) : undefined, onMouseLeave: node.hint ? hideMenuHint : undefined, onClick: () => {
|
|
771
639
|
if (node.disabled)
|
|
772
640
|
return;
|
|
773
|
-
|
|
641
|
+
const intent = resolveMenuNodeActionIntent({
|
|
642
|
+
variant: "flat",
|
|
643
|
+
hasChildren,
|
|
644
|
+
hasActionTarget: Boolean(node.url || node.onClick)
|
|
645
|
+
});
|
|
646
|
+
if (intent === "noop")
|
|
774
647
|
return;
|
|
775
648
|
activateNode(node);
|
|
776
649
|
}, className: cn("flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm transition-colors", isExactActive
|
|
@@ -794,27 +667,28 @@ export function SgMenu(props) {
|
|
|
794
667
|
return (_jsxs("button", { type: "button", disabled: node.disabled, "aria-current": isExactActive ? "page" : undefined, onMouseEnter: (event) => {
|
|
795
668
|
if (node.hint)
|
|
796
669
|
showMenuHint(event.currentTarget, node.hint);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
670
|
+
setTieredPath((prev) => resolveTieredHoverIntent({
|
|
671
|
+
currentPath: prev,
|
|
672
|
+
depth,
|
|
673
|
+
nodeId: node.id,
|
|
674
|
+
hasChildren,
|
|
675
|
+
openSubmenuOnHover
|
|
676
|
+
}) ?? prev);
|
|
804
677
|
}, onMouseLeave: node.hint ? hideMenuHint : undefined, onClick: () => {
|
|
805
678
|
if (node.disabled)
|
|
806
679
|
return;
|
|
680
|
+
const nextState = resolveTieredNodeClickIntent({
|
|
681
|
+
currentPath: tieredPath,
|
|
682
|
+
depth,
|
|
683
|
+
nodeId: node.id,
|
|
684
|
+
hasChildren,
|
|
685
|
+
hasActionTarget: Boolean(node.url || node.onClick)
|
|
686
|
+
});
|
|
807
687
|
if (hasChildren) {
|
|
808
|
-
|
|
809
|
-
setTieredPath((prev) => {
|
|
810
|
-
const base = prev.slice(0, depth);
|
|
811
|
-
return isOpenAtDepth ? base : [...base, node.id];
|
|
812
|
-
});
|
|
813
|
-
if (isOpenAtDepth)
|
|
814
|
-
return;
|
|
815
|
-
if (!node.url && !node.onClick)
|
|
816
|
-
return;
|
|
688
|
+
setTieredPath(nextState.nextPath);
|
|
817
689
|
}
|
|
690
|
+
if (!nextState.shouldActivateNode)
|
|
691
|
+
return;
|
|
818
692
|
activateNode(node);
|
|
819
693
|
}, className: cn("flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm transition-colors", isExactActive ? "bg-primary/15 text-primary" : isOpen ? "bg-muted/70" : "hover:bg-muted/60", node.disabled ? "cursor-not-allowed opacity-55" : ""), children: [_jsx("span", { className: cn("inline-flex shrink-0 items-center justify-center rounded", densityCfg.icon, iconNode ? "" : "bg-muted text-[10px] font-semibold"), children: iconNode ?? firstChars(node.label, 1) }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: node.label }), hasChildren ? (tieredOpenToLeft ? (_jsx(ChevronLeft, { className: "size-4 shrink-0 text-muted-foreground" })) : (_jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground" }))) : null] }, node.id));
|
|
820
694
|
}) }), activeNode?.children?.length
|
|
@@ -832,13 +706,13 @@ export function SgMenu(props) {
|
|
|
832
706
|
]);
|
|
833
707
|
const renderMegaColumns = React.useCallback((nodes) => {
|
|
834
708
|
if (!nodes.length) {
|
|
835
|
-
return _jsx("div", { className: "text-sm text-muted-foreground", children: "
|
|
709
|
+
return _jsx("div", { className: "text-sm text-muted-foreground", children: t(i18n, "components.menu.empty") });
|
|
836
710
|
}
|
|
837
711
|
return (_jsx("div", { className: "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", children: nodes.map((group) => (_jsx("div", { className: "space-y-3", children: group.children?.length ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-base font-semibold", children: group.label }), _jsx("div", { className: "space-y-1", children: group.children.map((item) => item.children?.length ? (_jsxs("div", { className: "space-y-1 pt-1", children: [_jsx("p", { className: "text-sm font-semibold", children: item.label }), _jsx("div", { className: "space-y-1 pl-1", children: item.children.map((leaf) => renderFlatAction(leaf, "px-1.5 py-1.5 text-sm")) })] }, item.id)) : (renderFlatAction(item, "px-1.5 py-1.5 text-sm"))) })] })) : (renderFlatAction(group, "px-1.5 py-1.5 text-sm")) }, group.id))) }));
|
|
838
712
|
}, [renderFlatAction]);
|
|
839
713
|
const renderMenuTree = (items) => {
|
|
840
714
|
if (items.length === 0) {
|
|
841
|
-
return _jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: "
|
|
715
|
+
return _jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: t(i18n, "components.menu.empty") });
|
|
842
716
|
}
|
|
843
717
|
if (effectiveMenuStyle === "panel") {
|
|
844
718
|
return _jsx("div", { className: "space-y-0.5", children: items.map((node) => renderNode(node, 0)) });
|
|
@@ -846,8 +720,7 @@ export function SgMenu(props) {
|
|
|
846
720
|
if (effectiveMenuStyle === "tiered") {
|
|
847
721
|
return _jsx("div", { className: "relative min-h-[120px]", children: renderTieredLevels(items, 0) });
|
|
848
722
|
}
|
|
849
|
-
const activeMegaNode = items
|
|
850
|
-
items[0];
|
|
723
|
+
const activeMegaNode = resolveMegaMenuActiveNode(items, megaActiveId);
|
|
851
724
|
if (effectiveMenuStyle === "mega-horizontal") {
|
|
852
725
|
return (_jsxs("div", { className: "rounded-md border border-border bg-background", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1 border-b border-border p-1", children: items.map((node) => {
|
|
853
726
|
const hasChildren = !!node.children?.length;
|
|
@@ -856,15 +729,24 @@ export function SgMenu(props) {
|
|
|
856
729
|
return (_jsxs("button", { type: "button", disabled: node.disabled, onMouseEnter: (event) => {
|
|
857
730
|
if (node.hint)
|
|
858
731
|
showMenuHint(event.currentTarget, node.hint);
|
|
859
|
-
|
|
860
|
-
|
|
732
|
+
const nextMegaActiveId = resolveMegaMenuHoverActiveId({
|
|
733
|
+
nodeId: node.id,
|
|
734
|
+
hasChildren
|
|
735
|
+
});
|
|
736
|
+
if (nextMegaActiveId)
|
|
737
|
+
setMegaActiveId(nextMegaActiveId);
|
|
861
738
|
}, onMouseLeave: node.hint ? hideMenuHint : undefined, onClick: () => {
|
|
862
739
|
if (node.disabled)
|
|
863
740
|
return;
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
741
|
+
const nextState = resolveMegaMenuInteraction({
|
|
742
|
+
nodeId: node.id,
|
|
743
|
+
hasChildren
|
|
744
|
+
});
|
|
745
|
+
if (nextState.nextMegaActiveId) {
|
|
746
|
+
setMegaActiveId(nextState.nextMegaActiveId);
|
|
867
747
|
}
|
|
748
|
+
if (!nextState.shouldActivateNode)
|
|
749
|
+
return;
|
|
868
750
|
activateNode(node);
|
|
869
751
|
}, className: cn("inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors", active ? "bg-muted/80 text-foreground" : "hover:bg-muted/60", node.disabled ? "cursor-not-allowed opacity-55" : ""), children: [iconNode ? (_jsx("span", { className: cn("inline-flex shrink-0 items-center justify-center", densityCfg.icon), children: iconNode })) : null, _jsx("span", { className: "truncate", children: node.label }), hasChildren ? _jsx(ChevronDown, { className: "size-4 shrink-0 text-muted-foreground" }) : null] }, node.id));
|
|
870
752
|
}) }), _jsx("div", { className: "p-4", children: renderMegaColumns(activeMegaNode?.children ?? []) })] }));
|
|
@@ -876,15 +758,24 @@ export function SgMenu(props) {
|
|
|
876
758
|
return (_jsxs("button", { type: "button", disabled: node.disabled, onMouseEnter: (event) => {
|
|
877
759
|
if (node.hint)
|
|
878
760
|
showMenuHint(event.currentTarget, node.hint);
|
|
879
|
-
|
|
880
|
-
|
|
761
|
+
const nextMegaActiveId = resolveMegaMenuHoverActiveId({
|
|
762
|
+
nodeId: node.id,
|
|
763
|
+
hasChildren
|
|
764
|
+
});
|
|
765
|
+
if (nextMegaActiveId)
|
|
766
|
+
setMegaActiveId(nextMegaActiveId);
|
|
881
767
|
}, onMouseLeave: node.hint ? hideMenuHint : undefined, onClick: () => {
|
|
882
768
|
if (node.disabled)
|
|
883
769
|
return;
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
770
|
+
const nextState = resolveMegaMenuInteraction({
|
|
771
|
+
nodeId: node.id,
|
|
772
|
+
hasChildren
|
|
773
|
+
});
|
|
774
|
+
if (nextState.nextMegaActiveId) {
|
|
775
|
+
setMegaActiveId(nextState.nextMegaActiveId);
|
|
887
776
|
}
|
|
777
|
+
if (!nextState.shouldActivateNode)
|
|
778
|
+
return;
|
|
888
779
|
activateNode(node);
|
|
889
780
|
}, className: cn("flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm transition-colors", active ? "bg-muted/80 text-foreground" : "hover:bg-muted/60", node.disabled ? "cursor-not-allowed opacity-55" : ""), children: [iconNode ? (_jsx("span", { className: cn("inline-flex shrink-0 items-center justify-center", densityCfg.icon), children: iconNode })) : null, _jsx("span", { className: "min-w-0 flex-1 truncate", children: node.label }), hasChildren ? _jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground" }) : null] }, node.id));
|
|
890
781
|
}) }), _jsx("div", { className: "min-h-[220px] flex-1 p-4", children: renderMegaColumns(activeMegaNode?.children ?? []) })] }));
|
|
@@ -904,13 +795,13 @@ export function SgMenu(props) {
|
|
|
904
795
|
setCollapsedState(!collapsedState);
|
|
905
796
|
}, "aria-label": menuStyle === "hybrid"
|
|
906
797
|
? pinnedState
|
|
907
|
-
? "
|
|
798
|
+
? t(i18n, "components.menu.unpinAndCollapse")
|
|
908
799
|
: drawerOpen
|
|
909
|
-
? "
|
|
910
|
-
: "
|
|
800
|
+
? t(i18n, "components.menu.close")
|
|
801
|
+
: t(i18n, "components.menu.open")
|
|
911
802
|
: collapsedState
|
|
912
|
-
? "
|
|
913
|
-
: "
|
|
803
|
+
? t(i18n, "components.menu.expand")
|
|
804
|
+
: t(i18n, "components.menu.collapse"), className: cn("inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-border", "bg-background hover:bg-muted/60", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35"), children: _jsx(CollapseIcon, { collapsed: menuStyle === "hybrid" ? !(pinnedState || drawerOpen) : collapsedState, side: collapseIconSide }) })) : null;
|
|
914
805
|
const showBrandContent = Boolean(brand && (!isCollapsed || isHorizontalDockZone));
|
|
915
806
|
const showBrandSpacer = !showBrandContent && !isCollapsed;
|
|
916
807
|
// In horizontal zones: collapse goes left when aligned left/center, right when aligned right.
|
|
@@ -918,13 +809,13 @@ export function SgMenu(props) {
|
|
|
918
809
|
const collapseOnLeft = isHorizontalDockZone
|
|
919
810
|
? horizontalDockAlign !== "right"
|
|
920
811
|
: resolvedPosition === "right";
|
|
921
|
-
const shellHeaderRow = (brand || showCollapseButton || showPinButton || showDockDragHandle) ? (_jsxs("div", { className: cn("flex items-center gap-2 border-b border-border", densityCfg.section), children: [collapseOnLeft ? collapseButton : null, showBrandContent && brand ? (_jsxs("button", { type: "button", onClick: brand.onClick, className: cn("min-w-0 rounded-md", isHorizontalDockZone ? "flex-none" : "flex-1", "flex items-center gap-2", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35"), children: [brand.image ? (_jsx("span", { className: "inline-flex shrink-0 items-center justify-center", children: brand.image })) : brand.imageSrc ? (_jsx("img", { src: brand.imageSrc, alt: brand.title ?? "
|
|
812
|
+
const shellHeaderRow = (brand || showCollapseButton || showPinButton || showDockDragHandle) ? (_jsxs("div", { className: cn("flex items-center gap-2 border-b border-border", densityCfg.section), children: [collapseOnLeft ? collapseButton : null, showBrandContent && brand ? (_jsxs("button", { type: "button", onClick: brand.onClick, className: cn("min-w-0 rounded-md", isHorizontalDockZone ? "flex-none" : "flex-1", "flex items-center gap-2", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35"), children: [brand.image ? (_jsx("span", { className: "inline-flex shrink-0 items-center justify-center", children: brand.image })) : brand.imageSrc ? (_jsx("img", { src: brand.imageSrc, alt: brand.title ?? t(i18n, "components.menu.brandAlt"), className: cn("w-auto", isHorizontalDockZone ? "h-7 max-w-[120px]" : "h-12 max-w-[160px]") })) : null, brand.title ? (_jsx("span", { className: "truncate text-sm font-semibold", children: brand.title })) : null] })) : showBrandSpacer ? (_jsx("div", { className: "flex-1" })) : null, showDockDragHandle ? (_jsx("button", { type: "button", onPointerDown: handleDockDragPointerDown, "aria-label": t(i18n, "components.menu.drag"), className: cn("inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-border", "bg-background hover:bg-muted/60", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35", dockDragActive ? "cursor-grabbing" : "cursor-grab"), children: _jsxs("svg", { viewBox: "0 0 24 24", className: "size-4", "aria-hidden": "true", children: [_jsx("circle", { cx: "8", cy: "8", r: "1.25", fill: "currentColor" }), _jsx("circle", { cx: "8", cy: "12", r: "1.25", fill: "currentColor" }), _jsx("circle", { cx: "8", cy: "16", r: "1.25", fill: "currentColor" }), _jsx("circle", { cx: "16", cy: "8", r: "1.25", fill: "currentColor" }), _jsx("circle", { cx: "16", cy: "12", r: "1.25", fill: "currentColor" }), _jsx("circle", { cx: "16", cy: "16", r: "1.25", fill: "currentColor" })] }) })) : null, showPinButton ? (_jsx("button", { type: "button", onClick: () => {
|
|
922
813
|
const next = !pinnedState;
|
|
923
814
|
setPinnedState(next);
|
|
924
815
|
if (menuStyle === "hybrid" && next)
|
|
925
816
|
setDrawerOpen(false);
|
|
926
|
-
}, "aria-label": pinnedState ? "
|
|
927
|
-
const shellContentArea = (_jsxs(_Fragment, { children: [searchEnabled && !isCollapsed ? (_jsx("div", { className: cn("border-b border-border", densityCfg.section), children: _jsx(SgAutocomplete, { id: `${searchInputId}-menu-search`, label: search?.placeholder ?? "
|
|
817
|
+
}, "aria-label": pinnedState ? t(i18n, "components.menu.unpin") : t(i18n, "components.menu.pin"), className: cn("inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-border", "bg-background hover:bg-muted/60", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35"), children: _jsx(PinIcon, { pinned: pinnedState }) })) : null, !collapseOnLeft ? collapseButton : null] })) : null;
|
|
818
|
+
const shellContentArea = (_jsxs(_Fragment, { children: [searchEnabled && !isCollapsed ? (_jsx("div", { className: cn("border-b border-border", densityCfg.section), children: _jsx(SgAutocomplete, { id: `${searchInputId}-menu-search`, label: search?.placeholder ?? t(i18n, "components.actions.search"), placeholder: search?.placeholder ?? t(i18n, "components.actions.search"), value: searchValue, onChange: setSearchValue, source: autocompleteSource, minLengthForSearch: 0, openOnFocus: true, showDropDownButton: true, clearOnSelect: true, grouped: true, onSelect: (item) => {
|
|
928
819
|
const nodeId = item.data?.nodeId;
|
|
929
820
|
if (!nodeId)
|
|
930
821
|
return;
|
|
@@ -933,25 +824,22 @@ export function SgMenu(props) {
|
|
|
933
824
|
return;
|
|
934
825
|
const parentChain = collectParentChain(maps.parentById, node.id);
|
|
935
826
|
const rootToParent = [...parentChain].reverse();
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
return sameStringArray(prev, next) ? prev : next;
|
|
953
|
-
});
|
|
954
|
-
setLocalActiveId(node.id);
|
|
827
|
+
const nextState = resolveMenuSearchSelectionState({
|
|
828
|
+
currentExpandedIds: expandedIds,
|
|
829
|
+
parentChain,
|
|
830
|
+
rootToParent,
|
|
831
|
+
nodeId: node.id,
|
|
832
|
+
hasChildren: Boolean(node.children?.length),
|
|
833
|
+
hasActionTarget: Boolean(node.url || node.onClick)
|
|
834
|
+
});
|
|
835
|
+
setExpandedIds((prev) => {
|
|
836
|
+
const next = nextState.expandedIds;
|
|
837
|
+
return sameStringArray(prev, next) ? prev : next;
|
|
838
|
+
});
|
|
839
|
+
setTieredPath(nextState.tieredPath);
|
|
840
|
+
setMegaActiveId(nextState.megaActiveId);
|
|
841
|
+
if (!nextState.shouldActivateNode) {
|
|
842
|
+
setLocalActiveId(nextState.localActiveId);
|
|
955
843
|
return;
|
|
956
844
|
}
|
|
957
845
|
activateNode(node);
|
|
@@ -961,28 +849,16 @@ export function SgMenu(props) {
|
|
|
961
849
|
const node = data?.nodeId ? maps.nodeById.get(data.nodeId) : undefined;
|
|
962
850
|
const iconNode = node ? resolveIcon(node) : null;
|
|
963
851
|
return (_jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [_jsx("span", { className: cn("mt-0.5 inline-flex shrink-0 items-center justify-center rounded", densityCfg.icon, iconNode ? "" : "bg-muted text-[10px] font-semibold"), children: iconNode ?? firstChars(item.label, 1) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate", children: item.label }), path ? _jsx("div", { className: "truncate text-xs text-muted-foreground", children: path }) : null] })] }));
|
|
964
|
-
} }) })) : null, _jsx("div", { className: cn("min-h-0 flex-1", effectiveMenuStyle === "panel" ? "overflow-auto" : "overflow-visible", densityCfg.section), role: "navigation", "aria-label":
|
|
852
|
+
} }) })) : null, _jsx("div", { className: cn("min-h-0 flex-1", effectiveMenuStyle === "panel" ? "overflow-auto" : "overflow-visible", densityCfg.section), role: "navigation", "aria-label": resolvedAriaLabel, onKeyDown: effectiveMenuStyle === "panel" ? onListKeyDown : undefined, children: renderMenuTree(filteredMenu) }), (user || (userMenu && userMenu.length > 0) || footer) ? (_jsx("div", { className: cn("border-t border-border", densityCfg.section, userSectionClassName), style: userSectionStyle, children: user ? (isCollapsed ? (_jsx("button", { type: "button", onClick: user.onClick, className: cn("mb-2 w-full rounded-md", "flex items-center gap-2", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35", "hover:bg-muted/60", densityCfg.row), title: user.name, children: _jsx(SgAvatar, { src: user.avatar ? undefined : user.avatarSrc, label: user.name, size: density === "compact" ? "sm" : "md", severity: "secondary", children: user.avatar ?? undefined }) })) : (_jsxs(SgCard, { className: "mb-2", cardStyle: "outlined", size: density === "compact" ? "sm" : "md", collapsible: true, defaultOpen: false, title: user.name, description: user.subtitle, leading: (_jsx("button", { type: "button", onClick: (event) => {
|
|
965
853
|
event.stopPropagation();
|
|
966
854
|
user.onClick?.();
|
|
967
855
|
}, className: "inline-flex rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35", "aria-label": user.name, children: _jsx(SgAvatar, { src: user.avatar ? undefined : user.avatarSrc, label: user.name, size: density === "compact" ? "sm" : "md", severity: "secondary", children: user.avatar ?? undefined }) })), children: [userMenu && userMenu.length > 0 ? (_jsx("div", { className: "space-y-0.5", children: userMenu.map((node) => renderNode(node, 0)) })) : null, footer ? _jsx("div", { className: userMenu && userMenu.length > 0 ? "mt-2" : undefined, children: footer }) : null] }))) : (_jsxs(_Fragment, { children: [userMenu && userMenu.length > 0 && !isCollapsed ? (_jsx("div", { className: "space-y-0.5", children: userMenu.map((node) => renderNode(node, 0)) })) : null, footer ? _jsx("div", { className: "mt-2", children: footer }) : null] })) })) : null] }));
|
|
968
856
|
const shellBody = (_jsxs("div", { ref: menuRootRef, className: cn("flex h-full min-h-0 flex-col bg-background text-foreground"), children: [shellHeaderRow, shellContentArea] }));
|
|
969
|
-
const
|
|
970
|
-
const dockAlignStyle = dockMode && effectiveDockZone === "right" && !isCollapsed
|
|
857
|
+
const dockAlignStyle = dockAlign === "end"
|
|
971
858
|
? { alignSelf: "flex-end" }
|
|
972
|
-
:
|
|
859
|
+
: dockAlign === "start"
|
|
973
860
|
? { alignSelf: "flex-start" }
|
|
974
861
|
: undefined;
|
|
975
|
-
const sidebarWidthCss = dockMode && isHorizontalDockZone && menuStyle !== "sidebar"
|
|
976
|
-
? "100%"
|
|
977
|
-
: menuStyle === "inline" && isMegaMenuStyle
|
|
978
|
-
? "100%"
|
|
979
|
-
: menuStyle === "hybrid"
|
|
980
|
-
? pinnedState
|
|
981
|
-
? expandedWidthCss
|
|
982
|
-
: collapsedWidthCss
|
|
983
|
-
: isCollapsed
|
|
984
|
-
? collapsedWidthCss
|
|
985
|
-
: expandedWidthCss;
|
|
986
862
|
const sidebarWidthStyle = sidebarWidthCss;
|
|
987
863
|
const sidebarShell = (_jsx("aside", { ref: (node) => {
|
|
988
864
|
sidebarShellRef.current = node;
|
|
@@ -1005,10 +881,10 @@ export function SgMenu(props) {
|
|
|
1005
881
|
return (_jsxs(_Fragment, { children: [createPortal(shellForRender, portalTarget), menuHintNode] }));
|
|
1006
882
|
}
|
|
1007
883
|
if (menuStyle === "drawer") {
|
|
1008
|
-
return (_jsxs(_Fragment, { children: [_jsx(SgExpandablePanel, { mode: "overlay", open: drawerOpen, onOpenChange: setDrawerOpen, expandTo: resolvedPosition === "left" ? "right" : "left", placement: "start", size: resolvedOverlaySize, animation: { type: "slide", durationMs: 180 }, border: border, elevation: elevation === "md" ? "lg" : elevation, rounded: "none", closeOnOutsideClick: !pinnedState, closeOnEsc: !pinnedState, trapFocus: true, showBackdrop: overlayBackdropResolved, ariaLabel:
|
|
884
|
+
return (_jsxs(_Fragment, { children: [_jsx(SgExpandablePanel, { mode: "overlay", open: drawerOpen, onOpenChange: setDrawerOpen, expandTo: resolvedPosition === "left" ? "right" : "left", placement: "start", size: resolvedOverlaySize, animation: { type: "slide", durationMs: 180 }, border: border, elevation: elevation === "md" ? "lg" : elevation, rounded: "none", closeOnOutsideClick: !pinnedState, closeOnEsc: !pinnedState, trapFocus: true, showBackdrop: overlayBackdropResolved, ariaLabel: resolvedAriaLabel, role: "dialog", className: className, style: style, children: shellBody }), menuHintNode] }));
|
|
1009
885
|
}
|
|
1010
886
|
if (menuStyle === "hybrid") {
|
|
1011
|
-
return (_jsxs(_Fragment, { children: [shellForRender, !pinnedState ? (_jsx(SgExpandablePanel, { mode: "overlay", open: drawerOpen, onOpenChange: setDrawerOpen, expandTo: resolvedPosition === "left" ? "right" : "left", placement: "start", size: resolvedOverlaySize, animation: { type: "slide", durationMs: 180 }, border: border, elevation: elevation === "md" ? "lg" : elevation, rounded: "none", closeOnOutsideClick: true, closeOnEsc: true, trapFocus: true, showBackdrop: overlayBackdropResolved, ariaLabel:
|
|
887
|
+
return (_jsxs(_Fragment, { children: [shellForRender, !pinnedState ? (_jsx(SgExpandablePanel, { mode: "overlay", open: drawerOpen, onOpenChange: setDrawerOpen, expandTo: resolvedPosition === "left" ? "right" : "left", placement: "start", size: resolvedOverlaySize, animation: { type: "slide", durationMs: 180 }, border: border, elevation: elevation === "md" ? "lg" : elevation, rounded: "none", closeOnOutsideClick: true, closeOnEsc: true, trapFocus: true, showBackdrop: overlayBackdropResolved, ariaLabel: resolvedAriaLabel, role: "dialog", children: _jsx(SgMenu, { menu: menu, selection: { activeId: effectiveActiveId, activeUrl: effectiveActiveUrl }, brand: brand, user: user, userMenu: userMenu, menuStyle: "inline", menuVariantStyle: menuVariantStyle, position: position, density: density, indent: indent, collapsed: false, collapsedWidth: collapsedWidth, expandedWidth: expandedWidth, mode: mode, expandedIds: expandedIds, onExpandedIdsChange: setExpandedIds, showCollapseButton: false, showPinButton: showPinButton, pinned: pinnedState, onPinnedChange: (next) => {
|
|
1012
888
|
setPinnedState(next);
|
|
1013
889
|
if (next)
|
|
1014
890
|
setDrawerOpen(false);
|
|
@@ -1023,7 +899,7 @@ export function SgMenu(props) {
|
|
|
1023
899
|
}, onAction: onAction, onItemClick: (node) => {
|
|
1024
900
|
setLocalActiveId(node.id);
|
|
1025
901
|
onItemClick?.(node);
|
|
1026
|
-
}, ariaLabel:
|
|
902
|
+
}, ariaLabel: resolvedAriaLabel, keyboardNavigation: keyboardNavigation, openSubmenuOnHover: openSubmenuOnHover, search: search, elevation: "none", border: false, footer: footer }) })) : null, menuHintNode] }));
|
|
1027
903
|
}
|
|
1028
904
|
return (_jsxs(_Fragment, { children: [shellForRender, menuHintNode] }));
|
|
1029
905
|
}
|