pixelize-design-library 2.4.2-beta.57 → 2.4.2-beta.59
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/CHANGELOG.md +86 -0
- package/COMPONENT-GOTCHAS.md +88 -2
- package/DESIGN-GUIDE.md +27 -4
- package/dist/Components/AppSwitcher/AppSwitcher.js +5 -1
- package/dist/Components/AppSwitcher/AppSwitcherProps.d.ts +2 -0
- package/dist/Components/AppSwitcher/AppSwitcherProps.js +7 -4
- package/dist/Components/Breadcrumbs/Breadcrumbs.d.ts +1 -1
- package/dist/Components/Breadcrumbs/Breadcrumbs.js +33 -10
- package/dist/Components/Breadcrumbs/BreadcrumbsProps.d.ts +16 -2
- package/dist/Components/Card/Card.d.ts +3 -1
- package/dist/Components/Card/Card.js +12 -2
- package/dist/Components/Card/CardProps.d.ts +5 -1
- package/dist/Components/Commerce/commerceTypes.d.ts +5 -0
- package/dist/Components/Editor/Editor.js +50 -2
- package/dist/Components/Editor/EditorFieldBar.d.ts +15 -0
- package/dist/Components/Editor/EditorFieldBar.js +19 -0
- package/dist/Components/Editor/EditorProps.d.ts +74 -1
- package/dist/Components/Editor/MakeDynamicDialog.d.ts +12 -0
- package/dist/Components/Editor/MakeDynamicDialog.js +59 -0
- package/dist/Components/Editor/editorLabels.d.ts +4 -0
- package/dist/Components/Editor/editorLabels.js +24 -0
- package/dist/Components/Editor/editorTokenStyles.d.ts +28 -0
- package/dist/Components/Editor/editorTokenStyles.js +30 -0
- package/dist/Components/Editor/editorTokens.d.ts +26 -0
- package/dist/Components/Editor/editorTokens.js +134 -0
- package/dist/Components/Editor/useEditorBridge.d.ts +40 -0
- package/dist/Components/Editor/useEditorBridge.js +141 -0
- package/dist/Components/Kbd/Kbd.d.ts +6 -0
- package/dist/Components/Kbd/Kbd.js +33 -0
- package/dist/Components/Kbd/KbdProps.d.ts +17 -0
- package/dist/Components/Kbd/KbdProps.js +2 -0
- package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +4 -0
- package/dist/Components/LauncherTrigger/LauncherTrigger.js +41 -0
- package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +16 -0
- package/dist/Components/LauncherTrigger/LauncherTriggerProps.js +2 -0
- package/dist/Components/MediaGallery/MediaGallery.js +1 -1
- package/dist/Components/NavLauncher/NavLauncher.d.ts +3 -0
- package/dist/Components/NavLauncher/NavLauncher.js +395 -0
- package/dist/Components/NavLauncher/NavLauncherProps.d.ts +187 -0
- package/dist/Components/NavLauncher/NavLauncherProps.js +2 -0
- package/dist/Components/NavLauncher/NewTabLink.d.ts +3 -0
- package/dist/Components/NavLauncher/NewTabLink.js +14 -0
- package/dist/Components/NavLauncher/PinToggle.d.ts +3 -0
- package/dist/Components/NavLauncher/PinToggle.js +21 -0
- package/dist/Components/NavLauncher/launcherParts.d.ts +93 -0
- package/dist/Components/NavLauncher/launcherParts.js +130 -0
- package/dist/Components/NavLauncher/match.d.ts +53 -0
- package/dist/Components/NavLauncher/match.js +178 -0
- package/dist/Components/NavLauncher/revealStyles.d.ts +4 -0
- package/dist/Components/NavLauncher/revealStyles.js +19 -0
- package/dist/Components/NavigationBar/NavigationBar.d.ts +1 -1
- package/dist/Components/NavigationBar/NavigationBar.js +2 -2
- package/dist/Components/NavigationBar/NavigationBarProps.d.ts +2 -0
- package/dist/Components/PageHeader/PageHeader.d.ts +1 -1
- package/dist/Components/PageHeader/PageHeader.js +9 -2
- package/dist/Components/PageHeader/PageHeaderProps.d.ts +8 -0
- package/dist/Components/PdfViewer/PdfViewer.d.ts +21 -4
- package/dist/Components/PdfViewer/PdfViewer.js +124 -3
- package/dist/Components/ProductCard/ProductCard.js +8 -3
- package/dist/Components/ProductCard/ProductCardMedia.js +3 -3
- package/dist/Components/ProductCard/QuickAddSheet.js +2 -2
- package/dist/Components/ProductCard/cardMetrics.d.ts +1 -0
- package/dist/Components/ProductCard/cardMetrics.js +3 -2
- package/dist/Components/ProductDetails/ProductDetails.d.ts +1 -1
- package/dist/Components/ProductDetails/ProductDetails.js +6 -5
- package/dist/Components/ProductDetails/ProductDetailsProps.d.ts +2 -0
- package/dist/Components/Search/Search.js +2 -1
- package/dist/Components/SideBar/SecondaryBar.js +4 -3
- package/dist/Components/SideBar/SideBar.d.ts +1 -1
- package/dist/Components/SideBar/SideBar.js +30 -9
- package/dist/Components/SideBar/SideBarProps.d.ts +32 -0
- package/dist/Components/SideBar/components/MenuItemBox.d.ts +6 -0
- package/dist/Components/SideBar/components/MenuItemBox.js +8 -4
- package/dist/Components/SideBar/components/MenuPopoverContent.js +3 -2
- package/dist/Components/SideBar/components/OtherApps.js +2 -1
- package/dist/Components/SideBar/components/PinnedSection.d.ts +17 -0
- package/dist/Components/SideBar/components/PinnedSection.js +98 -0
- package/dist/Components/SideBar/sidebarFocus.d.ts +23 -0
- package/dist/Components/SideBar/sidebarFocus.js +20 -0
- package/dist/Components/StageProgress/StageProgress.js +2 -2
- package/dist/Components/StageProgress/StageProgressProps.d.ts +2 -0
- package/dist/Components/StageProgress/StepperStage.js +5 -2
- package/dist/Components/WizardDialog/WizardDialog.d.ts +9 -0
- package/dist/Components/WizardDialog/WizardDialog.js +102 -0
- package/dist/Components/WizardDialog/WizardDialogProps.d.ts +48 -0
- package/dist/Components/WizardDialog/WizardDialogProps.js +2 -0
- package/dist/Components/WizardDialog/useWizardFlow.d.ts +22 -0
- package/dist/Components/WizardDialog/useWizardFlow.js +47 -0
- package/dist/Components/WizardDialog/wizardSteps.d.ts +31 -0
- package/dist/Components/WizardDialog/wizardSteps.js +40 -0
- package/dist/Hooks/useMatchMedia.d.ts +2 -0
- package/dist/Hooks/useMatchMedia.js +36 -0
- package/dist/Theme/chakra/motion.js +2 -16
- package/dist/Utils/price.js +11 -6
- package/dist/esm/Components/AppSwitcher/AppSwitcher.js +2 -1
- package/dist/esm/Components/AppSwitcher/AppSwitcherProps.js +5 -3
- package/dist/esm/Components/Breadcrumbs/Breadcrumbs.js +35 -12
- package/dist/esm/Components/Card/Card.js +12 -2
- package/dist/esm/Components/Editor/Editor.js +48 -3
- package/dist/esm/Components/Editor/EditorFieldBar.js +13 -0
- package/dist/esm/Components/Editor/MakeDynamicDialog.js +20 -0
- package/dist/esm/Components/Editor/editorLabels.js +20 -0
- package/dist/esm/Components/Editor/editorTokenStyles.js +25 -0
- package/dist/esm/Components/Editor/editorTokens.js +121 -0
- package/dist/esm/Components/Editor/useEditorBridge.js +138 -0
- package/dist/esm/Components/Kbd/Kbd.js +28 -0
- package/dist/esm/Components/Kbd/KbdProps.js +1 -0
- package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +37 -0
- package/dist/esm/Components/LauncherTrigger/LauncherTriggerProps.js +1 -0
- package/dist/esm/Components/MediaGallery/MediaGallery.js +1 -1
- package/dist/esm/Components/NavLauncher/NavLauncher.js +356 -0
- package/dist/esm/Components/NavLauncher/NavLauncherProps.js +1 -0
- package/dist/esm/Components/NavLauncher/NewTabLink.js +11 -0
- package/dist/esm/Components/NavLauncher/PinToggle.js +18 -0
- package/dist/esm/Components/NavLauncher/launcherParts.js +110 -0
- package/dist/esm/Components/NavLauncher/match.js +172 -0
- package/dist/esm/Components/NavLauncher/revealStyles.js +15 -0
- package/dist/esm/Components/NavigationBar/NavigationBar.js +3 -3
- package/dist/esm/Components/PageHeader/PageHeader.js +9 -2
- package/dist/esm/Components/PdfViewer/PdfViewer.js +123 -2
- package/dist/esm/Components/ProductCard/ProductCard.js +9 -4
- package/dist/esm/Components/ProductCard/ProductCardMedia.js +3 -3
- package/dist/esm/Components/ProductCard/QuickAddSheet.js +2 -2
- package/dist/esm/Components/ProductCard/cardMetrics.js +2 -1
- package/dist/esm/Components/ProductDetails/ProductDetails.js +6 -5
- package/dist/esm/Components/Search/Search.js +3 -2
- package/dist/esm/Components/SideBar/SecondaryBar.js +4 -3
- package/dist/esm/Components/SideBar/SideBar.js +31 -10
- package/dist/esm/Components/SideBar/components/MenuItemBox.js +4 -2
- package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +3 -2
- package/dist/esm/Components/SideBar/components/OtherApps.js +2 -1
- package/dist/esm/Components/SideBar/components/PinnedSection.js +92 -0
- package/dist/esm/Components/SideBar/sidebarFocus.js +15 -0
- package/dist/esm/Components/StageProgress/StageProgress.js +2 -2
- package/dist/esm/Components/StageProgress/StepperStage.js +5 -2
- package/dist/esm/Components/WizardDialog/WizardDialog.js +58 -0
- package/dist/esm/Components/WizardDialog/WizardDialogProps.js +1 -0
- package/dist/esm/Components/WizardDialog/useWizardFlow.js +44 -0
- package/dist/esm/Components/WizardDialog/wizardSteps.js +35 -0
- package/dist/esm/Hooks/useMatchMedia.js +32 -0
- package/dist/esm/Theme/chakra/motion.js +2 -16
- package/dist/esm/Utils/price.js +11 -6
- package/dist/esm/index.js +7 -0
- package/dist/index.d.ts +19 -4
- package/dist/index.js +21 -1
- package/package.json +1 -1
- package/dist/Components/MediaGallery/useMatchMedia.d.ts +0 -2
- package/dist/Components/MediaGallery/useMatchMedia.js +0 -20
- package/dist/Components/ProductCard/useMinWidth.d.ts +0 -2
- package/dist/Components/ProductCard/useMinWidth.js +0 -20
- package/dist/esm/Components/MediaGallery/useMatchMedia.js +0 -16
- package/dist/esm/Components/ProductCard/useMinWidth.js +0 -16
|
@@ -4,6 +4,7 @@ import { Box, Flex, Icon, VStack, Badge, Popover, PopoverTrigger, PopoverContent
|
|
|
4
4
|
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
5
5
|
import { getSidebarActiveAccent } from "./sidebarAccentColors.js";
|
|
6
6
|
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
7
|
+
import { sidebarFocusStyles } from "./sidebarFocus.js";
|
|
7
8
|
import { menuDisabled } from "./menuDisabled.js";
|
|
8
9
|
import { ChevronLeft, ChevronRight, LockKeyhole } from "lucide-react";
|
|
9
10
|
import OverflowTooltipText from "./components/OverflowTooltipText.js";
|
|
@@ -46,7 +47,7 @@ const CollapsedItem = ({ item, isActive, itemFontSize, onItemClick }) => {
|
|
|
46
47
|
e.preventDefault();
|
|
47
48
|
if (!blocked)
|
|
48
49
|
onItemClick === null || onItemClick === void 0 ? void 0 : onItemClick(item);
|
|
49
|
-
}, bg: isActive ? "whiteAlpha.300" : "transparent", transition: "background 0.12s", children: _jsxs(Box, { position: "relative", display: "flex", alignItems: "center", children: [_jsx(Icon, { as: item.icon, fontSize: SIDEBAR_ROW.iconSize, color: isActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" }), badgeVal != null && (_jsx(Box, { position: "absolute", top: "0", right: "0", w: "0.375rem", h: "0.375rem", borderRadius: "full", bg: theme.colors.sidebar.semanticText.error }))] }) }) }), _jsx(Portal, { children: _jsx(PopoverContent, { borderRadius: "lg", bg: theme.colors.sidebar.elevated, boxShadow: "lg", minW: "10rem", maxW: "14rem", py: "0.125rem", ml: "-0.5rem", cursor: blocked ? "not-allowed" : "pointer", zIndex: 1500, color: theme.colors.white, border: 'none', children: _jsx(PopoverBody, { p: 3, children: _jsxs(Flex, { align: "center", gap: 2, justify: "space-between", children: [_jsxs(Flex, { align: "center", gap: 2, flex: "1", minW: 0, children: [_jsx(Icon, { as: item.icon, fontSize: "md", flexShrink: 0 }), _jsx(OverflowTooltipText, { placement: "right", maxWidth: "100%", fontSize: itemFontSize, fontWeight: "medium", as: "p", children: item.title })] }), item.locked ? (item.lockedReason ? (_jsx(ToolTip, { label: item.lockedReason, placement: "top", children: _jsx(Icon, { as: LockKeyhole, fontSize: "sm", flexShrink: 0, color: theme.colors.sidebar.mutedIcon }) })) : (_jsx(Icon, { as: LockKeyhole, fontSize: "sm", flexShrink: 0, color: theme.colors.sidebar.mutedIcon }))) : (badgeVal != null && (_jsx(Badge, { ...softBadge(theme.colors, colorScheme, sidebarRowPeak(theme.colors.sidebar.elevated)), fontSize: "xs", variant: variant, borderRadius: radius, children: badgeVal })))] }) }) }) })] }));
|
|
50
|
+
}, bg: isActive ? "whiteAlpha.300" : "transparent", transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors), children: _jsxs(Box, { position: "relative", display: "flex", alignItems: "center", children: [_jsx(Icon, { as: item.icon, fontSize: SIDEBAR_ROW.iconSize, color: isActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" }), badgeVal != null && (_jsx(Box, { position: "absolute", top: "0", right: "0", w: "0.375rem", h: "0.375rem", borderRadius: "full", bg: theme.colors.sidebar.semanticText.error }))] }) }) }), _jsx(Portal, { children: _jsx(PopoverContent, { borderRadius: "lg", bg: theme.colors.sidebar.elevated, boxShadow: "lg", minW: "10rem", maxW: "14rem", py: "0.125rem", ml: "-0.5rem", cursor: blocked ? "not-allowed" : "pointer", zIndex: 1500, color: theme.colors.white, border: 'none', children: _jsx(PopoverBody, { p: 3, children: _jsxs(Flex, { align: "center", gap: 2, justify: "space-between", children: [_jsxs(Flex, { align: "center", gap: 2, flex: "1", minW: 0, children: [_jsx(Icon, { as: item.icon, fontSize: "md", flexShrink: 0 }), _jsx(OverflowTooltipText, { placement: "right", maxWidth: "100%", fontSize: itemFontSize, fontWeight: "medium", as: "p", children: item.title })] }), item.locked ? (item.lockedReason ? (_jsx(ToolTip, { label: item.lockedReason, placement: "top", children: _jsx(Icon, { as: LockKeyhole, fontSize: "sm", flexShrink: 0, color: theme.colors.sidebar.mutedIcon }) })) : (_jsx(Icon, { as: LockKeyhole, fontSize: "sm", flexShrink: 0, color: theme.colors.sidebar.mutedIcon }))) : (badgeVal != null && (_jsx(Badge, { ...softBadge(theme.colors, colorScheme, sidebarRowPeak(theme.colors.sidebar.elevated)), fontSize: "xs", variant: variant, borderRadius: radius, children: badgeVal })))] }) }) }) })] }));
|
|
50
51
|
};
|
|
51
52
|
const ExpandedItem = ({ item, isActive, itemFontSize, onItemClick }) => {
|
|
52
53
|
var _a;
|
|
@@ -60,7 +61,7 @@ const ExpandedItem = ({ item, isActive, itemFontSize, onItemClick }) => {
|
|
|
60
61
|
e.preventDefault();
|
|
61
62
|
if (!blocked)
|
|
62
63
|
onItemClick === null || onItemClick === void 0 ? void 0 : onItemClick(item);
|
|
63
|
-
}, borderLeft: "2px solid", borderLeftColor: isActive ? theme.colors.sidebar.activeIcon : "transparent", transition: "background 0.12s", children: [_jsxs(Flex, { align: "center", gap: 2, flex: "1", minW: 0, overflow: "hidden", children: [_jsx(Icon, { as: item.icon, fontSize: SIDEBAR_ROW.iconSize, flexShrink: 0, color: isActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" }), _jsx(Box, { flex: "1", minW: 0, children: _jsx(OverflowTooltipText, { placement: "right", maxWidth: "100%", fontSize: itemFontSize, fontWeight: isActive ? 600 : 500, color: isActive ? activeAccent.label : theme.colors.white, _groupHover: isActive ? undefined : { color: theme.colors.white }, as: "p", children: item.title }) })] }), item.locked ? (_jsx(Icon, { as: LockKeyhole, fontSize: "sm", flexShrink: 0, color: theme.colors.sidebar.mutedIcon })) : (badgeVal != null && (_jsx(Badge, { ...softBadge(theme.colors, colorScheme, sidebarRowPeak(theme.colors.sidebar.background[500])), fontSize: "xs", variant: variant, borderRadius: radius, children: badgeVal })))] }));
|
|
64
|
+
}, borderLeft: "2px solid", borderLeftColor: isActive ? theme.colors.sidebar.activeIcon : "transparent", transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors), children: [_jsxs(Flex, { align: "center", gap: 2, flex: "1", minW: 0, overflow: "hidden", children: [_jsx(Icon, { as: item.icon, fontSize: SIDEBAR_ROW.iconSize, flexShrink: 0, color: isActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" }), _jsx(Box, { flex: "1", minW: 0, children: _jsx(OverflowTooltipText, { placement: "right", maxWidth: "100%", fontSize: itemFontSize, fontWeight: isActive ? 600 : 500, color: isActive ? activeAccent.label : theme.colors.white, _groupHover: isActive ? undefined : { color: theme.colors.white }, as: "p", children: item.title }) })] }), item.locked ? (_jsx(Icon, { as: LockKeyhole, fontSize: "sm", flexShrink: 0, color: theme.colors.sidebar.mutedIcon })) : (badgeVal != null && (_jsx(Badge, { ...softBadge(theme.colors, colorScheme, sidebarRowPeak(theme.colors.sidebar.background[500])), fontSize: "xs", variant: variant, borderRadius: radius, children: badgeVal })))] }));
|
|
64
65
|
return item.locked && item.lockedReason ? (_jsx(ToolTip, { label: item.lockedReason, placement: "top", children: row })) : (row);
|
|
65
66
|
};
|
|
66
67
|
const SecondaryBar = ({ items = [], defaultCollapsed = true, collapsedWidth = "3.5rem", expandedWidth = "10rem", headerFontSize = SIDEBAR_ROW.fontSize, itemFontSize = SIDEBAR_ROW.fontSize, selected, onItemClick, title, icon, }) => {
|
|
@@ -69,7 +70,7 @@ const SecondaryBar = ({ items = [], defaultCollapsed = true, collapsedWidth = "3
|
|
|
69
70
|
const scrollContainerId = sanitizeId(useId());
|
|
70
71
|
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
71
72
|
const width = collapsed ? collapsedWidth : expandedWidth;
|
|
72
|
-
return (_jsx(Box, { as: "nav", "aria-label": title ? `${title} navigation` : "Secondary navigation", transition: transitionFor(["width"], "base"), pos: "sticky", top: 0, sx: fullViewportHeight, w: width, bg: theme.colors.sidebar.background[500], borderLeft: "1px solid", borderColor: theme.colors.sidebar.divider, boxShadow: theme.shadows.md, zIndex: 1, children: _jsxs(VStack, { align: "stretch", spacing: 0, h: "100%", children: [_jsxs(Flex, { align: "center", justify: collapsed ? "center" : "space-between", px: collapsed ? 0 : "10px", gap: "7px", h: HEADER_HEIGHT, overflow: "hidden", borderBottom: "1px solid", borderColor: theme.colors.sidebar.divider, flexShrink: 0, children: [!collapsed && (_jsxs(_Fragment, { children: [icon && (_jsx(Icon, { as: icon, fontSize: SIDEBAR_ROW.iconSize, color: activeAccent.icon, flexShrink: 0 })), _jsx(Box, { flex: "1", minW: 0, children: _jsx(OverflowTooltipText, { placement: "right", maxWidth: "100%", fontSize: headerFontSize, fontWeight: 600, color: theme.colors.white, as: "p", children: title !== null && title !== void 0 ? title : "" }) })] })), _jsx(Box, { as: "button", type: "button", "aria-label": collapsed ? "Expand navigation panel" : "Collapse navigation panel", "aria-expanded": !collapsed, border: "none", bg: "transparent", w: "24px", h: "24px", borderRadius: "lg", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", flexShrink: 0, onClick: () => setCollapsed((c) => !c), _hover: { bg: "whiteAlpha.200" }, transition: "background 0.15s", children: collapsed ? (_jsx(ToolTip, { label: title !== null && title !== void 0 ? title : "", placement: "right", children: _jsx(ChevronRight, { size: 13, color: theme.colors.sidebar.mutedIcon }) })) : (_jsx(ChevronLeft, { size: 13, color: theme.colors.sidebar.mutedIcon })) })] }), _jsx(Box, { id: scrollContainerId, flex: "1", minH: 0, overflowY: "auto", overflowX: "hidden", py: 2, px: "6px", sx: cssForScrollBar(theme), children: items.map((item) => {
|
|
73
|
+
return (_jsx(Box, { as: "nav", "aria-label": title ? `${title} navigation` : "Secondary navigation", transition: transitionFor(["width"], "base"), pos: "sticky", top: 0, sx: fullViewportHeight, w: width, bg: theme.colors.sidebar.background[500], borderLeft: "1px solid", borderColor: theme.colors.sidebar.divider, boxShadow: theme.shadows.md, zIndex: 1, children: _jsxs(VStack, { align: "stretch", spacing: 0, h: "100%", children: [_jsxs(Flex, { align: "center", justify: collapsed ? "center" : "space-between", px: collapsed ? 0 : "10px", gap: "7px", h: HEADER_HEIGHT, overflow: "hidden", borderBottom: "1px solid", borderColor: theme.colors.sidebar.divider, flexShrink: 0, children: [!collapsed && (_jsxs(_Fragment, { children: [icon && (_jsx(Icon, { as: icon, fontSize: SIDEBAR_ROW.iconSize, color: activeAccent.icon, flexShrink: 0 })), _jsx(Box, { flex: "1", minW: 0, children: _jsx(OverflowTooltipText, { placement: "right", maxWidth: "100%", fontSize: headerFontSize, fontWeight: 600, color: theme.colors.white, as: "p", children: title !== null && title !== void 0 ? title : "" }) })] })), _jsx(Box, { as: "button", type: "button", "aria-label": collapsed ? "Expand navigation panel" : "Collapse navigation panel", "aria-expanded": !collapsed, border: "none", bg: "transparent", w: "24px", h: "24px", borderRadius: "lg", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", flexShrink: 0, onClick: () => setCollapsed((c) => !c), _hover: { bg: "whiteAlpha.200" }, transition: "background 0.15s", sx: sidebarFocusStyles(theme.colors), children: collapsed ? (_jsx(ToolTip, { label: title !== null && title !== void 0 ? title : "", placement: "right", children: _jsx(ChevronRight, { size: 13, color: theme.colors.sidebar.mutedIcon }) })) : (_jsx(ChevronLeft, { size: 13, color: theme.colors.sidebar.mutedIcon })) })] }), _jsx(Box, { id: scrollContainerId, flex: "1", minH: 0, overflowY: "auto", overflowX: "hidden", py: 2, px: "6px", sx: cssForScrollBar(theme), children: items.map((item) => {
|
|
73
74
|
var _a;
|
|
74
75
|
const isActive = String(selected) === String(item.id) ||
|
|
75
76
|
String(selected) === String(item.title);
|
|
@@ -7,7 +7,8 @@ import { sanitizeId } from "./sidebarId.js";
|
|
|
7
7
|
import { cssForScrollBar, HEADER_HEIGHT } from "../../Constants/Sidebar.js";
|
|
8
8
|
import { overlayScrimStyles } from "../../Theme/chakra/overlay.styles.js";
|
|
9
9
|
import { REDUCED_MOTION_QUERY } from "../../Theme/chakra/motion.js";
|
|
10
|
-
import {
|
|
10
|
+
import { sidebarFocusStyles } from "./sidebarFocus.js";
|
|
11
|
+
import { MOTION_EASE, MOTION_MS } from "../../Theme/tokens/primitives/motion.js";
|
|
11
12
|
import { ArrowLeftToLine, ArrowRightToLine, ChevronLeft, Maximize2, MoreVertical, PanelLeft, Plus, X } from "lucide-react";
|
|
12
13
|
// Overlay stacking — mirrors theme.zIndices (overlay=1300 scrim, modal=1400 panel).
|
|
13
14
|
const Z_SCRIM = 1300;
|
|
@@ -18,6 +19,18 @@ const Z_PANEL = 1400;
|
|
|
18
19
|
const RAIL_WIDTH = "4.688rem";
|
|
19
20
|
const PANEL_EASE = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
20
21
|
const EXPANDED_WIDTH = "13rem";
|
|
22
|
+
const COLLAPSE_MS = { ...MOTION_MS, slow: 260 };
|
|
23
|
+
const NO_ACTIVE_MENU = "\u0000pinned-active";
|
|
24
|
+
const inlineTransitions = (collapse) => {
|
|
25
|
+
var _a;
|
|
26
|
+
if (collapse === "none")
|
|
27
|
+
return { width: "none", shadow: "none" };
|
|
28
|
+
const ms = COLLAPSE_MS[(_a = collapse.speed) !== null && _a !== void 0 ? _a : "slow"];
|
|
29
|
+
return {
|
|
30
|
+
width: `width ${ms}ms ${PANEL_EASE}`,
|
|
31
|
+
shadow: collapse.animateShadow === false ? "none" : `box-shadow ${ms}ms ${MOTION_EASE}`,
|
|
32
|
+
};
|
|
33
|
+
};
|
|
21
34
|
const OVERLAY_WIDTH = "14.7rem";
|
|
22
35
|
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
23
36
|
import MenuItems from "./components/MenuItems.js";
|
|
@@ -25,6 +38,7 @@ import DefaultLogo from "../../Assets/defaultLogo.js";
|
|
|
25
38
|
import OverflowTooltipText from "./components/OverflowTooltipText.js";
|
|
26
39
|
import SecondaryBar from "./SecondaryBar.js";
|
|
27
40
|
import OtherApps from "./components/OtherApps.js";
|
|
41
|
+
import PinnedSection from "./components/PinnedSection.js";
|
|
28
42
|
import { SIDEBAR_CHROME, SIDEBAR_ROW } from "./sidebarScale.js";
|
|
29
43
|
import { fullViewportHeight } from "../../Theme/tokens/builders/viewportHeight.js";
|
|
30
44
|
const SidebarHeader = ({ logo, companyName, companySubtitle, toggle, isLoading, onToggle, overlay, onClose, }) => {
|
|
@@ -41,6 +55,7 @@ const SidebarHeader = ({ logo, companyName, companySubtitle, toggle, isLoading,
|
|
|
41
55
|
// some brands (zinc), making the tile disappear. See THEME-CONTRACT.md sidebar rules.
|
|
42
56
|
bg: theme.colors.sidebar.activeBg, color: activeAccent.label, borderRadius: "lg", boxSize: "1.75rem", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: "bold", flexShrink: 0, cursor: toggle ? "pointer" : undefined, onClick: toggle ? onToggle : undefined, children: _jsx(DefaultLogo, {}) }));
|
|
43
57
|
return (_jsxs(Flex, { h: HEADER_HEIGHT, w: "100%", alignItems: "center", justifyContent: toggle ? "center" : "flex-start", gap: 2, px: toggle ? "8px" : "10px", borderBottom: "1px solid", borderColor: theme.colors.sidebar.divider, minW: 0, overflow: "hidden", flexShrink: 0, children: [logoNode, !toggle && (_jsxs(Box, { flex: "1", minW: 0, children: [_jsx(OverflowTooltipText, { placement: "right", fontWeight: 600, fontSize: SIDEBAR_CHROME.titleFontSize, textAlign: "left", color: theme.colors.white, lineHeight: "1.3", as: "p", width: "100%", children: companyName !== null && companyName !== void 0 ? companyName : "" }), companySubtitle && (_jsx(Text, { fontSize: SIDEBAR_CHROME.metaFontSize, color: theme.colors.sidebar.mutedLabel, lineHeight: "1.3", isTruncated: true, children: companySubtitle }))] })), !toggle && (_jsx(Box, { as: "button", type: "button", onClick: overlay ? onClose : onToggle, "aria-label": overlay ? "Close menu" : "Collapse sidebar", w: "22px", h: "22px", borderRadius: "lg", bg: "whiteAlpha.200", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, position: "relative", _hover: { bg: "whiteAlpha.300" }, transition: "background 0.15s", sx: {
|
|
58
|
+
...sidebarFocusStyles(theme.colors),
|
|
44
59
|
"&::after": {
|
|
45
60
|
content: '""',
|
|
46
61
|
position: "absolute",
|
|
@@ -59,7 +74,7 @@ const SkeletonMenuRow = ({ toggle }) => {
|
|
|
59
74
|
const AddNewButton = ({ toggle, onClick, overlay }) => {
|
|
60
75
|
const theme = useCustomTheme();
|
|
61
76
|
const activeAccent = getSidebarActiveAccent(theme.colors);
|
|
62
|
-
return (_jsxs(Flex, { as: "button", type: "button", "data-group": "", "aria-label": "Add New", alignItems: "center", gap: "9px", px: "10px", py: overlay ? "11px" : "7px", borderRadius: "lg", cursor: "pointer", border: "none", bg: "transparent", w: "100%", mb: "2px", justifyContent: toggle ? "center" : "flex-start", _hover: { backgroundColor: "whiteAlpha.200" }, transition: "background 0.12s", onClick: onClick, children: [_jsx(Box, { w: "18px", h: "18px", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: _jsx(Plus, { size: 16, color: activeAccent.icon }) }), !toggle && (_jsx(Text, { fontSize: SIDEBAR_ROW.fontSize, color: theme.colors.white, fontWeight: "medium", children: "Add New" }))] }));
|
|
77
|
+
return (_jsxs(Flex, { as: "button", type: "button", "data-group": "", "aria-label": "Add New", alignItems: "center", gap: "9px", px: "10px", py: overlay ? "11px" : "7px", borderRadius: "lg", cursor: "pointer", border: "none", bg: "transparent", w: "100%", mb: "2px", justifyContent: toggle ? "center" : "flex-start", _hover: { backgroundColor: "whiteAlpha.200" }, transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors), onClick: onClick, children: [_jsx(Box, { w: "18px", h: "18px", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: _jsx(Plus, { size: 16, color: activeAccent.icon }) }), !toggle && (_jsx(Text, { fontSize: SIDEBAR_ROW.fontSize, color: theme.colors.white, fontWeight: "medium", children: "Add New" }))] }));
|
|
63
78
|
};
|
|
64
79
|
const deriveInitials = (user) => {
|
|
65
80
|
if (user.initials)
|
|
@@ -77,13 +92,13 @@ const UserFooter = ({ user, toggle, overlay }) => {
|
|
|
77
92
|
const avatar = user.avatarUrl ? (_jsx(Image, { src: user.avatarUrl, alt: user.name, boxSize: "26px", borderRadius: "full", objectFit: "cover", flexShrink: 0 })) : (_jsx(Box, { boxSize: "26px", borderRadius: "full",
|
|
78
93
|
// Same rail-safe pairing as the logo tile — primary.opacity[32] was invisible on zinc/skyline.
|
|
79
94
|
bg: theme.colors.sidebar.activeBg, color: activeAccent.label, display: "flex", alignItems: "center", justifyContent: "center", fontSize: SIDEBAR_CHROME.metaFontSize, fontWeight: 600, flexShrink: 0, children: deriveInitials(user) }));
|
|
80
|
-
return (_jsx(Box, { borderTop: "1px solid", borderColor: theme.colors.sidebar.divider, py: 2, px: "6px", flexShrink: 0, children: _jsxs(Flex, { as: "button", type: "button", onClick: user.onClick, align: "center", gap: 2, px: 2, py: overlay ? "10px" : "6px", w: "100%", borderRadius: "lg", cursor: "pointer", bg: "transparent", border: "none", justify: toggle ? "center" : "flex-start", _hover: { bg: "whiteAlpha.200" }, transition: "background 0.12s", children: [avatar, !toggle && (_jsxs(Box, { flex: "1", minW: 0, textAlign: "left", children: [_jsx(Text, { fontSize: SIDEBAR_CHROME.labelFontSize, fontWeight: 500, color: theme.colors.white, isTruncated: true, children: user.name }), user.role && (_jsx(Text, { fontSize: SIDEBAR_CHROME.metaFontSize, color: theme.colors.sidebar.mutedLabel, isTruncated: true, children: user.role }))] })), !toggle && _jsx(MoreVertical, { size: 14, color: theme.colors.sidebar.mutedIcon })] }) }));
|
|
95
|
+
return (_jsx(Box, { borderTop: "1px solid", borderColor: theme.colors.sidebar.divider, py: 2, px: "6px", flexShrink: 0, children: _jsxs(Flex, { as: "button", type: "button", onClick: user.onClick, align: "center", gap: 2, px: 2, py: overlay ? "10px" : "6px", w: "100%", borderRadius: "lg", cursor: "pointer", bg: "transparent", border: "none", justify: toggle ? "center" : "flex-start", _hover: { bg: "whiteAlpha.200" }, transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors), children: [avatar, !toggle && (_jsxs(Box, { flex: "1", minW: 0, textAlign: "left", children: [_jsx(Text, { fontSize: SIDEBAR_CHROME.labelFontSize, fontWeight: 500, color: theme.colors.white, isTruncated: true, children: user.name }), user.role && (_jsx(Text, { fontSize: SIDEBAR_CHROME.metaFontSize, color: theme.colors.sidebar.mutedLabel, isTruncated: true, children: user.role }))] })), !toggle && _jsx(MoreVertical, { size: 14, color: theme.colors.sidebar.mutedIcon })] }) }));
|
|
81
96
|
};
|
|
82
97
|
// Footer "cell" — one segment of the Collapse | Hide/Show control.
|
|
83
98
|
const ControlCell = ({ onClick, ariaLabel, label, accent, children }) => {
|
|
84
99
|
const theme = useCustomTheme();
|
|
85
100
|
const activeAccent = getSidebarActiveAccent(theme.colors);
|
|
86
|
-
return (_jsxs(Flex, { as: "button", type: "button", "aria-label": ariaLabel, flex: "1", minW: 0, alignItems: "center", justifyContent: "center", gap: "7px", px: "10px", py: "14px", cursor: "pointer", border: "none", bg: accent ? "whiteAlpha.300" : "transparent", onClick: onClick, _hover: { bg: accent ? "whiteAlpha.400" : "whiteAlpha.200" }, transition: "background 0.15s", children: [children, label && (_jsx(Text, { fontSize: SIDEBAR_CHROME.labelFontSize, color: accent ? activeAccent.label : theme.colors.sidebar.mutedLabel, fontWeight: "medium", children: label }))] }));
|
|
101
|
+
return (_jsxs(Flex, { as: "button", type: "button", "aria-label": ariaLabel, flex: "1", minW: 0, alignItems: "center", justifyContent: "center", gap: "7px", px: "10px", py: "14px", cursor: "pointer", border: "none", bg: accent ? "whiteAlpha.300" : "transparent", onClick: onClick, _hover: { bg: accent ? "whiteAlpha.400" : "whiteAlpha.200" }, transition: "background 0.15s", sx: sidebarFocusStyles(theme.colors), children: [children, label && (_jsx(Text, { fontSize: SIDEBAR_CHROME.labelFontSize, color: accent ? activeAccent.label : theme.colors.sidebar.mutedLabel, fontWeight: "medium", children: label }))] }));
|
|
87
102
|
};
|
|
88
103
|
// Collapse (mini-rail) sits next to the Hide/Show navbar-mode toggle. In the
|
|
89
104
|
// desktop mobile-nav overlay only "Show" (return to the docked sidebar) is shown;
|
|
@@ -102,7 +117,7 @@ const FooterControls = ({ toggle, overlay, mobileNav, onToggleCollapse, onToggle
|
|
|
102
117
|
return shell(_jsxs(_Fragment, { children: [_jsx(ControlCell, { onClick: onToggleCollapse, ariaLabel: toggle ? "Expand sidebar" : undefined, label: toggle ? undefined : "Collapse", children: toggle ? (_jsx(ArrowRightToLine, { size: 14, color: iconColor })) : (_jsx(ArrowLeftToLine, { size: 14, color: iconColor })) }), onToggleMobileNav && (_jsx(ControlCell, { onClick: onToggleMobileNav, ariaLabel: toggle ? "Show sidebar in overlay mode" : undefined, label: toggle ? undefined : "Hide", children: _jsx(Maximize2, { size: 14, color: iconColor }) }))] }));
|
|
103
118
|
};
|
|
104
119
|
// --- Main Sidebar component ---
|
|
105
|
-
export default function Sidebar({ menus, activeMenu, handleMenuClick, handleSecondaryNavClick, toggle, changeToggle, logo, companyName, companySubtitle, user, isDisableAddNew = false, onAddNewClick, selectedSubMenu, selectedSecondaryItem, isLoading, loadingLines, open = true, otherApps, mobileOpen = false, onMobileClose, mobileNav = false, onToggleMobileNav, peekOpen = false, onPeekEnd, }) {
|
|
120
|
+
export default function Sidebar({ menus, activeMenu, handleMenuClick, handleSecondaryNavClick, toggle, changeToggle, logo, companyName, companySubtitle, user, isDisableAddNew = false, onAddNewClick, selectedSubMenu, selectedSecondaryItem, isLoading, loadingLines, open = true, otherApps, mobileOpen = false, onMobileClose, mobileNav = false, onToggleMobileNav, peekOpen = false, onPeekEnd, pinnedItems, pinnedLabel, onUnpin, activePinnedId, pinnedLimit, pinnedLabels, collapseTransition = { speed: "slow" }, autoCollapse = true, }) {
|
|
106
121
|
var _a, _b;
|
|
107
122
|
const theme = useCustomTheme();
|
|
108
123
|
const scrollContainerId = sanitizeId(useId());
|
|
@@ -142,20 +157,24 @@ export default function Sidebar({ menus, activeMenu, handleMenuClick, handleSeco
|
|
|
142
157
|
const activeTop = menus.find((m) => String(m.title) === String(activeMenu)) ||
|
|
143
158
|
menus.find((m) => String(m.id) === String(activeMenu));
|
|
144
159
|
const secondaryItems = (_b = activeTop === null || activeTop === void 0 ? void 0 : activeTop.secondaryItems) !== null && _b !== void 0 ? _b : [];
|
|
160
|
+
const inline = inlineTransitions(collapseTransition);
|
|
161
|
+
const pinnedActive = activePinnedId != null && !!(pinnedItems === null || pinnedItems === void 0 ? void 0 : pinnedItems.some((p) => String(p.id) === String(activePinnedId)));
|
|
145
162
|
// Sync toggle with external `open` prop.
|
|
146
163
|
// `open=false` → collapsed (toggle=true); `open=true` → expanded (toggle=false).
|
|
147
164
|
useEffect(() => {
|
|
165
|
+
if (!autoCollapse)
|
|
166
|
+
return;
|
|
148
167
|
const shouldBeCollapsed = !open;
|
|
149
168
|
if (shouldBeCollapsed !== toggleRef.current) {
|
|
150
169
|
changeToggleRef.current();
|
|
151
170
|
}
|
|
152
|
-
}, [open]);
|
|
171
|
+
}, [open, autoCollapse]);
|
|
153
172
|
// Auto-collapse the main sidebar whenever a secondary panel is active.
|
|
154
173
|
useEffect(() => {
|
|
155
|
-
if (secondaryItems.length > 0 && !toggleRef.current) {
|
|
174
|
+
if (autoCollapse && secondaryItems.length > 0 && !toggleRef.current) {
|
|
156
175
|
changeToggleRef.current();
|
|
157
176
|
}
|
|
158
|
-
}, [secondaryItems.length]);
|
|
177
|
+
}, [secondaryItems.length, autoCollapse]);
|
|
159
178
|
// Esc closes the overlay.
|
|
160
179
|
useEffect(() => {
|
|
161
180
|
if (!isOverlayOpen)
|
|
@@ -257,12 +276,14 @@ export default function Sidebar({ menus, activeMenu, handleMenuClick, handleSeco
|
|
|
257
276
|
base: dragging
|
|
258
277
|
? "none"
|
|
259
278
|
: "transform 0.28s cubic-bezier(0.22, 1, 0.36, 1)",
|
|
260
|
-
lg:
|
|
279
|
+
lg: inline.shadow,
|
|
261
280
|
}, borderRadius: "none", boxShadow: mobileNav ? theme.shadows["2xl"] : { base: theme.shadows["2xl"], lg: theme.shadows.lg }, display: "flex", background: theme.colors.sidebar.background[500], borderRight: "1px solid", borderColor: theme.colors.sidebar.divider, role: overlay ? "dialog" : undefined, "aria-modal": overlay ? true : undefined, "aria-label": overlay ? "Main navigation" : undefined, ...(overlay && !isOverlayOpen ? { inert: "" } : {}), sx: {
|
|
262
281
|
...fullViewportHeight,
|
|
263
282
|
touchAction: overlay ? "pan-y" : "auto",
|
|
264
283
|
[REDUCED_MOTION_QUERY]: { "&, & *": { transition: "none", animation: "none" } },
|
|
265
284
|
}, onMouseLeave: overlayPeeking ? onPeekEnd : undefined, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerUp, children: [_jsx(Box, { width: mobileNav
|
|
266
285
|
? OVERLAY_WIDTH
|
|
267
|
-
: { base: OVERLAY_WIDTH, lg: effToggle ? RAIL_WIDTH : EXPANDED_WIDTH }, transition:
|
|
286
|
+
: { base: OVERLAY_WIDTH, lg: effToggle ? RAIL_WIDTH : EXPANDED_WIDTH }, transition: mobileNav
|
|
287
|
+
? `width 0.26s ${PANEL_EASE}`
|
|
288
|
+
: { base: `width 0.26s ${PANEL_EASE}`, lg: inline.width }, display: "flex", flexDirection: "column", justifyContent: "space-between", sx: { "@media (prefers-reduced-motion: reduce)": { transition: "none" } }, children: _jsxs(Flex, { flexDir: "column", w: "100%", as: "nav", "aria-label": "Main navigation", h: "100%", children: [_jsx(SidebarHeader, { logo: logo, companyName: companyName, companySubtitle: companySubtitle, toggle: effToggle, isLoading: isLoading, onToggle: changeToggle, overlay: overlay, onClose: onMobileClose }), _jsx(Box, { id: scrollContainerId, flex: "1", minH: 0, w: "100%", overflowY: "auto", overflowX: "hidden", py: 2, px: "6px", sx: cssForScrollBar(theme), children: isLoading ? (Array.from({ length: loadingLines !== null && loadingLines !== void 0 ? loadingLines : 15 }).map((_, i) => (_jsx(SkeletonMenuRow, { toggle: effToggle }, i)))) : (_jsxs(_Fragment, { children: [pinnedItems && pinnedItems.length > 0 && (_jsx(PinnedSection, { items: pinnedItems, label: pinnedLabel, limit: pinnedLimit, toggle: effToggle, overlay: overlay, activeId: activePinnedId, labels: pinnedLabels, onItemClick: handleMenuClickInternal, onUnpin: onUnpin })), _jsx(MenuItems, { activeMenu: pinnedActive ? NO_ACTIVE_MENU : activeMenu, menus: menus, handleMenuClick: handleMenuClickInternal, toggle: effToggle, selectedSubMenu: selectedSubMenu, overlay: overlay, containerId: scrollContainerId })] })) }), _jsxs(Box, { flexShrink: 0, children: [!isDisableAddNew && (_jsx(Box, { borderTop: "1px solid", borderColor: theme.colors.sidebar.divider, py: 2, px: "6px", children: _jsx(AddNewButton, { toggle: effToggle, onClick: onAddNewClick, overlay: overlay }) })), _jsx(OtherApps, { toggle: effToggle, otherApps: otherApps !== null && otherApps !== void 0 ? otherApps : [] }), user && _jsx(UserFooter, { user: user, toggle: effToggle, overlay: overlay }), _jsx(FooterControls, { toggle: effToggle, overlay: overlay, mobileNav: mobileNav, onToggleCollapse: changeToggle, onToggleMobileNav: onToggleMobileNav })] })] }) }), secondaryItems.length > 0 && (_jsx(SecondaryBar, { items: secondaryItems, defaultCollapsed: effToggle, collapsedWidth: "3.5rem", expandedWidth: "10rem", selected: selectedSecondaryItem, onItemClick: handleSecondaryNavClick, title: activeTop === null || activeTop === void 0 ? void 0 : activeTop.title, icon: activeTop === null || activeTop === void 0 ? void 0 : activeTop.icon })), overlay && (_jsx(Box, { display: mobileOpen ? "block" : "none", position: "absolute", top: "50%", right: "6px", transform: "translateY(-50%)", w: "4px", h: "42px", borderRadius: "full", bg: theme.colors.sidebar.mutedIcon, "aria-hidden": "true" }))] })] }));
|
|
268
289
|
}
|
|
@@ -12,13 +12,14 @@ import { scaleFor } from "../../../Theme/chakra/paletteFromTheme.js";
|
|
|
12
12
|
import TruncatedTextWithTooltip from "./TextTruncation.js";
|
|
13
13
|
import { sanitizeId } from "../sidebarId.js";
|
|
14
14
|
import ToolTip from "../../ToolTip/ToolTip.js";
|
|
15
|
+
import { sidebarFocusStyles } from "../sidebarFocus.js";
|
|
15
16
|
import { SIDEBAR_ROW } from "../sidebarScale.js";
|
|
16
17
|
/**
|
|
17
18
|
* These rows are real `<a href>`s now, so the browser's own gestures have to keep working —
|
|
18
19
|
* cmd/ctrl-click for a new tab, shift for a window, middle-click. Only a plain left click is
|
|
19
20
|
* routed through the in-app handler.
|
|
20
21
|
*/
|
|
21
|
-
const isBrowserNavigationGesture = (e) => e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0;
|
|
22
|
+
export const isBrowserNavigationGesture = (e) => e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0;
|
|
22
23
|
const MenuItemBox = ({ menu, activeMenu, toggle, handleClick, isOpen, onToggle, overlay = false, }) => {
|
|
23
24
|
var _a, _b, _c, _d, _e;
|
|
24
25
|
const theme = useCustomTheme();
|
|
@@ -86,6 +87,7 @@ const MenuItemBox = ({ menu, activeMenu, toggle, handleClick, isOpen, onToggle,
|
|
|
86
87
|
cursor: isBlocked ? "not-allowed" : "pointer",
|
|
87
88
|
opacity: isBlocked ? 0.6 : 1,
|
|
88
89
|
transition: "background 0.12s",
|
|
90
|
+
sx: sidebarFocusStyles(theme.colors),
|
|
89
91
|
};
|
|
90
92
|
const buttonContent = (_jsxs(Flex, { align: "center", gap: "9px", justify: toggle ? "center" : "space-between", w: "100%", children: [_jsxs(Flex, { align: "center", gap: "9px", overflow: "hidden", children: [_jsxs(Box, { position: "relative", display: "flex", alignItems: "center", children: [_jsx(Icon, { as: icon, fontSize: "md", verticalAlign: "middle", color: isAnyActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isAnyActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" }), toggle && (badgeValue != null || hasSubmenuBadge) && (_jsx(Box, { position: "absolute", top: "0", right: "0", w: "0.375rem", h: "0.375rem", borderRadius: "full", bg: theme.colors.sidebar.semanticText.error }))] }), !toggle && (_jsx(TruncatedTextWithTooltip
|
|
91
93
|
// open={isOpen}
|
|
@@ -138,7 +140,7 @@ const MenuItemBox = ({ menu, activeMenu, toggle, handleClick, isOpen, onToggle,
|
|
|
138
140
|
e.preventDefault();
|
|
139
141
|
if (!isSubBlocked)
|
|
140
142
|
handleClick === null || handleClick === void 0 ? void 0 : handleClick(sub);
|
|
141
|
-
}, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, transition: "background 0.12s", children: [_jsxs(Flex, { align: "center", gap: "9px", overflow: "hidden", flex: "1", children: [sub.icon ? (_jsx(Icon, { as: sub.icon, fontSize: SIDEBAR_ROW.iconSize, flexShrink: 0, color: isSubActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isSubActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" })) : (_jsx(Box, { w: "5px", h: "5px", borderRadius: "full", flexShrink: 0, bg: isSubActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isSubActive ? undefined : { bg: theme.colors.white }, transition: "background 0.12s" })), _jsx(TruncatedTextWithTooltip, { fontSize: SIDEBAR_ROW.fontSize, fontWeight: isSubActive ? 600 : 500, lineHeight: "1", color: isSubActive ? activeAccent.label : theme.colors.sidebar.mutedLabel, _groupHover: isSubActive ? undefined : { color: theme.colors.white }, children: sub.title })] }), sub.locked ? (_jsx(Icon, { as: LockKeyhole, fontSize: "md", color: theme.colors.sidebar.mutedIcon })) : (() => {
|
|
143
|
+
}, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors), children: [_jsxs(Flex, { align: "center", gap: "9px", overflow: "hidden", flex: "1", children: [sub.icon ? (_jsx(Icon, { as: sub.icon, fontSize: SIDEBAR_ROW.iconSize, flexShrink: 0, color: isSubActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isSubActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" })) : (_jsx(Box, { w: "5px", h: "5px", borderRadius: "full", flexShrink: 0, bg: isSubActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isSubActive ? undefined : { bg: theme.colors.white }, transition: "background 0.12s" })), _jsx(TruncatedTextWithTooltip, { fontSize: SIDEBAR_ROW.fontSize, fontWeight: isSubActive ? 600 : 500, lineHeight: "1", color: isSubActive ? activeAccent.label : theme.colors.sidebar.mutedLabel, _groupHover: isSubActive ? undefined : { color: theme.colors.white }, children: sub.title })] }), sub.locked ? (_jsx(Icon, { as: LockKeyhole, fontSize: "md", color: theme.colors.sidebar.mutedIcon })) : (() => {
|
|
142
144
|
var _a, _b, _c, _d;
|
|
143
145
|
const subCfg = typeof sub.badge === "object" && sub.badge !== null
|
|
144
146
|
? sub.badge
|
|
@@ -9,6 +9,7 @@ import { sidebarRowPeak } from "../../../Theme/tokens/builders/sidebarAccent.js"
|
|
|
9
9
|
import { scaleFor } from "../../../Theme/chakra/paletteFromTheme.js";
|
|
10
10
|
import TruncatedTextWithTooltip from "./TextTruncation.js";
|
|
11
11
|
import ToolTip from "../../ToolTip/ToolTip.js";
|
|
12
|
+
import { sidebarFocusStyles } from "../sidebarFocus.js";
|
|
12
13
|
import { SIDEBAR_ROW } from "../sidebarScale.js";
|
|
13
14
|
const MenuPopoverContent = ({ menu, handleMenuClick, activeMenu, }) => {
|
|
14
15
|
var _a;
|
|
@@ -26,7 +27,7 @@ const MenuPopoverContent = ({ menu, handleMenuClick, activeMenu, }) => {
|
|
|
26
27
|
if (!isBlocked)
|
|
27
28
|
handleMenuClick === null || handleMenuClick === void 0 ? void 0 : handleMenuClick(menu);
|
|
28
29
|
}
|
|
29
|
-
}, cursor: !hasSubMenu ? (isBlocked ? "not-allowed" : "pointer") : "default", "aria-disabled": !hasSubMenu && isBlocked ? true : undefined, children: [_jsx(Icon, { as: icon, fontSize: "md", flexShrink: 0, color: !hasSubMenu && isParentActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon }), _jsx(TruncatedTextWithTooltip, { fontWeight: 600, fontSize: SIDEBAR_ROW.fontSize, flex: "1", color: theme.colors.white, children: title }), (() => {
|
|
30
|
+
}, cursor: !hasSubMenu ? (isBlocked ? "not-allowed" : "pointer") : "default", "aria-disabled": !hasSubMenu && isBlocked ? true : undefined, sx: hasSubMenu ? undefined : sidebarFocusStyles(theme.colors, theme.colors.sidebar.elevated), children: [_jsx(Icon, { as: icon, fontSize: "md", flexShrink: 0, color: !hasSubMenu && isParentActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon }), _jsx(TruncatedTextWithTooltip, { fontWeight: 600, fontSize: SIDEBAR_ROW.fontSize, flex: "1", color: theme.colors.white, children: title }), (() => {
|
|
30
31
|
var _a, _b, _c, _d;
|
|
31
32
|
const cfg = typeof badge === "object" && badge !== null
|
|
32
33
|
? badge
|
|
@@ -50,7 +51,7 @@ const MenuPopoverContent = ({ menu, handleMenuClick, activeMenu, }) => {
|
|
|
50
51
|
}, px: 2, h: SIDEBAR_ROW.subHeight, mb: "2px", borderRadius: "lg", align: "center", justify: "space-between", gap: 2, borderLeft: "2px solid", borderLeftColor: isSubActive ? theme.colors.sidebar.activeIcon : "transparent", bg: isSubActive ? "whiteAlpha.300" : "transparent", _hover: {
|
|
51
52
|
bg: isSubBlocked ? "transparent" : isSubActive ? "whiteAlpha.400" : "whiteAlpha.200",
|
|
52
53
|
cursor: !isSubBlocked ? "pointer" : "not-allowed",
|
|
53
|
-
}, opacity: isSubBlocked ? 0.5 : 1, pointerEvents: isSubBlocked ? "none" : "auto", transition: "background 0.12s", children: [_jsxs(Flex, { align: "center", gap: "9px", overflow: "hidden", flex: "1", children: [sub.icon ? (_jsx(Icon, { as: sub.icon, fontSize: SIDEBAR_ROW.iconSize, flexShrink: 0, color: isSubActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isSubActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" })) : (_jsx(Box, { w: "5px", h: "5px", borderRadius: "full", flexShrink: 0, bg: isSubActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isSubActive ? undefined : { bg: theme.colors.white }, transition: "background 0.12s" })), _jsx(TruncatedTextWithTooltip, { fontSize: SIDEBAR_ROW.fontSize, fontWeight: isSubActive ? 600 : 500, flex: 1, color: isSubActive ? activeAccent.label : theme.colors.white, _groupHover: isSubActive ? undefined : { color: theme.colors.white }, children: sub.title })] }), sub.locked ? (sub.lockedReason ? (_jsx(ToolTip, { label: sub.lockedReason, placement: "top", children: _jsx(Icon, { as: LockKeyhole, fontSize: "md", color: theme.colors.sidebar.mutedIcon }) })) : (_jsx(Icon, { as: LockKeyhole, fontSize: "md", color: theme.colors.sidebar.mutedIcon }))) : (() => {
|
|
54
|
+
}, opacity: isSubBlocked ? 0.5 : 1, pointerEvents: isSubBlocked ? "none" : "auto", transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors, theme.colors.sidebar.elevated), children: [_jsxs(Flex, { align: "center", gap: "9px", overflow: "hidden", flex: "1", children: [sub.icon ? (_jsx(Icon, { as: sub.icon, fontSize: SIDEBAR_ROW.iconSize, flexShrink: 0, color: isSubActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isSubActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" })) : (_jsx(Box, { w: "5px", h: "5px", borderRadius: "full", flexShrink: 0, bg: isSubActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isSubActive ? undefined : { bg: theme.colors.white }, transition: "background 0.12s" })), _jsx(TruncatedTextWithTooltip, { fontSize: SIDEBAR_ROW.fontSize, fontWeight: isSubActive ? 600 : 500, flex: 1, color: isSubActive ? activeAccent.label : theme.colors.white, _groupHover: isSubActive ? undefined : { color: theme.colors.white }, children: sub.title })] }), sub.locked ? (sub.lockedReason ? (_jsx(ToolTip, { label: sub.lockedReason, placement: "top", children: _jsx(Icon, { as: LockKeyhole, fontSize: "md", color: theme.colors.sidebar.mutedIcon }) })) : (_jsx(Icon, { as: LockKeyhole, fontSize: "md", color: theme.colors.sidebar.mutedIcon }))) : (() => {
|
|
54
55
|
var _a, _b, _c, _d;
|
|
55
56
|
const cfg = typeof sub.badge === "object" && sub.badge !== null
|
|
56
57
|
? sub.badge
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Flex, Text, Popover, PopoverTrigger, PopoverContent, PopoverBody, Portal, } from "@chakra-ui/react";
|
|
3
3
|
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
4
|
+
import { sidebarFocusStyles } from "../sidebarFocus.js";
|
|
4
5
|
const OtherApps = ({ toggle, otherApps }) => {
|
|
5
6
|
const theme = useCustomTheme();
|
|
6
7
|
if (!otherApps || otherApps.length === 0)
|
|
@@ -10,7 +11,7 @@ const OtherApps = ({ toggle, otherApps }) => {
|
|
|
10
11
|
return (_jsx(Box, { display: "flex", alignItems: "center", justifyContent: "center", color: theme.colors.sidebar.mutedIcon, fontSize: "15px", flexShrink: 0, children: (_a = app.icon) !== null && _a !== void 0 ? _a : (_jsx(Box, { w: "0.5rem", h: "0.5rem", borderRadius: "full", bg: app.color, boxShadow: `0 0 5px ${app.color}`, flexShrink: 0 })) }));
|
|
11
12
|
};
|
|
12
13
|
// Grid cell: icon on top, truncated label below (label hidden when collapsed).
|
|
13
|
-
const appCell = (app) => (_jsxs(Flex, { as: "button", type: "button", direction: "column", align: "center", justify: "center", gap: "3px", px: "2px", py: "6px", borderRadius: "lg", cursor: "pointer", border: "none", bg: "transparent", w: "100%", _hover: { backgroundColor: "whiteAlpha.200" }, transition: "background 0.12s", onClick: app.onClick, children: [appIcon(app), !toggle && (_jsx(Text, { fontSize: "9.5px", color: theme.colors.sidebar.mutedLabel, textAlign: "center", maxW: "44px", isTruncated: true, children: app.name }))] }));
|
|
14
|
+
const appCell = (app) => (_jsxs(Flex, { as: "button", type: "button", direction: "column", align: "center", justify: "center", gap: "3px", px: "2px", py: "6px", borderRadius: "lg", cursor: "pointer", border: "none", bg: "transparent", w: "100%", _hover: { backgroundColor: "whiteAlpha.200" }, transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors), onClick: app.onClick, children: [appIcon(app), !toggle && (_jsx(Text, { fontSize: "9.5px", color: theme.colors.sidebar.mutedLabel, textAlign: "center", maxW: "44px", isTruncated: true, children: app.name }))] }));
|
|
14
15
|
const flyoutBody = (app) => {
|
|
15
16
|
var _a;
|
|
16
17
|
return (_jsxs(_Fragment, { children: [_jsxs(Flex, { alignItems: "center", gap: 2, mb: 1, onClick: app.onClick, children: [(_a = app.icon) !== null && _a !== void 0 ? _a : (_jsx(Box, { w: "0.5rem", h: "0.5rem", borderRadius: "full", bg: app.color, boxShadow: `0 0 5px ${app.color}`, flexShrink: 0 })), _jsx(Text, { fontSize: "sm", fontWeight: 700, color: theme.colors.white, children: app.name })] }), app.description && (_jsx(Text, { fontSize: "xs", color: theme.colors.sidebar.mutedLabel, children: app.description }))] }));
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useState } from "react";
|
|
3
|
+
import { Badge, Box, Flex, Icon } from "@chakra-ui/react";
|
|
4
|
+
import { ChevronDown, ChevronUp, PinOff } from "lucide-react";
|
|
5
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
6
|
+
import { sidebarFocusStyles } from "../sidebarFocus.js";
|
|
7
|
+
import { resolveSoftTone } from "../../../Theme/tokens/builders/tone.js";
|
|
8
|
+
import { sidebarRowPeak } from "../../../Theme/tokens/builders/sidebarAccent.js";
|
|
9
|
+
import { scaleFor } from "../../../Theme/chakra/paletteFromTheme.js";
|
|
10
|
+
import { getSidebarActiveAccent } from "../sidebarAccentColors.js";
|
|
11
|
+
import { sanitizeId } from "../sidebarId.js";
|
|
12
|
+
import { SIDEBAR_ROW } from "../sidebarScale.js";
|
|
13
|
+
import ToolTip from "../../ToolTip/ToolTip.js";
|
|
14
|
+
import TruncatedTextWithTooltip from "./TextTruncation.js";
|
|
15
|
+
import { isBrowserNavigationGesture } from "./MenuItemBox.js";
|
|
16
|
+
export const DEFAULT_PINNED_LABEL = "Pinned";
|
|
17
|
+
export const DEFAULT_PINNED_LIMIT = 5;
|
|
18
|
+
const DEFAULT_LABELS = {
|
|
19
|
+
showMore: "Show {count} more",
|
|
20
|
+
showLess: "Show less",
|
|
21
|
+
unpin: "Unpin {title}",
|
|
22
|
+
};
|
|
23
|
+
const UNPIN_SIZE = "24px";
|
|
24
|
+
const LADDER = { hover: "whiteAlpha.200", active: "whiteAlpha.300", activeHover: "whiteAlpha.400" };
|
|
25
|
+
const VISUALLY_HIDDEN = {
|
|
26
|
+
position: "absolute",
|
|
27
|
+
width: "1px",
|
|
28
|
+
height: "1px",
|
|
29
|
+
padding: 0,
|
|
30
|
+
margin: "-1px",
|
|
31
|
+
clip: "rect(0, 0, 0, 0)",
|
|
32
|
+
whiteSpace: "nowrap",
|
|
33
|
+
border: 0,
|
|
34
|
+
};
|
|
35
|
+
const readBadge = (badge) => {
|
|
36
|
+
var _a;
|
|
37
|
+
const cfg = typeof badge === "object" && badge !== null ? badge : null;
|
|
38
|
+
const value = (_a = cfg === null || cfg === void 0 ? void 0 : cfg.count) !== null && _a !== void 0 ? _a : (typeof badge !== "object" ? badge : undefined);
|
|
39
|
+
return { cfg, value };
|
|
40
|
+
};
|
|
41
|
+
const PinnedSection = ({ items, label = DEFAULT_PINNED_LABEL, limit = DEFAULT_PINNED_LIMIT, toggle, overlay, activeId, labels, onItemClick, onUnpin, }) => {
|
|
42
|
+
const theme = useCustomTheme();
|
|
43
|
+
const activeAccent = getSidebarActiveAccent(theme.colors);
|
|
44
|
+
const text = { ...DEFAULT_LABELS, ...labels };
|
|
45
|
+
const headingId = `pinned-${sanitizeId(useId())}`;
|
|
46
|
+
const cap = Math.max(1, limit);
|
|
47
|
+
const rowHeight = overlay ? SIDEBAR_ROW.overlayHeight : SIDEBAR_ROW.height;
|
|
48
|
+
const activeIndex = activeId == null ? -1 : items.findIndex((i) => String(i.id) === String(activeId));
|
|
49
|
+
const [showAll, setShowAll] = useState(activeIndex >= cap);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (activeIndex >= cap)
|
|
52
|
+
setShowAll(true);
|
|
53
|
+
}, [activeIndex, cap]);
|
|
54
|
+
if (items.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
const hiddenCount = Math.max(0, items.length - cap);
|
|
57
|
+
const visible = showAll ? items : items.slice(0, cap);
|
|
58
|
+
const badgeSurface = sidebarRowPeak(theme.colors.sidebar.background[500]);
|
|
59
|
+
const unpinReplacesBadge = !!onUnpin && !overlay;
|
|
60
|
+
const renderBadge = (badge) => {
|
|
61
|
+
var _a, _b, _c;
|
|
62
|
+
const { cfg, value } = readBadge(badge);
|
|
63
|
+
if (value == null)
|
|
64
|
+
return null;
|
|
65
|
+
const tone = resolveSoftTone(theme.colors, scaleFor(theme.colors, (_a = cfg === null || cfg === void 0 ? void 0 : cfg.color) !== null && _a !== void 0 ? _a : "teal"), badgeSurface);
|
|
66
|
+
return (_jsx(Badge, { bg: tone.bg, color: tone.fg, fontSize: "xs", variant: (_b = cfg === null || cfg === void 0 ? void 0 : cfg.variant) !== null && _b !== void 0 ? _b : "subtle", borderRadius: (_c = cfg === null || cfg === void 0 ? void 0 : cfg.radius) !== null && _c !== void 0 ? _c : "full", ...(unpinReplacesBadge
|
|
67
|
+
? { _groupHover: { opacity: 0 }, _groupFocusWithin: { opacity: 0 } }
|
|
68
|
+
: {}), children: value }));
|
|
69
|
+
};
|
|
70
|
+
const renderRow = (item) => {
|
|
71
|
+
const isActive = activeIndex >= 0 && String(item.id) === String(activeId);
|
|
72
|
+
const anchor = (_jsx(Box, { as: "a", href: item.url, "data-group": "", "data-pinned-id": String(item.id), "aria-label": toggle ? item.title : undefined, "aria-current": isActive ? "page" : undefined, display: "flex", alignItems: "center", textAlign: "left", textDecoration: "none", w: "100%", h: rowHeight, borderRadius: "lg", pl: "10px", pr: !toggle && onUnpin ? `calc(10px + ${UNPIN_SIZE})` : "10px", fontWeight: "medium", borderLeft: "2px solid", borderLeftColor: isActive ? theme.colors.sidebar.activeIcon : "transparent", backgroundColor: isActive ? LADDER.active : "transparent", color: theme.colors.sidebar.mutedIcon, _hover: { backgroundColor: isActive ? LADDER.activeHover : LADDER.hover }, cursor: "pointer", transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors), onClick: (e) => {
|
|
73
|
+
if (item.url && isBrowserNavigationGesture(e))
|
|
74
|
+
return;
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
onItemClick(item);
|
|
77
|
+
}, children: _jsxs(Flex, { align: "center", gap: "9px", justify: toggle ? "center" : "space-between", w: "100%", children: [_jsxs(Flex, { align: "center", gap: "9px", overflow: "hidden", flex: toggle ? undefined : 1, minW: 0, children: [_jsxs(Box, { position: "relative", display: "flex", alignItems: "center", children: [_jsx(Icon, { as: item.icon, fontSize: "md", color: isActive ? activeAccent.icon : theme.colors.sidebar.mutedIcon, _groupHover: isActive ? undefined : { color: theme.colors.white }, transition: "color 0.12s" }), toggle && readBadge(item.badge).value != null && (_jsx(Box, { position: "absolute", top: "0", right: "0", w: "0.375rem", h: "0.375rem", borderRadius: "full", bg: theme.colors.sidebar.semanticText.error }))] }), !toggle && (_jsx(TruncatedTextWithTooltip, { fontSize: SIDEBAR_ROW.fontSize, lineHeight: "1", flex: "1", color: isActive ? activeAccent.label : theme.colors.white, fontWeight: isActive ? 600 : 500, _groupHover: isActive ? undefined : { color: theme.colors.white }, children: item.title }))] }), !toggle && renderBadge(item.badge)] }) }));
|
|
78
|
+
return (_jsxs(Box, { as: "li", listStyleType: "none", position: "relative", "data-group": "", children: [toggle ? (_jsx(ToolTip, { label: item.title, placement: "right", children: anchor })) : (anchor), onUnpin && !toggle && (_jsx(Box, { as: "button", type: "button", "aria-label": text.unpin.replace("{title}", item.title), onClick: () => onUnpin(item), position: "absolute", top: "50%", right: "6px", transform: "translateY(-50%)", w: UNPIN_SIZE, h: UNPIN_SIZE, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "lg", border: "none", bg: "transparent", cursor: "pointer", color: theme.colors.sidebar.mutedIcon, opacity: overlay ? 1 : 0, _groupHover: { opacity: 1 }, _groupFocusWithin: { opacity: 1 }, _hover: {
|
|
79
|
+
bg: LADDER.active,
|
|
80
|
+
color: theme.colors.white,
|
|
81
|
+
}, transition: "opacity 0.12s, background 0.12s", sx: sidebarFocusStyles(theme.colors), children: _jsx(Icon, { as: PinOff, fontSize: "sm" }) }))] }, String(item.id)));
|
|
82
|
+
};
|
|
83
|
+
const disclosureLabel = showAll
|
|
84
|
+
? text.showLess
|
|
85
|
+
: text.showMore.replace("{count}", String(hiddenCount));
|
|
86
|
+
const disclosureButton = (_jsxs(Box, { as: "button", type: "button", "aria-expanded": showAll, "aria-label": toggle ? disclosureLabel : undefined, onClick: () => setShowAll((v) => !v), display: "flex", alignItems: "center", justifyContent: toggle ? "center" : "flex-start", gap: "9px", w: "100%", h: rowHeight, px: "10px", borderRadius: "lg", border: "none", borderLeft: "2px solid transparent", bg: "transparent", cursor: "pointer", color: theme.colors.sidebar.mutedLabel, fontSize: SIDEBAR_ROW.fontSize, fontWeight: "medium", _hover: {
|
|
87
|
+
backgroundColor: LADDER.hover,
|
|
88
|
+
color: theme.colors.white,
|
|
89
|
+
}, transition: "background 0.12s", sx: sidebarFocusStyles(theme.colors), children: [_jsx(Icon, { as: showAll ? ChevronUp : ChevronDown, fontSize: "md", flexShrink: 0 }), !toggle && _jsx("span", { children: disclosureLabel })] }));
|
|
90
|
+
return (_jsxs(Box, { mb: toggle ? "5px" : "3px", "data-testid": "sidebar-pinned", children: [_jsx(Box, { id: headingId, as: "h2", px: "10px", pt: overlay ? "6px" : "10px", pb: "3px", fontSize: "9.5px", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.1em", color: theme.colors.sidebar.mutedLabel, whiteSpace: "nowrap", overflow: "hidden", sx: toggle ? VISUALLY_HIDDEN : undefined, children: label }), _jsxs(Box, { as: "ul", "aria-labelledby": headingId, display: "flex", flexDirection: "column", gap: "2px", m: 0, p: 0, children: [visible.map(renderRow), hiddenCount > 0 && (_jsx(Box, { as: "li", listStyleType: "none", children: toggle ? (_jsx(ToolTip, { label: disclosureLabel, placement: "right", children: disclosureButton })) : (disclosureButton) }))] }), _jsx(Box, { h: "1px", bg: theme.colors.sidebar.divider, mx: 2, mt: overlay ? "6px" : "8px", mb: overlay ? "3px" : "5px" })] }));
|
|
91
|
+
};
|
|
92
|
+
export default PinnedSection;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
|
|
2
|
+
import { AA_NON_TEXT, liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
|
|
3
|
+
const cache = new Map();
|
|
4
|
+
/** The ring is derived for the canvas; the rail is near-black in both modes, so it is re-settled against the rail. */
|
|
5
|
+
export const sidebarFocusRing = (colors, surface = colors.sidebar.background[500]) => {
|
|
6
|
+
const key = `${colors.focusRing.ring}|${surface}`;
|
|
7
|
+
let tokens = cache.get(key);
|
|
8
|
+
if (!tokens) {
|
|
9
|
+
tokens = { ring: liftToContrast(colors.focusRing.ring, surface, AA_NON_TEXT), halo: surface };
|
|
10
|
+
cache.set(key, tokens);
|
|
11
|
+
}
|
|
12
|
+
return { focusRing: tokens };
|
|
13
|
+
};
|
|
14
|
+
/** `focusVisibleStyles` for a control sitting on the rail (or on `surface`, e.g. the flyout). */
|
|
15
|
+
export const sidebarFocusStyles = (colors, surface) => focusVisibleStyles(sidebarFocusRing(colors, surface));
|
|
@@ -27,12 +27,12 @@ const StageProgress = ({ stages, variant = "chevron", value, onChange, size = "m
|
|
|
27
27
|
return "upcoming";
|
|
28
28
|
};
|
|
29
29
|
const total = stages.length;
|
|
30
|
-
return (_jsx(Box, { display: "flex", alignItems: variant === "stepper" ? "flex-start" : "stretch", gap: gapFor[variant], overflowX: "auto", p: "0.1rem", sx: scrollbarStyles(theme.colors, "overlay"), children: stages.map((stage, index) => {
|
|
30
|
+
return (_jsx(Box, { as: variant === "stepper" ? "ol" : undefined, m: variant === "stepper" ? 0 : undefined, listStyleType: variant === "stepper" ? "none" : undefined, display: "flex", alignItems: variant === "stepper" ? "flex-start" : "stretch", gap: gapFor[variant], overflowX: "auto", p: "0.1rem", sx: scrollbarStyles(theme.colors, "overlay"), children: stages.map((stage, index) => {
|
|
31
31
|
var _a;
|
|
32
32
|
const state = stateFor(index);
|
|
33
33
|
const stageDisabled = (_a = stage.isDisabled) !== null && _a !== void 0 ? _a : stage.disabled;
|
|
34
34
|
if (variant === "stepper") {
|
|
35
|
-
return (_jsx(StepperStage, { label: stage.label, number: index + 1, state: state, size: size, colorScheme: colorScheme, isReadOnly: isReadOnly, isDisabled: stageDisabled, showCheck: showCheck, isFirst: index === 0, isLast: index === total - 1, leftDone: activeIndex > -1 && index <= activeIndex, rightDone: activeIndex > -1 && index < activeIndex, scrollIntoViewWhenActive: scrollActiveIntoView, onClick: () => onChange === null || onChange === void 0 ? void 0 : onChange(stage.id, stage, index) }, stage.id));
|
|
35
|
+
return (_jsx(StepperStage, { label: stage.label, number: index + 1, state: state, size: size, colorScheme: colorScheme, isReadOnly: isReadOnly, isDisabled: stageDisabled, showCheck: showCheck, isFirst: index === 0, isLast: index === total - 1, leftDone: activeIndex > -1 && index <= activeIndex, rightDone: activeIndex > -1 && index < activeIndex, scrollIntoViewWhenActive: scrollActiveIntoView, asListItem: true, onClick: () => onChange === null || onChange === void 0 ? void 0 : onChange(stage.id, stage, index) }, stage.id));
|
|
36
36
|
}
|
|
37
37
|
return (_jsx(StageItem, { label: stage.label, index: index, total: total, state: state, variant: variant, size: size, colorScheme: colorScheme, isReadOnly: isReadOnly, isDisabled: stageDisabled, showCheck: showCheck, minWidth: minStageWidth, scrollIntoViewWhenActive: scrollActiveIntoView, tooltipThreshold: tooltipThreshold, onClick: () => onChange === null || onChange === void 0 ? void 0 : onChange(stage.id, stage, index) }, stage.id));
|
|
38
38
|
}) }));
|
|
@@ -11,7 +11,7 @@ const sizeMap = {
|
|
|
11
11
|
md: { circle: "30px", font: "0.8rem", label: "0.8rem", icon: 14, minW: "104px" },
|
|
12
12
|
lg: { circle: "38px", font: "0.95rem", label: "0.9rem", icon: 16, minW: "124px" },
|
|
13
13
|
};
|
|
14
|
-
const StepperStage = ({ label, number, state, size = "md", colorScheme = "primary", onClick, isReadOnly = false, isDisabled, disabled: legacyDisabled = false, showCheck = true, isFirst, isLast, leftDone, rightDone, scrollIntoViewWhenActive = true, }) => {
|
|
14
|
+
const StepperStage = ({ label, number, state, size = "md", colorScheme = "primary", onClick, isReadOnly = false, isDisabled, disabled: legacyDisabled = false, showCheck = true, isFirst, isLast, leftDone, rightDone, scrollIntoViewWhenActive = true, asListItem = false, }) => {
|
|
15
15
|
const disabled = isDisabled !== null && isDisabled !== void 0 ? isDisabled : legacyDisabled;
|
|
16
16
|
const theme = useCustomTheme();
|
|
17
17
|
const palette = resolveStagePalette(theme, colorScheme);
|
|
@@ -33,10 +33,13 @@ const StepperStage = ({ label, number, state, size = "md", colorScheme = "primar
|
|
|
33
33
|
const circleColor = filled ? palette.onStrong : palette.mutedText;
|
|
34
34
|
const circleBorder = filled ? palette.strong : palette.muted;
|
|
35
35
|
const line = (done, hidden) => (_jsx(Box, { flex: "1", height: "2px", bg: done ? palette.strong : palette.muted, visibility: hidden ? "hidden" : "visible", transition: "background 0.18s ease" }));
|
|
36
|
-
|
|
36
|
+
const node = (_jsxs(Flex, { ref: ref, direction: "column", align: "center", flex: "1", minWidth: s.minW, flexShrink: 0, as: (interactive ? "button" : "div"), type: interactive ? "button" : undefined, "data-group": "", "aria-disabled": disabled ? true : undefined, border: "none", bg: "transparent", cursor: interactive ? "pointer" : "default", opacity: disabled ? 0.55 : 1, onClick: interactive ? onClick : undefined, borderRadius: "lg", sx: interactive ? focusVisibleStyles(theme.colors) : undefined, children: [_jsxs(Flex, { align: "center", width: "100%", children: [line(leftDone, isFirst), _jsx(Flex, { align: "center", justify: "center", boxSize: s.circle, borderRadius: "full", flexShrink: 0, bg: circleBg, color: circleColor, border: "2px solid", borderColor: circleBorder, fontSize: s.font, fontWeight: 600, transition: transitionFor(["background-color", "border-color", "color", "box-shadow"]), "aria-current": state === "active" ? "step" : undefined, sx: state === "active"
|
|
37
37
|
? { boxShadow: `0 0 0 4px ${palette.soft}` }
|
|
38
38
|
: undefined, _groupHover: interactive
|
|
39
39
|
? { borderColor: palette.strong, bg: palette.strong, color: palette.onStrong }
|
|
40
40
|
: undefined, children: showCheck && state === "completed" ? _jsx(Check, { size: s.icon }) : number }), line(rightDone, isLast)] }), _jsx(Text, { mt: 2, px: 1, borderRadius: "sm", fontSize: s.label, fontWeight: state === "active" ? 600 : 500, color: state === "upcoming" ? palette.mutedText : palette.softText, textAlign: "center", noOfLines: 2, transition: "background 0.18s ease, color 0.18s ease", _groupHover: interactive ? { bg: palette.strong, color: palette.onStrong } : undefined, children: label })] }));
|
|
41
|
+
if (!asListItem)
|
|
42
|
+
return node;
|
|
43
|
+
return (_jsx(Box, { as: "li", display: "flex", flex: "1", minWidth: s.minW, flexShrink: 0, listStyleType: "none", children: node }));
|
|
41
44
|
};
|
|
42
45
|
export default StepperStage;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { Alert, AlertDescription, AlertIcon, AlertTitle, Box, Flex, Heading, ListItem, Text, UnorderedList, } from "@chakra-ui/react";
|
|
4
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
5
|
+
import Modal, { ModalBody, ModalFooter } from "../Modal/Modal.js";
|
|
6
|
+
import Button from "../Button/Button.js";
|
|
7
|
+
import StageProgress from "../StageProgress/StageProgress.js";
|
|
8
|
+
import DescriptionList from "../DescriptionList/DescriptionList.js";
|
|
9
|
+
import { useWizardFlow } from "./useWizardFlow.js";
|
|
10
|
+
export { wizardTransition, firstInvalidStep, INITIAL_WIZARD_STATE } from "./wizardSteps.js";
|
|
11
|
+
const DEFAULT_LABELS = {
|
|
12
|
+
back: "Back",
|
|
13
|
+
next: "Continue",
|
|
14
|
+
finish: "Finish",
|
|
15
|
+
cancel: "Cancel",
|
|
16
|
+
stepOf: (current, total) => `Step ${current} of ${total}`,
|
|
17
|
+
fixErrors: "Fix the following to continue",
|
|
18
|
+
review: "Review",
|
|
19
|
+
};
|
|
20
|
+
const WizardBody = ({ flow, steps, labels, testId }) => {
|
|
21
|
+
const { colors } = useCustomTheme();
|
|
22
|
+
const headingRef = useRef(null);
|
|
23
|
+
const previousStep = useRef(flow.step.id);
|
|
24
|
+
const reviewed = steps.filter((candidate) => candidate.summary !== undefined);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
var _a;
|
|
27
|
+
if (previousStep.current === flow.step.id)
|
|
28
|
+
return;
|
|
29
|
+
previousStep.current = flow.step.id;
|
|
30
|
+
(_a = headingRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
31
|
+
}, [flow.step.id]);
|
|
32
|
+
return (_jsxs(Flex, { direction: "column", gap: 5, "data-testid": testId, "data-step": flow.step.id, children: [_jsx(StageProgress, { variant: "stepper", size: "sm", showCheck: true, stages: steps.map((candidate, index) => ({
|
|
33
|
+
id: candidate.id,
|
|
34
|
+
label: candidate.label,
|
|
35
|
+
isDisabled: index > flow.furthest,
|
|
36
|
+
})), value: flow.step.id, onChange: (_id, _stage, index) => flow.goto(index) }), _jsxs(Box, { children: [_jsx(Text, { fontSize: "xs", color: colors.textMuted, children: labels.stepOf(flow.index + 1, steps.length) }), _jsx(Heading, { ref: headingRef, tabIndex: -1, as: "h3", fontSize: "md", fontWeight: 600, color: colors.header[900], _focus: { outline: "none" }, children: flow.step.label })] }), flow.errors.length ? (_jsxs(Alert, { status: "error", variant: "subtle", borderRadius: "md", alignItems: "flex-start", role: "alert", "data-testid": "wizard-errors", children: [_jsx(AlertIcon, {}), _jsxs(Box, { children: [_jsx(AlertTitle, { fontSize: "sm", children: labels.fixErrors }), _jsx(AlertDescription, { fontSize: "sm", children: _jsx(UnorderedList, { mt: 1, children: flow.errors.map((message) => (_jsx(ListItem, { children: message }, message))) }) })] })] })) : null, _jsx(Box, { children: flow.step.content }), flow.isLast && reviewed.length ? (_jsxs(Box, { "data-testid": "wizard-review", children: [_jsx(Heading, { as: "h4", fontSize: "sm", fontWeight: 600, mb: 2, color: colors.header[900], children: labels.review }), _jsx(DescriptionList, { layout: "inline", columns: { base: 1 }, items: reviewed.map((candidate) => ({
|
|
37
|
+
label: candidate.label,
|
|
38
|
+
value: candidate.summary,
|
|
39
|
+
})) })] })) : null] }));
|
|
40
|
+
};
|
|
41
|
+
const WizardFooter = ({ flow, labels, isFinishing, onCancel }) => (_jsxs(Flex, { w: "100%", justify: "space-between", gap: 2, wrap: "wrap", children: [onCancel ? (_jsx(Button, { variant: "ghost", colorScheme: "gray", label: labels.cancel, onClick: onCancel, isDisabled: isFinishing, "data-testid": "wizard-cancel" })) : (_jsx("span", {})), _jsxs(Flex, { gap: 2, children: [!flow.isFirst ? (_jsx(Button, { variant: "outline", colorScheme: "gray", label: labels.back, onClick: flow.back, isDisabled: isFinishing, "data-testid": "wizard-back" })) : null, flow.isLast ? (_jsx(Button, { variant: "solid", colorScheme: "primary", label: labels.finish, onClick: flow.finish, isLoading: isFinishing, loadingText: labels.finish, "data-testid": "wizard-finish" })) : (_jsx(Button, { variant: "solid", colorScheme: "primary", label: labels.next, onClick: flow.next, "data-testid": "wizard-next" }))] })] }));
|
|
42
|
+
/** Modal wizard that owns step state: validated Continue, locked future steps, error summary, review on the last step. */
|
|
43
|
+
export const WizardDialog = ({ isOpen, onClose, title, subtitle, steps, onFinish, isFinishing = false, stepId, onStepChange, size = "3xl", labels, "data-testid": testId, }) => {
|
|
44
|
+
const flow = useWizardFlow({ steps, stepId, onStepChange, onFinish, resetWhen: !isOpen });
|
|
45
|
+
const resolved = { ...DEFAULT_LABELS, ...labels };
|
|
46
|
+
const guardedClose = () => {
|
|
47
|
+
if (!isFinishing)
|
|
48
|
+
onClose();
|
|
49
|
+
};
|
|
50
|
+
return (_jsxs(Modal, { isOpen: isOpen, onClose: guardedClose, size: size, isCentered: true, scrollBehavior: "inside", title: title, subtitle: subtitle, closeOnOverlayClick: false, closeOnEsc: !isFinishing, hideCloseButton: isFinishing, children: [_jsx(ModalBody, { children: _jsx(WizardBody, { flow: flow, steps: steps, labels: resolved, testId: testId !== null && testId !== void 0 ? testId : "wizard-dialog" }) }), _jsx(ModalFooter, { children: _jsx(WizardFooter, { flow: flow, labels: resolved, isFinishing: isFinishing, onCancel: guardedClose }) })] }));
|
|
51
|
+
};
|
|
52
|
+
/** The same wizard inline, for a full-page flow rather than a modal. */
|
|
53
|
+
export const Wizard = ({ steps, onFinish, isFinishing = false, stepId, onStepChange, labels, onCancel, "data-testid": testId, }) => {
|
|
54
|
+
const flow = useWizardFlow({ steps, stepId, onStepChange, onFinish });
|
|
55
|
+
const resolved = { ...DEFAULT_LABELS, ...labels };
|
|
56
|
+
return (_jsxs(Flex, { direction: "column", gap: 6, children: [_jsx(WizardBody, { flow: flow, steps: steps, labels: resolved, testId: testId !== null && testId !== void 0 ? testId : "wizard" }), _jsx(WizardFooter, { flow: flow, labels: resolved, isFinishing: isFinishing, onCancel: onCancel })] }));
|
|
57
|
+
};
|
|
58
|
+
export default WizardDialog;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { INITIAL_WIZARD_STATE, firstInvalidStep, wizardTransition, } from "./wizardSteps.js";
|
|
3
|
+
export function useWizardFlow({ steps, stepId, onStepChange, onFinish, resetWhen }) {
|
|
4
|
+
const [state, setState] = useState(INITIAL_WIZARD_STATE);
|
|
5
|
+
const validators = steps.map((step) => step.validate);
|
|
6
|
+
const controlledIndex = stepId === undefined ? -1 : steps.findIndex((s) => s.id === stepId);
|
|
7
|
+
const isControlled = stepId !== undefined;
|
|
8
|
+
const index = Math.min(isControlled ? Math.max(controlledIndex, 0) : state.index, Math.max(steps.length - 1, 0));
|
|
9
|
+
const furthest = Math.max(state.furthest, index);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (resetWhen)
|
|
12
|
+
setState(INITIAL_WIZARD_STATE);
|
|
13
|
+
}, [resetWhen]);
|
|
14
|
+
const dispatch = useCallback((action) => {
|
|
15
|
+
const base = { ...state, index, furthest };
|
|
16
|
+
const next = wizardTransition(base, action, validators);
|
|
17
|
+
setState(next);
|
|
18
|
+
if (next.index !== index)
|
|
19
|
+
onStepChange === null || onStepChange === void 0 ? void 0 : onStepChange(steps[next.index].id);
|
|
20
|
+
},
|
|
21
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
22
|
+
[state, index, furthest, steps, onStepChange]);
|
|
23
|
+
const finish = useCallback(() => {
|
|
24
|
+
const invalid = firstInvalidStep(validators);
|
|
25
|
+
if (invalid) {
|
|
26
|
+
dispatch({ type: "fail", ...invalid });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
onFinish();
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31
|
+
}, [dispatch, steps, onFinish]);
|
|
32
|
+
return {
|
|
33
|
+
index,
|
|
34
|
+
furthest,
|
|
35
|
+
errors: state.errors,
|
|
36
|
+
step: steps[index],
|
|
37
|
+
isFirst: index === 0,
|
|
38
|
+
isLast: index === steps.length - 1,
|
|
39
|
+
next: () => dispatch({ type: "next" }),
|
|
40
|
+
back: () => dispatch({ type: "back" }),
|
|
41
|
+
goto: (target) => dispatch({ type: "goto", index: target }),
|
|
42
|
+
finish,
|
|
43
|
+
};
|
|
44
|
+
}
|