@wallarm-org/design-system 0.43.0 → 0.44.1
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/components/AppShell/story-content/_storyConfigRenderer.d.ts +7 -0
- package/dist/components/AppShell/story-content/_storyConfigRenderer.js +46 -0
- package/dist/components/AppShell/story-content/_storyHomeContent.d.ts +2 -0
- package/dist/components/AppShell/story-content/_storyHomeContent.js +15 -0
- package/dist/components/AppShell/story-content/_storyNavConfigs.d.ts +6 -0
- package/dist/components/AppShell/story-content/_storyNavConfigs.js +693 -0
- package/dist/components/AppShell/story-content/index.d.ts +3 -0
- package/dist/components/AppShell/story-content/index.js +4 -0
- package/dist/components/Breadcrumbs/BreadcrumbsItem.js +2 -1
- package/dist/components/Breadcrumbs/BreadcrumbsScopeSwitcher.d.ts +18 -0
- package/dist/components/Breadcrumbs/BreadcrumbsScopeSwitcher.js +60 -0
- package/dist/components/Breadcrumbs/index.d.ts +1 -0
- package/dist/components/Breadcrumbs/index.js +2 -1
- package/dist/components/NavPanel/NavPanel.d.ts +8 -0
- package/dist/components/NavPanel/NavPanel.js +93 -0
- package/dist/components/NavPanel/NavPanelBack.d.ts +6 -0
- package/dist/components/NavPanel/NavPanelBack.js +31 -0
- package/dist/components/NavPanel/NavPanelContext.d.ts +12 -0
- package/dist/components/NavPanel/NavPanelContext.js +9 -0
- package/dist/components/NavPanel/NavPanelDivider.d.ts +3 -0
- package/dist/components/NavPanel/NavPanelDivider.js +10 -0
- package/dist/components/NavPanel/NavPanelGroup.d.ts +18 -0
- package/dist/components/NavPanel/NavPanelGroup.js +49 -0
- package/dist/components/NavPanel/NavPanelGroupContent.d.ts +6 -0
- package/dist/components/NavPanel/NavPanelGroupContent.js +36 -0
- package/dist/components/NavPanel/NavPanelGroupItem.d.ts +10 -0
- package/dist/components/NavPanel/NavPanelGroupItem.js +40 -0
- package/dist/components/NavPanel/NavPanelGroupLabel.d.ts +8 -0
- package/dist/components/NavPanel/NavPanelGroupLabel.js +48 -0
- package/dist/components/NavPanel/NavPanelHeader.d.ts +6 -0
- package/dist/components/NavPanel/NavPanelHeader.js +21 -0
- package/dist/components/NavPanel/NavPanelItem.d.ts +10 -0
- package/dist/components/NavPanel/NavPanelItem.js +35 -0
- package/dist/components/NavPanel/NavPanelResizeHandle.d.ts +2 -0
- package/dist/components/NavPanel/NavPanelResizeHandle.js +81 -0
- package/dist/components/NavPanel/NavPanelSectionHeader.d.ts +6 -0
- package/dist/components/NavPanel/NavPanelSectionHeader.js +22 -0
- package/dist/components/NavPanel/classes.d.ts +6 -0
- package/dist/components/NavPanel/classes.js +24 -0
- package/dist/components/NavPanel/index.d.ts +10 -0
- package/dist/components/NavPanel/index.js +11 -0
- package/dist/components/NavRail/NavRail.js +33 -2
- package/dist/components/NavRail/NavRailItem.js +10 -3
- package/dist/components/NavRail/classes.js +2 -2
- package/dist/components/NavRail/useShortcut.d.ts +2 -0
- package/dist/components/NavRail/useShortcut.js +44 -0
- package/dist/components/ProductNav/ProductNav.d.ts +13 -0
- package/dist/components/ProductNav/ProductNav.js +100 -0
- package/dist/components/ProductNav/ProductNavBreadcrumbs.d.ts +2 -0
- package/dist/components/ProductNav/ProductNavBreadcrumbs.js +38 -0
- package/dist/components/ProductNav/ProductNavContext.d.ts +22 -0
- package/dist/components/ProductNav/ProductNavContext.js +9 -0
- package/dist/components/ProductNav/ProductNavPanel.d.ts +6 -0
- package/dist/components/ProductNav/ProductNavPanel.js +82 -0
- package/dist/components/ProductNav/index.d.ts +10 -0
- package/dist/components/ProductNav/index.js +9 -0
- package/dist/components/ProductNav/matchNav.d.ts +16 -0
- package/dist/components/ProductNav/matchNav.js +108 -0
- package/dist/components/ProductNav/navUtils.d.ts +5 -0
- package/dist/components/ProductNav/navUtils.js +26 -0
- package/dist/components/ProductNav/types.d.ts +69 -0
- package/dist/components/ProductNav/types.js +0 -0
- package/dist/components/ProductNav/useLocationPathname.d.ts +4 -0
- package/dist/components/ProductNav/useLocationPathname.js +24 -0
- package/dist/components/ProductNav/useProductNav.d.ts +16 -0
- package/dist/components/ProductNav/useProductNav.js +19 -0
- package/dist/components/RemoteShell/RemoteShell.d.ts +7 -0
- package/dist/components/RemoteShell/RemoteShell.js +16 -0
- package/dist/components/RemoteShell/RemoteShellBreadcrumb.d.ts +6 -0
- package/dist/components/RemoteShell/RemoteShellBreadcrumb.js +16 -0
- package/dist/components/RemoteShell/RemoteShellContent.d.ts +6 -0
- package/dist/components/RemoteShell/RemoteShellContent.js +16 -0
- package/dist/components/RemoteShell/RemoteShellPanel.d.ts +6 -0
- package/dist/components/RemoteShell/RemoteShellPanel.js +16 -0
- package/dist/components/RemoteShell/index.d.ts +4 -0
- package/dist/components/RemoteShell/index.js +5 -0
- package/dist/components/SimpleCharts/LineChart/LineChart.js +5 -5
- package/dist/components/SimpleCharts/LineChart/LineChartZoomBrush.js +66 -12
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartActiveKey.js +4 -3
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartZoomState.d.ts +9 -2
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartZoomState.js +88 -34
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/useArrowNav.d.ts +7 -0
- package/dist/hooks/useArrowNav.js +98 -0
- package/dist/icons/ChevronUpDown.d.ts +3 -0
- package/dist/icons/ChevronUpDown.js +12 -0
- package/dist/icons/MapPin.d.ts +3 -0
- package/dist/icons/MapPin.js +12 -0
- package/dist/icons/index.d.ts +2 -0
- package/dist/icons/index.js +3 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -1
- package/dist/metadata/components.json +4829 -682
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { throttle } from "lodash-es";
|
|
5
|
+
import { cn } from "../../utils/cn.js";
|
|
6
|
+
import { useNavPanelInternalContext } from "./NavPanelContext.js";
|
|
7
|
+
const resizeHandleVariants = cva(cn('absolute right-0 top-0 bottom-0 z-1 w-12', 'translate-x-1/2', 'outline-none cursor-ew-resize', 'flex items-center justify-center', 'before:content-[""]', 'before:block', 'before:w-3 before:h-full', 'before:rounded-full', 'before:bg-bg-fill-brand', 'before:transition-opacity before:duration-150'), {
|
|
8
|
+
variants: {
|
|
9
|
+
barIsVisible: {
|
|
10
|
+
true: 'before:opacity-100',
|
|
11
|
+
false: 'before:opacity-0'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const NavPanelResizeHandle = ()=>{
|
|
16
|
+
const { width, setWidth, setIsResizing, minWidth, maxWidth } = useNavPanelInternalContext();
|
|
17
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
18
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
19
|
+
const dragStateRef = useRef({
|
|
20
|
+
startX: 0,
|
|
21
|
+
startWidth: 0
|
|
22
|
+
});
|
|
23
|
+
const handleMouseDown = (e)=>{
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
const navPanel = e.currentTarget.closest('[data-slot="nav-panel"]');
|
|
26
|
+
if (!navPanel) return;
|
|
27
|
+
dragStateRef.current = {
|
|
28
|
+
startX: e.clientX,
|
|
29
|
+
startWidth: navPanel.offsetWidth
|
|
30
|
+
};
|
|
31
|
+
setIsDragging(true);
|
|
32
|
+
setIsResizing(true);
|
|
33
|
+
};
|
|
34
|
+
const handleMouseMove = useMemo(()=>throttle((e)=>{
|
|
35
|
+
const { startX, startWidth } = dragStateRef.current;
|
|
36
|
+
const delta = e.clientX - startX;
|
|
37
|
+
const newWidth = Math.min(Math.max(startWidth + delta, minWidth), maxWidth);
|
|
38
|
+
setWidth(newWidth);
|
|
39
|
+
}, 16), [
|
|
40
|
+
minWidth,
|
|
41
|
+
maxWidth,
|
|
42
|
+
setWidth
|
|
43
|
+
]);
|
|
44
|
+
useEffect(()=>{
|
|
45
|
+
if (!isDragging) return;
|
|
46
|
+
const handleMouseUp = ()=>{
|
|
47
|
+
setIsDragging(false);
|
|
48
|
+
setIsResizing(false);
|
|
49
|
+
handleMouseMove.cancel();
|
|
50
|
+
};
|
|
51
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
52
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
53
|
+
return ()=>{
|
|
54
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
55
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
56
|
+
handleMouseMove.cancel();
|
|
57
|
+
};
|
|
58
|
+
}, [
|
|
59
|
+
isDragging,
|
|
60
|
+
handleMouseMove,
|
|
61
|
+
setIsResizing
|
|
62
|
+
]);
|
|
63
|
+
const barIsVisible = isHovered || isDragging;
|
|
64
|
+
return /*#__PURE__*/ jsx("div", {
|
|
65
|
+
role: "separator",
|
|
66
|
+
tabIndex: 0,
|
|
67
|
+
"aria-orientation": "vertical",
|
|
68
|
+
"aria-valuenow": width,
|
|
69
|
+
"aria-valuemin": minWidth,
|
|
70
|
+
"aria-valuemax": maxWidth,
|
|
71
|
+
"data-slot": "nav-panel-resize-handle",
|
|
72
|
+
className: cn(resizeHandleVariants({
|
|
73
|
+
barIsVisible
|
|
74
|
+
})),
|
|
75
|
+
onMouseEnter: ()=>setIsHovered(true),
|
|
76
|
+
onMouseLeave: ()=>setIsHovered(false),
|
|
77
|
+
onMouseDown: handleMouseDown
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
NavPanelResizeHandle.displayName = 'NavPanelResizeHandle';
|
|
81
|
+
export { NavPanelResizeHandle };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
|
|
2
|
+
export interface NavPanelSectionHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
ref?: Ref<HTMLDivElement>;
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export declare const NavPanelSectionHeader: FC<NavPanelSectionHeaderProps>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../utils/cn.js";
|
|
3
|
+
import { useTestId } from "../../utils/testId.js";
|
|
4
|
+
import { Text } from "../Text/index.js";
|
|
5
|
+
const NavPanelSectionHeader = ({ ref, className, children, ...props })=>{
|
|
6
|
+
const testId = useTestId('section-header');
|
|
7
|
+
return /*#__PURE__*/ jsx("div", {
|
|
8
|
+
...props,
|
|
9
|
+
ref: ref,
|
|
10
|
+
"data-slot": "nav-panel-section-header",
|
|
11
|
+
"data-testid": testId,
|
|
12
|
+
className: cn('flex shrink-0 items-center px-8 pt-6', className),
|
|
13
|
+
children: /*#__PURE__*/ jsx(Text, {
|
|
14
|
+
size: "xs",
|
|
15
|
+
weight: "medium",
|
|
16
|
+
color: "secondary",
|
|
17
|
+
children: children
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
NavPanelSectionHeader.displayName = 'NavPanelSectionHeader';
|
|
22
|
+
export { NavPanelSectionHeader };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const navPanelItemVariants: (props?: ({
|
|
2
|
+
active?: boolean | null | undefined;
|
|
3
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
4
|
+
export declare const navPanelGroupItemVariants: (props?: ({
|
|
5
|
+
active?: boolean | null | undefined;
|
|
6
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
2
|
+
const navPanelItemVariants = cva('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-6 p-8 text-sm transition-colors outline-none', {
|
|
3
|
+
variants: {
|
|
4
|
+
active: {
|
|
5
|
+
true: 'overlay-states-primary-active font-semibold text-text-primary',
|
|
6
|
+
false: 'text-text-secondary hover:overlay-states-primary-hover focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed'
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
defaultVariants: {
|
|
10
|
+
active: false
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
const navPanelGroupItemVariants = cva('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-6 py-8 pr-8 text-sm transition-colors outline-none', {
|
|
14
|
+
variants: {
|
|
15
|
+
active: {
|
|
16
|
+
true: 'overlay-states-primary-active font-semibold text-text-primary',
|
|
17
|
+
false: 'text-text-secondary hover:overlay-states-primary-hover focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
active: false
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
export { navPanelGroupItemVariants, navPanelItemVariants };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { NavPanel, type NavPanelProps } from './NavPanel';
|
|
2
|
+
export { NavPanelBack, type NavPanelBackProps } from './NavPanelBack';
|
|
3
|
+
export { NavPanelDivider, type NavPanelDividerProps } from './NavPanelDivider';
|
|
4
|
+
export { NavPanelGroup, type NavPanelGroupProps } from './NavPanelGroup';
|
|
5
|
+
export { NavPanelGroupContent, type NavPanelGroupContentProps } from './NavPanelGroupContent';
|
|
6
|
+
export { NavPanelGroupItem, type NavPanelGroupItemProps } from './NavPanelGroupItem';
|
|
7
|
+
export { NavPanelGroupLabel, type NavPanelGroupLabelProps } from './NavPanelGroupLabel';
|
|
8
|
+
export { NavPanelHeader, type NavPanelHeaderProps } from './NavPanelHeader';
|
|
9
|
+
export { NavPanelItem, type NavPanelItemProps } from './NavPanelItem';
|
|
10
|
+
export { NavPanelSectionHeader, type NavPanelSectionHeaderProps } from './NavPanelSectionHeader';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NavPanel } from "./NavPanel.js";
|
|
2
|
+
import { NavPanelBack } from "./NavPanelBack.js";
|
|
3
|
+
import { NavPanelDivider } from "./NavPanelDivider.js";
|
|
4
|
+
import { NavPanelGroup } from "./NavPanelGroup.js";
|
|
5
|
+
import { NavPanelGroupContent } from "./NavPanelGroupContent.js";
|
|
6
|
+
import { NavPanelGroupItem } from "./NavPanelGroupItem.js";
|
|
7
|
+
import { NavPanelGroupLabel } from "./NavPanelGroupLabel.js";
|
|
8
|
+
import { NavPanelHeader } from "./NavPanelHeader.js";
|
|
9
|
+
import { NavPanelItem } from "./NavPanelItem.js";
|
|
10
|
+
import { NavPanelSectionHeader } from "./NavPanelSectionHeader.js";
|
|
11
|
+
export { NavPanel, NavPanelBack, NavPanelDivider, NavPanelGroup, NavPanelGroupContent, NavPanelGroupItem, NavPanelGroupLabel, NavPanelHeader, NavPanelItem, NavPanelSectionHeader };
|
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef } from "react";
|
|
3
|
+
import { composeRefs } from "@radix-ui/react-compose-refs";
|
|
4
|
+
import { useArrowNav } from "../../hooks/useArrowNav.js";
|
|
2
5
|
import { cn } from "../../utils/cn.js";
|
|
3
6
|
import { TestIdProvider } from "../../utils/testId.js";
|
|
4
7
|
import { navRailVariants } from "./classes.js";
|
|
5
8
|
import { NavRailContextProvider } from "./NavRailContext.js";
|
|
6
|
-
const NavRail = ({ ref, collapsed = false, className, children, 'data-testid': testId, ...props })
|
|
9
|
+
const NavRail = ({ ref, collapsed = false, className, children, 'data-testid': testId, ...props })=>{
|
|
10
|
+
const internalRef = useRef(null);
|
|
11
|
+
const focusPanel = useCallback(()=>{
|
|
12
|
+
const panel = document.querySelector('[data-slot="nav-panel"]');
|
|
13
|
+
const target = panel?.querySelector('[tabindex="0"]');
|
|
14
|
+
target?.focus();
|
|
15
|
+
}, []);
|
|
16
|
+
const focusPanelAfterNav = useCallback(()=>{
|
|
17
|
+
const observer = new MutationObserver(()=>{
|
|
18
|
+
const panel = document.querySelector('[data-slot="nav-panel"]');
|
|
19
|
+
const target = panel?.querySelector('[data-slot="nav-panel-item"], [data-slot="nav-panel-back"]');
|
|
20
|
+
if (target) {
|
|
21
|
+
target.setAttribute('tabindex', '0');
|
|
22
|
+
target.focus();
|
|
23
|
+
observer.disconnect();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
observer.observe(document.body, {
|
|
27
|
+
childList: true,
|
|
28
|
+
subtree: true
|
|
29
|
+
});
|
|
30
|
+
setTimeout(()=>observer.disconnect(), 2000);
|
|
31
|
+
}, []);
|
|
32
|
+
useArrowNav(internalRef, '[data-slot="nav-rail-item"]', {
|
|
33
|
+
onArrowRight: focusPanel,
|
|
34
|
+
onEnter: focusPanelAfterNav
|
|
35
|
+
});
|
|
36
|
+
return /*#__PURE__*/ jsx(TestIdProvider, {
|
|
7
37
|
value: testId,
|
|
8
38
|
children: /*#__PURE__*/ jsx(NavRailContextProvider, {
|
|
9
39
|
value: {
|
|
@@ -11,7 +41,7 @@ const NavRail = ({ ref, collapsed = false, className, children, 'data-testid': t
|
|
|
11
41
|
},
|
|
12
42
|
children: /*#__PURE__*/ jsx("nav", {
|
|
13
43
|
...props,
|
|
14
|
-
ref: ref,
|
|
44
|
+
ref: composeRefs(internalRef, ref),
|
|
15
45
|
"aria-label": "Global navigation",
|
|
16
46
|
"data-slot": "nav-rail",
|
|
17
47
|
"data-testid": testId,
|
|
@@ -22,5 +52,6 @@ const NavRail = ({ ref, collapsed = false, className, children, 'data-testid': t
|
|
|
22
52
|
})
|
|
23
53
|
})
|
|
24
54
|
});
|
|
55
|
+
};
|
|
25
56
|
NavRail.displayName = 'NavRail';
|
|
26
57
|
export { NavRail };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment } from "react";
|
|
2
|
+
import { Fragment, useRef } from "react";
|
|
3
|
+
import { composeRefs } from "@radix-ui/react-compose-refs";
|
|
3
4
|
import { Slot } from "@radix-ui/react-slot";
|
|
4
5
|
import { cn } from "../../utils/cn.js";
|
|
5
6
|
import { useTestId } from "../../utils/testId.js";
|
|
@@ -9,13 +10,16 @@ import { TooltipContent } from "../Tooltip/TooltipContent.js";
|
|
|
9
10
|
import { TooltipTrigger } from "../Tooltip/TooltipTrigger.js";
|
|
10
11
|
import { navRailItemVariants } from "./classes.js";
|
|
11
12
|
import { useNavRailContext } from "./NavRailContext.js";
|
|
13
|
+
import { useShortcut } from "./useShortcut.js";
|
|
12
14
|
const NavRailItem = ({ ref, asChild = false, icon: Icon, label, shortcut, active = false, className, children, ...props })=>{
|
|
13
15
|
const { collapsed } = useNavRailContext();
|
|
14
16
|
const testId = useTestId('item');
|
|
15
17
|
const Comp = asChild ? Slot : 'a';
|
|
18
|
+
const internalRef = useRef(null);
|
|
19
|
+
useShortcut(shortcut, internalRef);
|
|
16
20
|
const element = /*#__PURE__*/ jsxs(Comp, {
|
|
17
21
|
...props,
|
|
18
|
-
ref: ref,
|
|
22
|
+
ref: composeRefs(internalRef, ref),
|
|
19
23
|
"aria-current": active ? 'page' : void 0,
|
|
20
24
|
"data-slot": "nav-rail-item",
|
|
21
25
|
"data-testid": testId,
|
|
@@ -43,7 +47,10 @@ const NavRailItem = ({ ref, asChild = false, icon: Icon, label, shortcut, active
|
|
|
43
47
|
children: [
|
|
44
48
|
/*#__PURE__*/ jsx(TooltipTrigger, {
|
|
45
49
|
asChild: true,
|
|
46
|
-
children:
|
|
50
|
+
children: props['aria-haspopup'] ? /*#__PURE__*/ jsx("span", {
|
|
51
|
+
className: "flex flex-1",
|
|
52
|
+
children: element
|
|
53
|
+
}) : element
|
|
47
54
|
}),
|
|
48
55
|
/*#__PURE__*/ jsxs(TooltipContent, {
|
|
49
56
|
children: [
|
|
@@ -10,11 +10,11 @@ const navRailVariants = cva('flex h-full shrink-0 flex-col overflow-hidden px-8
|
|
|
10
10
|
collapsed: false
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
|
-
const navRailItemVariants = cva('overlay flex h-32 cursor-pointer items-center rounded-6 p-8 text-sm transition-colors', {
|
|
13
|
+
const navRailItemVariants = cva('overlay flex h-32 w-full cursor-pointer items-center rounded-6 p-8 text-sm transition-colors outline-none', {
|
|
14
14
|
variants: {
|
|
15
15
|
active: {
|
|
16
16
|
true: 'overlay-states-primary-active text-text-primary',
|
|
17
|
-
false: 'text-text-secondary hover:overlay-states-primary-hover active:overlay-states-primary-pressed'
|
|
17
|
+
false: 'text-text-secondary hover:overlay-states-primary-hover focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed'
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
defaultVariants: {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
const SEQUENCE_TIMEOUT = 1000;
|
|
3
|
+
function isEditableTarget(element) {
|
|
4
|
+
if (!element) return false;
|
|
5
|
+
const tag = element.tagName;
|
|
6
|
+
if ('INPUT' === tag || 'TEXTAREA' === tag || 'SELECT' === tag) return true;
|
|
7
|
+
return element.isContentEditable;
|
|
8
|
+
}
|
|
9
|
+
function useShortcut(shortcut, ref) {
|
|
10
|
+
const indexRef = useRef(0);
|
|
11
|
+
const timerRef = useRef(void 0);
|
|
12
|
+
useEffect(()=>{
|
|
13
|
+
if (!shortcut || 0 === shortcut.length) return;
|
|
14
|
+
const handleKeyDown = (event)=>{
|
|
15
|
+
if (isEditableTarget(document.activeElement)) return;
|
|
16
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
17
|
+
const expected = shortcut[indexRef.current];
|
|
18
|
+
if (!expected) return;
|
|
19
|
+
if (event.key.toLowerCase() === expected.toLowerCase()) {
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
indexRef.current++;
|
|
22
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
23
|
+
if (indexRef.current === shortcut.length) {
|
|
24
|
+
indexRef.current = 0;
|
|
25
|
+
ref.current?.click();
|
|
26
|
+
} else timerRef.current = setTimeout(()=>{
|
|
27
|
+
indexRef.current = 0;
|
|
28
|
+
}, SEQUENCE_TIMEOUT);
|
|
29
|
+
} else {
|
|
30
|
+
indexRef.current = 0;
|
|
31
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
35
|
+
return ()=>{
|
|
36
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
37
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
38
|
+
};
|
|
39
|
+
}, [
|
|
40
|
+
shortcut,
|
|
41
|
+
ref
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
export { useShortcut };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type FC, type ReactNode } from 'react';
|
|
2
|
+
import type { NavConfig } from './types';
|
|
3
|
+
export interface ProductNavProps {
|
|
4
|
+
config: NavConfig;
|
|
5
|
+
/** URL prefix stripped before matching and prepended when navigating.
|
|
6
|
+
* Example: `"/edge"` turns URL `/edge/overview` into effective pathname `/overview`. */
|
|
7
|
+
basePath?: string;
|
|
8
|
+
/** Custom navigation handler for router integration (React Router, Next.js, etc.).
|
|
9
|
+
* Default: uses history.pushState to update the URL directly. */
|
|
10
|
+
onNavigate?: (pathname: string) => void;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export declare const ProductNav: FC<ProductNavProps>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { matchNav } from "./matchNav.js";
|
|
4
|
+
import { findFirstLinkPath } from "./navUtils.js";
|
|
5
|
+
import { ProductNavContextProvider } from "./ProductNavContext.js";
|
|
6
|
+
import { pushPathname, useLocationPathname } from "./useLocationPathname.js";
|
|
7
|
+
const ProductNav = ({ config, basePath, onNavigate, children })=>{
|
|
8
|
+
const fullPathname = useLocationPathname();
|
|
9
|
+
const pathname = basePath && fullPathname.startsWith(basePath) ? fullPathname.slice(basePath.length) || '/' : fullPathname;
|
|
10
|
+
const setPathname = useCallback((next)=>{
|
|
11
|
+
const fullPath = basePath ? `${basePath}${next}` : next;
|
|
12
|
+
if (onNavigate) onNavigate(fullPath);
|
|
13
|
+
else pushPathname(fullPath);
|
|
14
|
+
}, [
|
|
15
|
+
basePath,
|
|
16
|
+
onNavigate
|
|
17
|
+
]);
|
|
18
|
+
const { navStack, breadcrumbSegments, activeItemId } = useMemo(()=>matchNav(pathname, config), [
|
|
19
|
+
pathname,
|
|
20
|
+
config
|
|
21
|
+
]);
|
|
22
|
+
const urlDrillLevel = navStack.length - 1;
|
|
23
|
+
const [visualDrillLevel, setVisualDrillLevel] = useState(null);
|
|
24
|
+
useEffect(()=>{
|
|
25
|
+
setVisualDrillLevel(null);
|
|
26
|
+
}, [
|
|
27
|
+
pathname
|
|
28
|
+
]);
|
|
29
|
+
const effectiveDrillLevel = visualDrillLevel ?? urlDrillLevel;
|
|
30
|
+
const effectiveActiveItemId = effectiveDrillLevel < urlDrillLevel ? navStack[effectiveDrillLevel]?.activeItemId ?? activeItemId : activeItemId;
|
|
31
|
+
const navigate = useCallback((path)=>{
|
|
32
|
+
setVisualDrillLevel(null);
|
|
33
|
+
const segments = pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
|
|
34
|
+
const prefixSegments = segments.slice(0, 2 * effectiveDrillLevel);
|
|
35
|
+
setPathname(`/${[
|
|
36
|
+
...prefixSegments,
|
|
37
|
+
path
|
|
38
|
+
].join('/')}`);
|
|
39
|
+
}, [
|
|
40
|
+
effectiveDrillLevel,
|
|
41
|
+
pathname,
|
|
42
|
+
setPathname
|
|
43
|
+
]);
|
|
44
|
+
const drillInto = useCallback((drill)=>{
|
|
45
|
+
setVisualDrillLevel(null);
|
|
46
|
+
const defaultEntity = drill.entities?.[0]?.id ?? 'default';
|
|
47
|
+
const firstChildPath = findFirstLinkPath(drill.children) ?? '';
|
|
48
|
+
setPathname(`/${drill.path}/${defaultEntity}/${firstChildPath}`);
|
|
49
|
+
}, [
|
|
50
|
+
setPathname
|
|
51
|
+
]);
|
|
52
|
+
const goBack = useCallback(()=>{
|
|
53
|
+
setVisualDrillLevel((prev)=>{
|
|
54
|
+
const current = prev ?? urlDrillLevel;
|
|
55
|
+
return Math.max(current - 1, 0);
|
|
56
|
+
});
|
|
57
|
+
}, [
|
|
58
|
+
urlDrillLevel
|
|
59
|
+
]);
|
|
60
|
+
const navigateTo = useCallback((href)=>{
|
|
61
|
+
setVisualDrillLevel(null);
|
|
62
|
+
if ('/' === href) {
|
|
63
|
+
const firstPath = findFirstLinkPath(config.items) ?? '';
|
|
64
|
+
setPathname(`/${firstPath}`);
|
|
65
|
+
} else setPathname(href);
|
|
66
|
+
}, [
|
|
67
|
+
config.items,
|
|
68
|
+
setPathname
|
|
69
|
+
]);
|
|
70
|
+
const navCtxValue = useMemo(()=>({
|
|
71
|
+
config,
|
|
72
|
+
pathname,
|
|
73
|
+
navStack,
|
|
74
|
+
breadcrumbSegments,
|
|
75
|
+
activeItemId,
|
|
76
|
+
drillLevel: effectiveDrillLevel,
|
|
77
|
+
effectiveActiveItemId,
|
|
78
|
+
navigate,
|
|
79
|
+
drillInto,
|
|
80
|
+
goBack,
|
|
81
|
+
navigateTo
|
|
82
|
+
}), [
|
|
83
|
+
config,
|
|
84
|
+
pathname,
|
|
85
|
+
navStack,
|
|
86
|
+
breadcrumbSegments,
|
|
87
|
+
activeItemId,
|
|
88
|
+
effectiveDrillLevel,
|
|
89
|
+
effectiveActiveItemId,
|
|
90
|
+
navigate,
|
|
91
|
+
drillInto,
|
|
92
|
+
goBack,
|
|
93
|
+
navigateTo
|
|
94
|
+
]);
|
|
95
|
+
return /*#__PURE__*/ jsx(ProductNavContextProvider, {
|
|
96
|
+
value: navCtxValue,
|
|
97
|
+
children: children
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
export { ProductNav };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Breadcrumbs, BreadcrumbsItem, BreadcrumbsScopeSwitcher } from "../Breadcrumbs/index.js";
|
|
3
|
+
import { useProductNavContext } from "./ProductNavContext.js";
|
|
4
|
+
const ProductNavBreadcrumbs = ()=>{
|
|
5
|
+
const { breadcrumbSegments, navigateTo } = useProductNavContext();
|
|
6
|
+
return /*#__PURE__*/ jsx(Breadcrumbs, {
|
|
7
|
+
"data-slot": "nav-breadcrumbs",
|
|
8
|
+
children: breadcrumbSegments.map((segment, i)=>{
|
|
9
|
+
const isLast = i === breadcrumbSegments.length - 1;
|
|
10
|
+
if ('scope-switcher' === segment.type) {
|
|
11
|
+
if (segment.scopeItems?.length && segment.paramValue) return /*#__PURE__*/ jsx(BreadcrumbsScopeSwitcher, {
|
|
12
|
+
value: segment.paramValue,
|
|
13
|
+
items: segment.scopeItems,
|
|
14
|
+
onSelect: (item)=>navigateTo?.(item.href),
|
|
15
|
+
children: segment.label
|
|
16
|
+
}, i);
|
|
17
|
+
return /*#__PURE__*/ jsx(BreadcrumbsItem, {
|
|
18
|
+
children: segment.label
|
|
19
|
+
}, i);
|
|
20
|
+
}
|
|
21
|
+
if ('link' === segment.type && !isLast) return /*#__PURE__*/ jsx(BreadcrumbsItem, {
|
|
22
|
+
href: segment.href,
|
|
23
|
+
onClick: (e)=>{
|
|
24
|
+
if (navigateTo && segment.href) {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
navigateTo(segment.href);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
children: segment.label
|
|
30
|
+
}, i);
|
|
31
|
+
return /*#__PURE__*/ jsx(BreadcrumbsItem, {
|
|
32
|
+
children: segment.label
|
|
33
|
+
}, i);
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
ProductNavBreadcrumbs.displayName = 'ProductNavBreadcrumbs';
|
|
38
|
+
export { ProductNavBreadcrumbs };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { BreadcrumbSegment, NavConfig, NavConfigDrill, NavStackEntry } from './types';
|
|
2
|
+
export interface ProductNavContextValue {
|
|
3
|
+
config: NavConfig;
|
|
4
|
+
pathname: string;
|
|
5
|
+
navStack: NavStackEntry[];
|
|
6
|
+
breadcrumbSegments: BreadcrumbSegment[];
|
|
7
|
+
activeItemId: string | null;
|
|
8
|
+
/** Effective drill level (accounts for visual back-navigation) */
|
|
9
|
+
drillLevel: number;
|
|
10
|
+
/** Active item ID adjusted for the effective drill level */
|
|
11
|
+
effectiveActiveItemId: string | null;
|
|
12
|
+
/** Navigate to a link item (relative within current drill level) */
|
|
13
|
+
navigate: (path: string) => void;
|
|
14
|
+
/** Enter a drill scope */
|
|
15
|
+
drillInto: (drill: NavConfigDrill) => void;
|
|
16
|
+
/** Return to previous menu level visually (without URL change) */
|
|
17
|
+
goBack: () => void;
|
|
18
|
+
/** Navigate to an absolute href (used by breadcrumbs) */
|
|
19
|
+
navigateTo: (href: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare const ProductNavContextProvider: import("react").Provider<ProductNavContextValue | null>;
|
|
22
|
+
export declare function useProductNavContext(): ProductNavContextValue;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
const ProductNavCtx = createContext(null);
|
|
3
|
+
const ProductNavContextProvider = ProductNavCtx.Provider;
|
|
4
|
+
function useProductNavContext() {
|
|
5
|
+
const ctx = useContext(ProductNavCtx);
|
|
6
|
+
if (!ctx) throw new Error('useProductNavContext must be used within a ProductNav provider');
|
|
7
|
+
return ctx;
|
|
8
|
+
}
|
|
9
|
+
export { ProductNavContextProvider, useProductNavContext };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { NavPanel, NavPanelBack, NavPanelDivider, NavPanelGroup, NavPanelGroupContent, NavPanelGroupItem, NavPanelGroupLabel, NavPanelHeader, NavPanelItem, NavPanelSectionHeader } from "../NavPanel/index.js";
|
|
3
|
+
import { findDrillNode } from "./navUtils.js";
|
|
4
|
+
import { useProductNavContext } from "./ProductNavContext.js";
|
|
5
|
+
function renderNavItems(items, activeItemId, onNavigate, onDrillClick, level = 'root') {
|
|
6
|
+
return items.map((item)=>{
|
|
7
|
+
if ('link' === item.type) {
|
|
8
|
+
const Item = 'group' === level ? NavPanelGroupItem : NavPanelItem;
|
|
9
|
+
return /*#__PURE__*/ jsx(Item, {
|
|
10
|
+
icon: item.icon,
|
|
11
|
+
active: activeItemId === item.id,
|
|
12
|
+
onClick: ()=>onNavigate(item.path),
|
|
13
|
+
children: item.label
|
|
14
|
+
}, item.id);
|
|
15
|
+
}
|
|
16
|
+
if ('group' === item.type) return /*#__PURE__*/ jsxs(NavPanelGroup, {
|
|
17
|
+
defaultExpanded: item.defaultExpanded,
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ jsx(NavPanelGroupLabel, {
|
|
20
|
+
icon: item.icon,
|
|
21
|
+
children: item.label
|
|
22
|
+
}),
|
|
23
|
+
/*#__PURE__*/ jsx(NavPanelGroupContent, {
|
|
24
|
+
children: renderNavItems(item.children, activeItemId, onNavigate, onDrillClick, 'group')
|
|
25
|
+
})
|
|
26
|
+
]
|
|
27
|
+
}, item.id);
|
|
28
|
+
if ('section-header' === item.type) return /*#__PURE__*/ jsx(NavPanelSectionHeader, {
|
|
29
|
+
children: item.label
|
|
30
|
+
}, item.id);
|
|
31
|
+
if ('drill' === item.type) {
|
|
32
|
+
const Item = 'group' === level ? NavPanelGroupItem : NavPanelItem;
|
|
33
|
+
return /*#__PURE__*/ jsx(Item, {
|
|
34
|
+
icon: item.icon,
|
|
35
|
+
active: activeItemId === item.id,
|
|
36
|
+
onClick: ()=>onDrillClick?.(item),
|
|
37
|
+
children: item.label
|
|
38
|
+
}, item.id);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const ProductNavPanel = ({ resizable })=>{
|
|
44
|
+
const { config, navStack, effectiveActiveItemId, navigate, drillInto, goBack, drillLevel } = useProductNavContext();
|
|
45
|
+
const currentEntry = navStack[Math.min(drillLevel, navStack.length - 1)];
|
|
46
|
+
if (0 === drillLevel) {
|
|
47
|
+
const itemsWithDividers = [];
|
|
48
|
+
for (const item of config.items){
|
|
49
|
+
itemsWithDividers.push(renderNavItems([
|
|
50
|
+
item
|
|
51
|
+
], effectiveActiveItemId, navigate, drillInto));
|
|
52
|
+
if (item.dividerAfter) itemsWithDividers.push(/*#__PURE__*/ jsx(NavPanelDivider, {}, `divider-${item.id}`));
|
|
53
|
+
}
|
|
54
|
+
return /*#__PURE__*/ jsxs(NavPanel, {
|
|
55
|
+
resizable: resizable,
|
|
56
|
+
children: [
|
|
57
|
+
/*#__PURE__*/ jsx(NavPanelHeader, {
|
|
58
|
+
children: config.productLabel
|
|
59
|
+
}),
|
|
60
|
+
itemsWithDividers
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const parentEntry = navStack[drillLevel - 1];
|
|
65
|
+
const drillNode = findDrillNode(parentEntry.items, parentEntry.activeItemId);
|
|
66
|
+
const backLabel = drillNode?.label ?? 'Back';
|
|
67
|
+
return /*#__PURE__*/ jsxs(NavPanel, {
|
|
68
|
+
resizable: resizable,
|
|
69
|
+
children: [
|
|
70
|
+
/*#__PURE__*/ jsx(NavPanelHeader, {
|
|
71
|
+
children: currentEntry.title
|
|
72
|
+
}),
|
|
73
|
+
/*#__PURE__*/ jsx(NavPanelBack, {
|
|
74
|
+
onClick: goBack,
|
|
75
|
+
children: backLabel
|
|
76
|
+
}),
|
|
77
|
+
renderNavItems(currentEntry.items, effectiveActiveItemId, navigate, drillInto)
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
ProductNavPanel.displayName = 'ProductNavPanel';
|
|
82
|
+
export { ProductNavPanel };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { type MatchNavResult, matchNav } from './matchNav';
|
|
2
|
+
export { findDrillNode, findFirstLinkPath } from './navUtils';
|
|
3
|
+
export { ProductNav, type ProductNavProps } from './ProductNav';
|
|
4
|
+
export { ProductNavBreadcrumbs } from './ProductNavBreadcrumbs';
|
|
5
|
+
export type { ProductNavContextValue } from './ProductNavContext';
|
|
6
|
+
export { useProductNavContext } from './ProductNavContext';
|
|
7
|
+
export { ProductNavPanel } from './ProductNavPanel';
|
|
8
|
+
export type { BreadcrumbSegment, NavConfig, NavConfigDrill, NavConfigGroup, NavConfigLink, NavConfigNode, NavConfigSectionHeader, NavStackEntry, } from './types';
|
|
9
|
+
export { pushPathname, useLocationPathname } from './useLocationPathname';
|
|
10
|
+
export { type UseProductNavResult, useProductNav } from './useProductNav';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { matchNav } from "./matchNav.js";
|
|
2
|
+
import { findDrillNode, findFirstLinkPath } from "./navUtils.js";
|
|
3
|
+
import { ProductNav } from "./ProductNav.js";
|
|
4
|
+
import { ProductNavBreadcrumbs } from "./ProductNavBreadcrumbs.js";
|
|
5
|
+
import { useProductNavContext } from "./ProductNavContext.js";
|
|
6
|
+
import { ProductNavPanel } from "./ProductNavPanel.js";
|
|
7
|
+
import { pushPathname, useLocationPathname } from "./useLocationPathname.js";
|
|
8
|
+
import { useProductNav } from "./useProductNav.js";
|
|
9
|
+
export { ProductNav, ProductNavBreadcrumbs, ProductNavPanel, findDrillNode, findFirstLinkPath, matchNav, pushPathname, useLocationPathname, useProductNav, useProductNavContext };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BreadcrumbSegment, NavConfig, NavStackEntry } from './types';
|
|
2
|
+
export interface MatchNavResult {
|
|
3
|
+
navStack: NavStackEntry[];
|
|
4
|
+
breadcrumbSegments: BreadcrumbSegment[];
|
|
5
|
+
activeItemId: string | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pure function. Matches pathname against nav config.
|
|
9
|
+
*
|
|
10
|
+
* - `link` → single URL segment (`{path}`)
|
|
11
|
+
* - `drill` → segment + dynamic param (`{path}/:param/...`), pushes new stack level
|
|
12
|
+
* - `group` → NO URL segment, children promoted to parent level
|
|
13
|
+
*
|
|
14
|
+
* Called via useMemo on every pathname change.
|
|
15
|
+
*/
|
|
16
|
+
export declare const matchNav: (pathname: string, config: NavConfig) => MatchNavResult;
|