@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.
Files changed (95) hide show
  1. package/dist/components/AppShell/story-content/_storyConfigRenderer.d.ts +7 -0
  2. package/dist/components/AppShell/story-content/_storyConfigRenderer.js +46 -0
  3. package/dist/components/AppShell/story-content/_storyHomeContent.d.ts +2 -0
  4. package/dist/components/AppShell/story-content/_storyHomeContent.js +15 -0
  5. package/dist/components/AppShell/story-content/_storyNavConfigs.d.ts +6 -0
  6. package/dist/components/AppShell/story-content/_storyNavConfigs.js +693 -0
  7. package/dist/components/AppShell/story-content/index.d.ts +3 -0
  8. package/dist/components/AppShell/story-content/index.js +4 -0
  9. package/dist/components/Breadcrumbs/BreadcrumbsItem.js +2 -1
  10. package/dist/components/Breadcrumbs/BreadcrumbsScopeSwitcher.d.ts +18 -0
  11. package/dist/components/Breadcrumbs/BreadcrumbsScopeSwitcher.js +60 -0
  12. package/dist/components/Breadcrumbs/index.d.ts +1 -0
  13. package/dist/components/Breadcrumbs/index.js +2 -1
  14. package/dist/components/NavPanel/NavPanel.d.ts +8 -0
  15. package/dist/components/NavPanel/NavPanel.js +93 -0
  16. package/dist/components/NavPanel/NavPanelBack.d.ts +6 -0
  17. package/dist/components/NavPanel/NavPanelBack.js +31 -0
  18. package/dist/components/NavPanel/NavPanelContext.d.ts +12 -0
  19. package/dist/components/NavPanel/NavPanelContext.js +9 -0
  20. package/dist/components/NavPanel/NavPanelDivider.d.ts +3 -0
  21. package/dist/components/NavPanel/NavPanelDivider.js +10 -0
  22. package/dist/components/NavPanel/NavPanelGroup.d.ts +18 -0
  23. package/dist/components/NavPanel/NavPanelGroup.js +49 -0
  24. package/dist/components/NavPanel/NavPanelGroupContent.d.ts +6 -0
  25. package/dist/components/NavPanel/NavPanelGroupContent.js +36 -0
  26. package/dist/components/NavPanel/NavPanelGroupItem.d.ts +10 -0
  27. package/dist/components/NavPanel/NavPanelGroupItem.js +40 -0
  28. package/dist/components/NavPanel/NavPanelGroupLabel.d.ts +8 -0
  29. package/dist/components/NavPanel/NavPanelGroupLabel.js +48 -0
  30. package/dist/components/NavPanel/NavPanelHeader.d.ts +6 -0
  31. package/dist/components/NavPanel/NavPanelHeader.js +21 -0
  32. package/dist/components/NavPanel/NavPanelItem.d.ts +10 -0
  33. package/dist/components/NavPanel/NavPanelItem.js +35 -0
  34. package/dist/components/NavPanel/NavPanelResizeHandle.d.ts +2 -0
  35. package/dist/components/NavPanel/NavPanelResizeHandle.js +81 -0
  36. package/dist/components/NavPanel/NavPanelSectionHeader.d.ts +6 -0
  37. package/dist/components/NavPanel/NavPanelSectionHeader.js +22 -0
  38. package/dist/components/NavPanel/classes.d.ts +6 -0
  39. package/dist/components/NavPanel/classes.js +24 -0
  40. package/dist/components/NavPanel/index.d.ts +10 -0
  41. package/dist/components/NavPanel/index.js +11 -0
  42. package/dist/components/NavRail/NavRail.js +33 -2
  43. package/dist/components/NavRail/NavRailItem.js +10 -3
  44. package/dist/components/NavRail/classes.js +2 -2
  45. package/dist/components/NavRail/useShortcut.d.ts +2 -0
  46. package/dist/components/NavRail/useShortcut.js +44 -0
  47. package/dist/components/ProductNav/ProductNav.d.ts +13 -0
  48. package/dist/components/ProductNav/ProductNav.js +100 -0
  49. package/dist/components/ProductNav/ProductNavBreadcrumbs.d.ts +2 -0
  50. package/dist/components/ProductNav/ProductNavBreadcrumbs.js +38 -0
  51. package/dist/components/ProductNav/ProductNavContext.d.ts +22 -0
  52. package/dist/components/ProductNav/ProductNavContext.js +9 -0
  53. package/dist/components/ProductNav/ProductNavPanel.d.ts +6 -0
  54. package/dist/components/ProductNav/ProductNavPanel.js +82 -0
  55. package/dist/components/ProductNav/index.d.ts +10 -0
  56. package/dist/components/ProductNav/index.js +9 -0
  57. package/dist/components/ProductNav/matchNav.d.ts +16 -0
  58. package/dist/components/ProductNav/matchNav.js +108 -0
  59. package/dist/components/ProductNav/navUtils.d.ts +5 -0
  60. package/dist/components/ProductNav/navUtils.js +26 -0
  61. package/dist/components/ProductNav/types.d.ts +69 -0
  62. package/dist/components/ProductNav/types.js +0 -0
  63. package/dist/components/ProductNav/useLocationPathname.d.ts +4 -0
  64. package/dist/components/ProductNav/useLocationPathname.js +24 -0
  65. package/dist/components/ProductNav/useProductNav.d.ts +16 -0
  66. package/dist/components/ProductNav/useProductNav.js +19 -0
  67. package/dist/components/RemoteShell/RemoteShell.d.ts +7 -0
  68. package/dist/components/RemoteShell/RemoteShell.js +16 -0
  69. package/dist/components/RemoteShell/RemoteShellBreadcrumb.d.ts +6 -0
  70. package/dist/components/RemoteShell/RemoteShellBreadcrumb.js +16 -0
  71. package/dist/components/RemoteShell/RemoteShellContent.d.ts +6 -0
  72. package/dist/components/RemoteShell/RemoteShellContent.js +16 -0
  73. package/dist/components/RemoteShell/RemoteShellPanel.d.ts +6 -0
  74. package/dist/components/RemoteShell/RemoteShellPanel.js +16 -0
  75. package/dist/components/RemoteShell/index.d.ts +4 -0
  76. package/dist/components/RemoteShell/index.js +5 -0
  77. package/dist/components/SimpleCharts/LineChart/LineChart.js +5 -5
  78. package/dist/components/SimpleCharts/LineChart/LineChartZoomBrush.js +66 -12
  79. package/dist/components/SimpleCharts/LineChart/hooks/useLineChartActiveKey.js +4 -3
  80. package/dist/components/SimpleCharts/LineChart/hooks/useLineChartZoomState.d.ts +9 -2
  81. package/dist/components/SimpleCharts/LineChart/hooks/useLineChartZoomState.js +88 -34
  82. package/dist/hooks/index.d.ts +1 -0
  83. package/dist/hooks/index.js +2 -1
  84. package/dist/hooks/useArrowNav.d.ts +7 -0
  85. package/dist/hooks/useArrowNav.js +98 -0
  86. package/dist/icons/ChevronUpDown.d.ts +3 -0
  87. package/dist/icons/ChevronUpDown.js +12 -0
  88. package/dist/icons/MapPin.d.ts +3 -0
  89. package/dist/icons/MapPin.js +12 -0
  90. package/dist/icons/index.d.ts +2 -0
  91. package/dist/icons/index.js +3 -1
  92. package/dist/index.d.ts +3 -0
  93. package/dist/index.js +4 -1
  94. package/dist/metadata/components.json +4829 -682
  95. 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 })=>/*#__PURE__*/ jsx(TestIdProvider, {
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: element
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,2 @@
1
+ import { type RefObject } from 'react';
2
+ export declare function useShortcut(shortcut: string[] | undefined, ref: RefObject<HTMLElement | null>): void;
@@ -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,2 @@
1
+ import type { FC } from 'react';
2
+ export declare const ProductNavBreadcrumbs: FC;
@@ -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,6 @@
1
+ import type { FC } from 'react';
2
+ type ProductNavPanelProps = {
3
+ resizable?: boolean;
4
+ };
5
+ export declare const ProductNavPanel: FC<ProductNavPanelProps>;
6
+ export {};
@@ -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;