@wallarm-org/design-system 0.50.1 → 0.50.2-rc-feature-shell.2
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 +1 -1
- package/dist/components/AppShell/story-content/_storyConfigRenderer.js +15 -29
- package/dist/components/AppShell/story-content/_storyLib.js +1 -1
- package/dist/components/AppShell/story-content/_storyNavConfigs.d.ts +1 -1
- package/dist/components/AppShell/story-content/_storyNavConfigs.js +5 -0
- package/dist/components/NavPanel/NavPanelHeader.js +1 -1
- package/dist/components/Page/PageContent.js +1 -1
- package/dist/components/Page/PageHeader.js +1 -1
- package/dist/components/{ProductNav → RemoteShell}/HeaderActions.d.ts +1 -1
- package/dist/components/{ProductNav → RemoteShell}/NavItemsList.d.ts +1 -1
- package/dist/components/RemoteShell/NavPanelContent.d.ts +6 -0
- package/dist/components/RemoteShell/NavPanelContent.js +65 -0
- package/dist/components/RemoteShell/RemoteShell.d.ts +7 -0
- package/dist/components/RemoteShell/RemoteShell.js +109 -9
- package/dist/components/RemoteShell/RemoteShellBreadcrumb.js +39 -4
- package/dist/components/RemoteShell/RemoteShellContent.d.ts +1 -0
- package/dist/components/RemoteShell/RemoteShellContent.js +7 -3
- package/dist/components/RemoteShell/RemoteShellPanel.d.ts +4 -2
- package/dist/components/RemoteShell/RemoteShellPanel.js +76 -3
- package/dist/components/RemoteShell/index.d.ts +2 -0
- package/dist/components/RemoteShell/index.js +2 -1
- package/dist/components/{ProductNav/ProductNavContext.d.ts → RemoteShell/model/RemoteShellContext.d.ts} +3 -3
- package/dist/components/RemoteShell/model/RemoteShellContext.js +9 -0
- package/dist/components/RemoteShell/model/index.d.ts +5 -0
- package/dist/components/RemoteShell/model/index.js +5 -0
- package/dist/components/{ProductNav → RemoteShell/model}/matchNav.js +1 -1
- package/dist/components/{ProductNav → RemoteShell/model}/types.d.ts +2 -3
- package/dist/index.d.ts +1 -2
- package/dist/index.js +2 -3
- package/dist/metadata/components.json +35 -303
- package/package.json +1 -1
- package/dist/components/ProductNav/ProductNav.d.ts +0 -13
- package/dist/components/ProductNav/ProductNav.js +0 -109
- package/dist/components/ProductNav/ProductNavBreadcrumbs.d.ts +0 -2
- package/dist/components/ProductNav/ProductNavBreadcrumbs.js +0 -38
- package/dist/components/ProductNav/ProductNavContext.js +0 -9
- package/dist/components/ProductNav/ProductNavPanel.d.ts +0 -6
- package/dist/components/ProductNav/ProductNavPanel.js +0 -107
- package/dist/components/ProductNav/index.d.ts +0 -11
- package/dist/components/ProductNav/index.js +0 -10
- package/dist/components/ProductNav/useProductNav.d.ts +0 -16
- package/dist/components/ProductNav/useProductNav.js +0 -19
- /package/dist/components/{ProductNav → RemoteShell}/HeaderActions.js +0 -0
- /package/dist/components/{ProductNav → RemoteShell}/NavItemsList.js +0 -0
- /package/dist/components/{ProductNav → RemoteShell/model}/matchNav.d.ts +0 -0
- /package/dist/components/{ProductNav → RemoteShell/model}/navUtils.d.ts +0 -0
- /package/dist/components/{ProductNav → RemoteShell/model}/navUtils.js +0 -0
- /package/dist/components/{ProductNav → RemoteShell/model}/types.js +0 -0
- /package/dist/components/{ProductNav → RemoteShell/model}/useDrillTransition.d.ts +0 -0
- /package/dist/components/{ProductNav → RemoteShell/model}/useDrillTransition.js +0 -0
- /package/dist/components/{ProductNav → RemoteShell/model}/useLocationPathname.d.ts +0 -0
- /package/dist/components/{ProductNav → RemoteShell/model}/useLocationPathname.js +0 -0
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { NavPanel, NavPanelHeader } from "../../NavPanel/index.js";
|
|
4
3
|
import { Page, PageContent, PageHeader, PageTitle } from "../../Page/index.js";
|
|
5
|
-
import {
|
|
6
|
-
import { RemoteShell, RemoteShellBreadcrumb, RemoteShellContent, RemoteShellPanel } from "../../RemoteShell/index.js";
|
|
4
|
+
import { RemoteShell, RemoteShellBreadcrumb, RemoteShellContent, RemoteShellPanel, useRemoteShellContext } from "../../RemoteShell/index.js";
|
|
7
5
|
import { HomeContent } from "./_storyHomeContent.js";
|
|
8
6
|
import { PRODUCT_CONFIGS } from "./_storyLib.js";
|
|
9
7
|
const RemotePageContent = ()=>{
|
|
10
|
-
const { breadcrumbSegments } =
|
|
8
|
+
const { breadcrumbSegments } = useRemoteShellContext();
|
|
11
9
|
const lastSegment = breadcrumbSegments[breadcrumbSegments.length - 1];
|
|
12
10
|
const pageTitle = lastSegment?.label ?? '';
|
|
13
11
|
const fullPath = breadcrumbSegments.map((s)=>s.label).join(' / ');
|
|
@@ -44,36 +42,24 @@ const ConfigRemote = ({ config, basePath })=>{
|
|
|
44
42
|
}, [
|
|
45
43
|
config.productLabel
|
|
46
44
|
]);
|
|
47
|
-
|
|
48
|
-
children: [
|
|
49
|
-
/*#__PURE__*/ jsx(RemoteShellPanel, {
|
|
50
|
-
children: /*#__PURE__*/ jsxs(NavPanel, {
|
|
51
|
-
children: [
|
|
52
|
-
/*#__PURE__*/ jsx(NavPanelHeader, {
|
|
53
|
-
children: config.productLabel
|
|
54
|
-
}),
|
|
55
|
-
/*#__PURE__*/ jsx(NavPanelSkeleton, {
|
|
56
|
-
count: 6
|
|
57
|
-
})
|
|
58
|
-
]
|
|
59
|
-
})
|
|
60
|
-
}),
|
|
61
|
-
/*#__PURE__*/ jsx(RemoteShellContent, {})
|
|
62
|
-
]
|
|
63
|
-
});
|
|
64
|
-
return /*#__PURE__*/ jsx(ProductNav, {
|
|
45
|
+
return /*#__PURE__*/ jsx(RemoteShell, {
|
|
65
46
|
config: config,
|
|
66
47
|
basePath: basePath,
|
|
67
|
-
children: /*#__PURE__*/ jsxs(
|
|
48
|
+
children: loading ? /*#__PURE__*/ jsxs(Fragment, {
|
|
68
49
|
children: [
|
|
69
50
|
/*#__PURE__*/ jsx(RemoteShellPanel, {
|
|
70
|
-
|
|
71
|
-
resizable: true
|
|
72
|
-
})
|
|
51
|
+
isLoading: true
|
|
73
52
|
}),
|
|
74
|
-
/*#__PURE__*/ jsx(
|
|
75
|
-
|
|
53
|
+
/*#__PURE__*/ jsx(RemoteShellContent, {
|
|
54
|
+
isLoading: true
|
|
55
|
+
})
|
|
56
|
+
]
|
|
57
|
+
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
58
|
+
children: [
|
|
59
|
+
/*#__PURE__*/ jsx(RemoteShellPanel, {
|
|
60
|
+
resizable: true
|
|
76
61
|
}),
|
|
62
|
+
/*#__PURE__*/ jsx(RemoteShellBreadcrumb, {}),
|
|
77
63
|
/*#__PURE__*/ jsx(RemoteShellContent, {
|
|
78
64
|
children: /*#__PURE__*/ jsx(RemotePageContent, {})
|
|
79
65
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findFirstLinkPath, pushPathname } from "../../
|
|
1
|
+
import { findFirstLinkPath, pushPathname } from "../../RemoteShell/index.js";
|
|
2
2
|
import { aiHypervisorNavConfig, edgeNavConfig, infraDiscoveryNavConfig, securityTestingNavConfig, settingsNavConfig } from "./_storyNavConfigs.js";
|
|
3
3
|
const KNOWN_PRODUCTS = [
|
|
4
4
|
'home',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CircleDashed, Filter, Plus } from "../../../icons/index.js";
|
|
2
2
|
const edgeNavConfig = {
|
|
3
3
|
productLabel: 'Edge',
|
|
4
|
+
productPath: '/edge',
|
|
4
5
|
headerActions: [
|
|
5
6
|
{
|
|
6
7
|
icon: Filter,
|
|
@@ -340,6 +341,7 @@ const edgeNavConfig = {
|
|
|
340
341
|
};
|
|
341
342
|
const aiHypervisorNavConfig = {
|
|
342
343
|
productLabel: 'AI Hypervisor',
|
|
344
|
+
productPath: '/ai-hypervisor',
|
|
343
345
|
items: [
|
|
344
346
|
{
|
|
345
347
|
type: 'link',
|
|
@@ -454,6 +456,7 @@ const aiHypervisorNavConfig = {
|
|
|
454
456
|
};
|
|
455
457
|
const infraDiscoveryNavConfig = {
|
|
456
458
|
productLabel: 'Infra Discovery',
|
|
459
|
+
productPath: '/infra-discovery',
|
|
457
460
|
headerActions: [
|
|
458
461
|
{
|
|
459
462
|
icon: Filter,
|
|
@@ -554,6 +557,7 @@ const infraDiscoveryNavConfig = {
|
|
|
554
557
|
};
|
|
555
558
|
const securityTestingNavConfig = {
|
|
556
559
|
productLabel: 'Security Testing',
|
|
560
|
+
productPath: '/security-testing',
|
|
557
561
|
items: [
|
|
558
562
|
{
|
|
559
563
|
type: 'link',
|
|
@@ -627,6 +631,7 @@ const securityTestingNavConfig = {
|
|
|
627
631
|
};
|
|
628
632
|
const settingsNavConfig = {
|
|
629
633
|
productLabel: 'Settings',
|
|
634
|
+
productPath: '/settings',
|
|
630
635
|
items: [
|
|
631
636
|
{
|
|
632
637
|
type: 'link',
|
|
@@ -9,7 +9,7 @@ const NavPanelHeader = ({ ref, className, children, ...props })=>{
|
|
|
9
9
|
ref: ref,
|
|
10
10
|
"data-slot": "nav-panel-header",
|
|
11
11
|
"data-testid": testId,
|
|
12
|
-
className: cn('sticky top-0 z-10 flex shrink-0 items-center p-4', className),
|
|
12
|
+
className: cn('sticky top-0 z-10 flex shrink-0 items-center p-4 bg-bg-surface-2', className),
|
|
13
13
|
children: /*#__PURE__*/ jsx(Text, {
|
|
14
14
|
size: "sm",
|
|
15
15
|
weight: "medium",
|
|
@@ -8,7 +8,7 @@ const PageContent = ({ ref, children, className, ...props })=>{
|
|
|
8
8
|
ref: ref,
|
|
9
9
|
"data-testid": testId,
|
|
10
10
|
"data-slot": "page-content",
|
|
11
|
-
className: cn('flex-1 min-h-0 overflow-auto px-
|
|
11
|
+
className: cn('flex-1 min-h-0 overflow-auto px-16', className),
|
|
12
12
|
children: children
|
|
13
13
|
});
|
|
14
14
|
};
|
|
@@ -8,7 +8,7 @@ const PageHeader = ({ ref, sticky, children, className, ...props })=>{
|
|
|
8
8
|
ref: ref,
|
|
9
9
|
"data-testid": testId,
|
|
10
10
|
"data-slot": "page-header",
|
|
11
|
-
className: cn('flex items-center px-
|
|
11
|
+
className: cn('flex items-center px-16 pb-16 gap-12', sticky && 'sticky top-0 z-10 bg-bg-surface-2', className),
|
|
12
12
|
children: children
|
|
13
13
|
});
|
|
14
14
|
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { NavPanelBack, NavPanelDivider, NavPanelHeader } from "../NavPanel/index.js";
|
|
3
|
+
import { Text } from "../Text/index.js";
|
|
4
|
+
import { HeaderActions } from "./HeaderActions.js";
|
|
5
|
+
import { findDrillNode, useRemoteShellContext } from "./model/index.js";
|
|
6
|
+
import { NavItemsList } from "./NavItemsList.js";
|
|
7
|
+
const NavPanelContent = ({ level: rawLevel })=>{
|
|
8
|
+
const { config, navStack, effectiveActiveItemId, navigate, drillInto, goBack } = useRemoteShellContext();
|
|
9
|
+
const level = Math.min(rawLevel, navStack.length - 1);
|
|
10
|
+
const entry = navStack[level];
|
|
11
|
+
const hasHeaderActions = !!config.headerActions?.length;
|
|
12
|
+
if (0 === level) return /*#__PURE__*/ jsxs(Fragment, {
|
|
13
|
+
children: [
|
|
14
|
+
hasHeaderActions ? /*#__PURE__*/ jsxs("div", {
|
|
15
|
+
className: "sticky top-0 z-10 flex shrink-0 items-center justify-between p-4 bg-bg-surface-2",
|
|
16
|
+
children: [
|
|
17
|
+
/*#__PURE__*/ jsx(Text, {
|
|
18
|
+
size: "sm",
|
|
19
|
+
weight: "medium",
|
|
20
|
+
children: config.productLabel
|
|
21
|
+
}),
|
|
22
|
+
/*#__PURE__*/ jsx(HeaderActions, {
|
|
23
|
+
actions: config.headerActions
|
|
24
|
+
})
|
|
25
|
+
]
|
|
26
|
+
}) : /*#__PURE__*/ jsx(NavPanelHeader, {
|
|
27
|
+
children: config.productLabel
|
|
28
|
+
}),
|
|
29
|
+
/*#__PURE__*/ jsx(NavItemsList, {
|
|
30
|
+
items: config.items,
|
|
31
|
+
activeItemId: effectiveActiveItemId,
|
|
32
|
+
onNavigate: navigate,
|
|
33
|
+
onDrillClick: drillInto
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
});
|
|
37
|
+
const parentEntry = navStack[level - 1];
|
|
38
|
+
const drillNode = findDrillNode(parentEntry.items, parentEntry.activeItemId);
|
|
39
|
+
const backLabel = drillNode?.label ?? 'Back';
|
|
40
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
41
|
+
children: [
|
|
42
|
+
/*#__PURE__*/ jsxs("div", {
|
|
43
|
+
className: "sticky top-0 z-10 flex flex-col gap-2 bg-bg-surface-1",
|
|
44
|
+
children: [
|
|
45
|
+
/*#__PURE__*/ jsx(NavPanelHeader, {
|
|
46
|
+
children: entry.title
|
|
47
|
+
}),
|
|
48
|
+
/*#__PURE__*/ jsx(NavPanelBack, {
|
|
49
|
+
onClick: goBack,
|
|
50
|
+
children: backLabel
|
|
51
|
+
}),
|
|
52
|
+
/*#__PURE__*/ jsx(NavPanelDivider, {})
|
|
53
|
+
]
|
|
54
|
+
}),
|
|
55
|
+
/*#__PURE__*/ jsx(NavItemsList, {
|
|
56
|
+
items: entry.items,
|
|
57
|
+
activeItemId: effectiveActiveItemId,
|
|
58
|
+
onNavigate: navigate,
|
|
59
|
+
onDrillClick: drillInto
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
NavPanelContent.displayName = 'NavPanelContent';
|
|
65
|
+
export { NavPanelContent };
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
|
|
2
2
|
import { type TestableProps } from '../../utils/testId';
|
|
3
|
+
import type { NavConfig } from './model';
|
|
3
4
|
export interface RemoteShellProps extends HTMLAttributes<HTMLDivElement>, TestableProps {
|
|
4
5
|
ref?: Ref<HTMLDivElement>;
|
|
5
6
|
children?: ReactNode;
|
|
7
|
+
/** Navigation config used to build nav state for sub-components. */
|
|
8
|
+
config: NavConfig;
|
|
9
|
+
/** URL prefix stripped before matching and prepended when navigating (e.g. `"/edge"`). */
|
|
10
|
+
basePath?: string;
|
|
11
|
+
/** Custom navigation handler for router integration (React Router, Next.js, etc.). */
|
|
12
|
+
onNavigate?: (pathname: string) => void;
|
|
6
13
|
}
|
|
7
14
|
export declare const RemoteShell: FC<RemoteShellProps>;
|
|
@@ -1,16 +1,116 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
3
|
import { cn } from "../../utils/cn.js";
|
|
3
4
|
import { TestIdProvider } from "../../utils/testId.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { RemoteShellContextProvider, findFirstLinkPath, matchNav, pushPathname, useLocationPathname } from "./model/index.js";
|
|
6
|
+
const RemoteShell = ({ ref, className, children, config, basePath, onNavigate, 'data-testid': testId, ...props })=>{
|
|
7
|
+
const fullPathname = useLocationPathname();
|
|
8
|
+
const pathname = basePath && fullPathname.startsWith(basePath) ? fullPathname.slice(basePath.length) || '/' : fullPathname;
|
|
9
|
+
const setPathname = useCallback((next)=>{
|
|
10
|
+
const fullPath = basePath ? `${basePath}${next}` : next;
|
|
11
|
+
if (onNavigate) onNavigate(fullPath);
|
|
12
|
+
else pushPathname(fullPath);
|
|
13
|
+
}, [
|
|
14
|
+
basePath,
|
|
15
|
+
onNavigate
|
|
16
|
+
]);
|
|
17
|
+
const { navStack, breadcrumbSegments, activeItemId } = useMemo(()=>matchNav(pathname, config), [
|
|
18
|
+
pathname,
|
|
19
|
+
config
|
|
20
|
+
]);
|
|
21
|
+
const urlDrillLevel = navStack.length - 1;
|
|
22
|
+
const [visualDrillLevel, setVisualDrillLevel] = useState(null);
|
|
23
|
+
useEffect(()=>{
|
|
24
|
+
setVisualDrillLevel(null);
|
|
25
|
+
}, [
|
|
26
|
+
pathname
|
|
27
|
+
]);
|
|
28
|
+
const effectiveDrillLevel = visualDrillLevel ?? urlDrillLevel;
|
|
29
|
+
const effectiveActiveItemId = effectiveDrillLevel < urlDrillLevel ? navStack[effectiveDrillLevel]?.activeItemId ?? activeItemId : activeItemId;
|
|
30
|
+
const navigate = useCallback((path)=>{
|
|
31
|
+
setVisualDrillLevel(null);
|
|
32
|
+
const segments = pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
|
|
33
|
+
const prefixSegments = segments.slice(0, 2 * effectiveDrillLevel);
|
|
34
|
+
setPathname(`/${[
|
|
35
|
+
...prefixSegments,
|
|
36
|
+
path
|
|
37
|
+
].join('/')}`);
|
|
38
|
+
}, [
|
|
39
|
+
effectiveDrillLevel,
|
|
40
|
+
pathname,
|
|
41
|
+
setPathname
|
|
42
|
+
]);
|
|
43
|
+
const drillInto = useCallback((drill)=>{
|
|
44
|
+
setVisualDrillLevel(null);
|
|
45
|
+
const segments = pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
|
|
46
|
+
const prefixSegments = segments.slice(0, 2 * effectiveDrillLevel);
|
|
47
|
+
const defaultEntity = drill.entities?.[0]?.id ?? 'default';
|
|
48
|
+
const firstChildPath = findFirstLinkPath(drill.children) ?? '';
|
|
49
|
+
setPathname(`/${[
|
|
50
|
+
...prefixSegments,
|
|
51
|
+
drill.path,
|
|
52
|
+
defaultEntity,
|
|
53
|
+
firstChildPath
|
|
54
|
+
].join('/')}`);
|
|
55
|
+
}, [
|
|
56
|
+
effectiveDrillLevel,
|
|
57
|
+
pathname,
|
|
58
|
+
setPathname
|
|
59
|
+
]);
|
|
60
|
+
const goBack = useCallback(()=>{
|
|
61
|
+
setVisualDrillLevel((prev)=>{
|
|
62
|
+
const current = prev ?? urlDrillLevel;
|
|
63
|
+
return Math.max(current - 1, 0);
|
|
64
|
+
});
|
|
65
|
+
}, [
|
|
66
|
+
urlDrillLevel
|
|
67
|
+
]);
|
|
68
|
+
const navigateTo = useCallback((href)=>{
|
|
69
|
+
setVisualDrillLevel(null);
|
|
70
|
+
href === config.productPath ? setPathname('/') : setPathname(href);
|
|
71
|
+
}, [
|
|
72
|
+
config.productPath,
|
|
73
|
+
setPathname
|
|
74
|
+
]);
|
|
75
|
+
const navCtxValue = useMemo(()=>({
|
|
76
|
+
config,
|
|
77
|
+
pathname,
|
|
78
|
+
navStack,
|
|
79
|
+
breadcrumbSegments,
|
|
80
|
+
activeItemId,
|
|
81
|
+
drillLevel: effectiveDrillLevel,
|
|
82
|
+
effectiveActiveItemId,
|
|
83
|
+
navigate,
|
|
84
|
+
drillInto,
|
|
85
|
+
goBack,
|
|
86
|
+
navigateTo
|
|
87
|
+
}), [
|
|
88
|
+
config,
|
|
89
|
+
pathname,
|
|
90
|
+
navStack,
|
|
91
|
+
breadcrumbSegments,
|
|
92
|
+
activeItemId,
|
|
93
|
+
effectiveDrillLevel,
|
|
94
|
+
effectiveActiveItemId,
|
|
95
|
+
navigate,
|
|
96
|
+
drillInto,
|
|
97
|
+
goBack,
|
|
98
|
+
navigateTo
|
|
99
|
+
]);
|
|
100
|
+
return /*#__PURE__*/ jsx(RemoteShellContextProvider, {
|
|
101
|
+
value: navCtxValue,
|
|
102
|
+
children: /*#__PURE__*/ jsx(TestIdProvider, {
|
|
103
|
+
value: testId,
|
|
104
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
105
|
+
...props,
|
|
106
|
+
ref: ref,
|
|
107
|
+
"data-slot": "remote-shell",
|
|
108
|
+
"data-testid": testId,
|
|
109
|
+
className: cn('grid h-full overflow-hidden overscroll-none [grid-template-areas:"panel_breadcrumb""panel_content"] [grid-template-columns:auto_1fr] [grid-template-rows:auto_1fr]', className),
|
|
110
|
+
children: children
|
|
111
|
+
})
|
|
13
112
|
})
|
|
14
113
|
});
|
|
114
|
+
};
|
|
15
115
|
RemoteShell.displayName = 'RemoteShell';
|
|
16
116
|
export { RemoteShell };
|
|
@@ -1,15 +1,50 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../utils/cn.js";
|
|
3
3
|
import { useTestId } from "../../utils/testId.js";
|
|
4
|
+
import { Breadcrumbs, BreadcrumbsItem, BreadcrumbsScopeSwitcher } from "../Breadcrumbs/index.js";
|
|
5
|
+
import { useRemoteShellContext } from "./model/index.js";
|
|
4
6
|
const RemoteShellBreadcrumb = ({ ref, className, children, ...props })=>{
|
|
5
7
|
const testId = useTestId('breadcrumb');
|
|
6
|
-
|
|
8
|
+
const { breadcrumbSegments, navigateTo } = useRemoteShellContext();
|
|
9
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
7
10
|
...props,
|
|
8
11
|
ref: ref,
|
|
9
12
|
"data-slot": "remote-shell-breadcrumb",
|
|
10
13
|
"data-testid": testId,
|
|
11
|
-
className: cn('[grid-area:breadcrumb] flex items-center px-
|
|
12
|
-
children:
|
|
14
|
+
className: cn('[grid-area:breadcrumb] flex items-center gap-8 px-16 py-8', className),
|
|
15
|
+
children: [
|
|
16
|
+
/*#__PURE__*/ jsx(Breadcrumbs, {
|
|
17
|
+
"data-slot": "nav-breadcrumbs",
|
|
18
|
+
children: breadcrumbSegments.map((segment, i)=>{
|
|
19
|
+
const isLast = i === breadcrumbSegments.length - 1;
|
|
20
|
+
if ('scope-switcher' === segment.type) {
|
|
21
|
+
if (segment.scopeItems?.length && segment.paramValue) return /*#__PURE__*/ jsx(BreadcrumbsScopeSwitcher, {
|
|
22
|
+
value: segment.paramValue,
|
|
23
|
+
items: segment.scopeItems,
|
|
24
|
+
onSelect: (item)=>navigateTo?.(item.href),
|
|
25
|
+
children: segment.label
|
|
26
|
+
}, i);
|
|
27
|
+
return /*#__PURE__*/ jsx(BreadcrumbsItem, {
|
|
28
|
+
children: segment.label
|
|
29
|
+
}, i);
|
|
30
|
+
}
|
|
31
|
+
if ('link' === segment.type && !isLast) return /*#__PURE__*/ jsx(BreadcrumbsItem, {
|
|
32
|
+
href: segment.href,
|
|
33
|
+
onClick: (e)=>{
|
|
34
|
+
if (navigateTo && segment.href) {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
navigateTo(segment.href);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
children: segment.label
|
|
40
|
+
}, i);
|
|
41
|
+
return /*#__PURE__*/ jsx(BreadcrumbsItem, {
|
|
42
|
+
children: segment.label
|
|
43
|
+
}, i);
|
|
44
|
+
})
|
|
45
|
+
}),
|
|
46
|
+
children
|
|
47
|
+
]
|
|
13
48
|
});
|
|
14
49
|
};
|
|
15
50
|
RemoteShellBreadcrumb.displayName = 'RemoteShellBreadcrumb';
|
|
@@ -2,5 +2,6 @@ import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
|
|
|
2
2
|
export interface RemoteShellContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
3
|
ref?: Ref<HTMLDivElement>;
|
|
4
4
|
children?: ReactNode;
|
|
5
|
+
isLoading?: boolean;
|
|
5
6
|
}
|
|
6
7
|
export declare const RemoteShellContent: FC<RemoteShellContentProps>;
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../utils/cn.js";
|
|
3
3
|
import { useTestId } from "../../utils/testId.js";
|
|
4
|
-
|
|
4
|
+
import { Loader } from "../Loader/index.js";
|
|
5
|
+
const RemoteShellContent = ({ ref, className, children, isLoading, ...props })=>{
|
|
5
6
|
const testId = useTestId('content');
|
|
6
7
|
return /*#__PURE__*/ jsx("div", {
|
|
7
8
|
...props,
|
|
8
9
|
ref: ref,
|
|
9
10
|
"data-slot": "remote-shell-content",
|
|
10
11
|
"data-testid": testId,
|
|
11
|
-
className: cn('[grid-area:content] min-h-0 overflow-auto overscroll-none
|
|
12
|
-
children:
|
|
12
|
+
className: cn('[grid-area:content] min-h-0 overflow-auto overscroll-none [scrollbar-width:thin]', isLoading && 'flex items-center justify-center', className),
|
|
13
|
+
children: isLoading ? /*#__PURE__*/ jsx(Loader, {
|
|
14
|
+
size: "3xl",
|
|
15
|
+
color: "brand"
|
|
16
|
+
}) : children
|
|
13
17
|
});
|
|
14
18
|
};
|
|
15
19
|
RemoteShellContent.displayName = 'RemoteShellContent';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { FC, HTMLAttributes,
|
|
1
|
+
import type { FC, HTMLAttributes, Ref } from 'react';
|
|
2
2
|
export interface RemoteShellPanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
3
|
ref?: Ref<HTMLDivElement>;
|
|
4
|
-
|
|
4
|
+
resizable?: boolean;
|
|
5
|
+
isLoading?: boolean;
|
|
6
|
+
loaderCount?: number;
|
|
5
7
|
}
|
|
6
8
|
export declare const RemoteShellPanel: FC<RemoteShellPanelProps>;
|
|
@@ -1,15 +1,88 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../utils/cn.js";
|
|
3
3
|
import { useTestId } from "../../utils/testId.js";
|
|
4
|
-
|
|
4
|
+
import { NavPanel, NavPanelHeader, NavPanelSkeleton } from "../NavPanel/index.js";
|
|
5
|
+
import { useRemoteShellContext } from "./model/index.js";
|
|
6
|
+
import { DRILL_ANIMATION_DURATION, DRILL_ANIMATION_EASING, useDrillTransition } from "./model/useDrillTransition.js";
|
|
7
|
+
import { NavPanelContent } from "./NavPanelContent.js";
|
|
8
|
+
const RemoteShellPanel = ({ ref, className, resizable, isLoading, loaderCount = 6, ...props })=>{
|
|
5
9
|
const testId = useTestId('panel');
|
|
10
|
+
const { config, drillLevel } = useRemoteShellContext();
|
|
11
|
+
const { transition, clearTransition } = useDrillTransition(drillLevel);
|
|
12
|
+
if (isLoading) return /*#__PURE__*/ jsx("div", {
|
|
13
|
+
...props,
|
|
14
|
+
ref: ref,
|
|
15
|
+
"data-slot": "remote-shell-panel",
|
|
16
|
+
"data-testid": testId,
|
|
17
|
+
className: cn('[grid-area:panel] min-h-0', className),
|
|
18
|
+
children: /*#__PURE__*/ jsxs(NavPanel, {
|
|
19
|
+
children: [
|
|
20
|
+
/*#__PURE__*/ jsx(NavPanelHeader, {
|
|
21
|
+
children: config.productLabel
|
|
22
|
+
}),
|
|
23
|
+
/*#__PURE__*/ jsx(NavPanelSkeleton, {
|
|
24
|
+
count: loaderCount
|
|
25
|
+
})
|
|
26
|
+
]
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
if (transition) {
|
|
30
|
+
const isForward = 'forward' === transition.direction;
|
|
31
|
+
const slideAnim = isForward ? 'ds-nav-drill-forward' : 'ds-nav-drill-backward';
|
|
32
|
+
const timing = `${DRILL_ANIMATION_DURATION} ${DRILL_ANIMATION_EASING} both`;
|
|
33
|
+
return /*#__PURE__*/ jsx("div", {
|
|
34
|
+
...props,
|
|
35
|
+
ref: ref,
|
|
36
|
+
"data-slot": "remote-shell-panel",
|
|
37
|
+
"data-testid": testId,
|
|
38
|
+
className: cn('[grid-area:panel] min-h-0', className),
|
|
39
|
+
children: /*#__PURE__*/ jsx(NavPanel, {
|
|
40
|
+
resizable: resizable,
|
|
41
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
42
|
+
className: "min-h-0 flex-1 overflow-hidden",
|
|
43
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
44
|
+
className: "flex motion-reduce:animate-none",
|
|
45
|
+
style: {
|
|
46
|
+
animation: `${slideAnim} ${timing}`
|
|
47
|
+
},
|
|
48
|
+
onAnimationEnd: clearTransition,
|
|
49
|
+
children: [
|
|
50
|
+
/*#__PURE__*/ jsx("div", {
|
|
51
|
+
className: "flex w-full shrink-0 flex-col gap-2 motion-reduce:animate-none",
|
|
52
|
+
style: {
|
|
53
|
+
animation: `${isForward ? 'ds-nav-blur-out' : 'ds-nav-blur-in'} ${timing}`
|
|
54
|
+
},
|
|
55
|
+
children: /*#__PURE__*/ jsx(NavPanelContent, {
|
|
56
|
+
level: isForward ? transition.fromLevel : drillLevel
|
|
57
|
+
})
|
|
58
|
+
}),
|
|
59
|
+
/*#__PURE__*/ jsx("div", {
|
|
60
|
+
className: "flex w-full shrink-0 flex-col gap-2 motion-reduce:animate-none",
|
|
61
|
+
style: {
|
|
62
|
+
animation: `${isForward ? 'ds-nav-blur-in' : 'ds-nav-blur-out'} ${timing}`
|
|
63
|
+
},
|
|
64
|
+
children: /*#__PURE__*/ jsx(NavPanelContent, {
|
|
65
|
+
level: isForward ? drillLevel : transition.fromLevel
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
}
|
|
6
74
|
return /*#__PURE__*/ jsx("div", {
|
|
7
75
|
...props,
|
|
8
76
|
ref: ref,
|
|
9
77
|
"data-slot": "remote-shell-panel",
|
|
10
78
|
"data-testid": testId,
|
|
11
79
|
className: cn('[grid-area:panel] min-h-0', className),
|
|
12
|
-
children:
|
|
80
|
+
children: /*#__PURE__*/ jsx(NavPanel, {
|
|
81
|
+
resizable: resizable,
|
|
82
|
+
children: /*#__PURE__*/ jsx(NavPanelContent, {
|
|
83
|
+
level: drillLevel
|
|
84
|
+
})
|
|
85
|
+
})
|
|
13
86
|
});
|
|
14
87
|
};
|
|
15
88
|
RemoteShellPanel.displayName = 'RemoteShellPanel';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { BreadcrumbSegment, NavConfig, NavConfigDrill, NavConfigGroup, NavConfigHeaderAction, NavConfigLink, NavConfigNode, NavConfigSectionHeader, NavStackEntry, RemoteShellContextValue, } from './model';
|
|
2
|
+
export { findFirstLinkPath, pushPathname, useLocationPathname, useRemoteShellContext, } from './model';
|
|
1
3
|
export { RemoteShell, type RemoteShellProps } from './RemoteShell';
|
|
2
4
|
export { RemoteShellBreadcrumb, type RemoteShellBreadcrumbProps } from './RemoteShellBreadcrumb';
|
|
3
5
|
export { RemoteShellContent, type RemoteShellContentProps } from './RemoteShellContent';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { findFirstLinkPath, pushPathname, useLocationPathname, useRemoteShellContext } from "./model/index.js";
|
|
1
2
|
import { RemoteShell } from "./RemoteShell.js";
|
|
2
3
|
import { RemoteShellBreadcrumb } from "./RemoteShellBreadcrumb.js";
|
|
3
4
|
import { RemoteShellContent } from "./RemoteShellContent.js";
|
|
4
5
|
import { RemoteShellPanel } from "./RemoteShellPanel.js";
|
|
5
|
-
export { RemoteShell, RemoteShellBreadcrumb, RemoteShellContent, RemoteShellPanel };
|
|
6
|
+
export { RemoteShell, RemoteShellBreadcrumb, RemoteShellContent, RemoteShellPanel, findFirstLinkPath, pushPathname, useLocationPathname, useRemoteShellContext };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BreadcrumbSegment, NavConfig, NavConfigDrill, NavStackEntry } from './types';
|
|
2
|
-
export interface
|
|
2
|
+
export interface RemoteShellContextValue {
|
|
3
3
|
config: NavConfig;
|
|
4
4
|
pathname: string;
|
|
5
5
|
navStack: NavStackEntry[];
|
|
@@ -18,5 +18,5 @@ export interface ProductNavContextValue {
|
|
|
18
18
|
/** Navigate to an absolute href (used by breadcrumbs) */
|
|
19
19
|
navigateTo: (href: string) => void;
|
|
20
20
|
}
|
|
21
|
-
export declare const
|
|
22
|
-
export declare function
|
|
21
|
+
export declare const RemoteShellContextProvider: import("react").Provider<RemoteShellContextValue | null>;
|
|
22
|
+
export declare function useRemoteShellContext(): RemoteShellContextValue;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
const RemoteShellCtx = createContext(null);
|
|
3
|
+
const RemoteShellContextProvider = RemoteShellCtx.Provider;
|
|
4
|
+
function useRemoteShellContext() {
|
|
5
|
+
const ctx = useContext(RemoteShellCtx);
|
|
6
|
+
if (!ctx) throw new Error('useRemoteShellContext must be used within a RemoteShell with a config prop');
|
|
7
|
+
return ctx;
|
|
8
|
+
}
|
|
9
|
+
export { RemoteShellContextProvider, useRemoteShellContext };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type MatchNavResult, matchNav } from './matchNav';
|
|
2
|
+
export { findDrillNode, findFirstLinkPath } from './navUtils';
|
|
3
|
+
export { RemoteShellContextProvider, type RemoteShellContextValue, useRemoteShellContext, } from './RemoteShellContext';
|
|
4
|
+
export type { BreadcrumbSegment, NavConfig, NavConfigDrill, NavConfigGroup, NavConfigHeaderAction, NavConfigLink, NavConfigNode, NavConfigSectionHeader, NavStackEntry, } from './types';
|
|
5
|
+
export { pushPathname, useLocationPathname } from './useLocationPathname';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { matchNav } from "./matchNav.js";
|
|
2
|
+
import { findDrillNode, findFirstLinkPath } from "./navUtils.js";
|
|
3
|
+
import { RemoteShellContextProvider, useRemoteShellContext } from "./RemoteShellContext.js";
|
|
4
|
+
import { pushPathname, useLocationPathname } from "./useLocationPathname.js";
|
|
5
|
+
export { RemoteShellContextProvider, findDrillNode, findFirstLinkPath, matchNav, pushPathname, useLocationPathname, useRemoteShellContext };
|