@wallarm-org/design-system 0.54.0 → 0.55.0
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/AppShell.d.ts +6 -0
- package/dist/components/AppShell/AppShell.js +22 -50
- package/dist/components/AppShell/AppShellHeader.js +1 -1
- package/dist/components/AppShell/AppShellRail.js +1 -1
- package/dist/components/AppShell/ExpandOverlay.d.ts +13 -0
- package/dist/components/AppShell/ExpandOverlay.js +36 -0
- package/dist/components/AppShell/RevealOverlay.d.ts +12 -0
- package/dist/components/AppShell/RevealOverlay.js +23 -0
- package/dist/components/AppShell/constants.d.ts +6 -0
- package/dist/components/AppShell/constants.js +7 -0
- package/dist/components/AppShell/index.d.ts +1 -1
- package/dist/components/AppShell/story-content/_storyHeaderActions.d.ts +1 -0
- package/dist/components/AppShell/story-content/_storyHeaderActions.js +75 -0
- package/dist/components/AppShell/story-content/_storyNavRailFooter.d.ts +13 -0
- package/dist/components/AppShell/story-content/_storyNavRailFooter.js +26 -0
- package/dist/components/AppShell/story-content/_storyProductNavItems.d.ts +7 -0
- package/dist/components/AppShell/story-content/_storyProductNavItems.js +49 -0
- package/dist/components/AppShell/story-content/index.d.ts +3 -0
- package/dist/components/AppShell/story-content/index.js +4 -1
- package/dist/components/AppShell/useShellAnimation.d.ts +21 -0
- package/dist/components/AppShell/useShellAnimation.js +116 -0
- package/dist/metadata/components.json +16 -3
- package/package.json +1 -1
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { type FC, type HTMLAttributes, type ReactNode, type Ref } from 'react';
|
|
2
2
|
import { type TestableProps } from '../../utils/testId';
|
|
3
|
+
export interface AppShellExpandFrom {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
borderRadius?: number;
|
|
7
|
+
}
|
|
3
8
|
export interface AppShellProps extends HTMLAttributes<HTMLDivElement>, TestableProps {
|
|
4
9
|
ref?: Ref<HTMLDivElement>;
|
|
5
10
|
children?: ReactNode;
|
|
6
11
|
reveal?: boolean;
|
|
12
|
+
expandFrom?: AppShellExpandFrom;
|
|
7
13
|
onRevealed?: () => void;
|
|
8
14
|
appeared?: boolean;
|
|
9
15
|
}
|
|
@@ -1,51 +1,20 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo, useRef } from "react";
|
|
3
3
|
import { composeRefs } from "@radix-ui/react-compose-refs";
|
|
4
4
|
import { cn } from "../../utils/cn.js";
|
|
5
5
|
import { TestIdProvider } from "../../utils/testId.js";
|
|
6
6
|
import { AppShellContext } from "./AppShellContext.js";
|
|
7
|
-
|
|
7
|
+
import { ExpandOverlay } from "./ExpandOverlay.js";
|
|
8
|
+
import { RevealOverlay } from "./RevealOverlay.js";
|
|
9
|
+
import { useShellAnimation } from "./useShellAnimation.js";
|
|
10
|
+
const AppShell = ({ ref, className, children, reveal, expandFrom, onRevealed, appeared: appearedProp, 'data-testid': testId, ...props })=>{
|
|
8
11
|
const internalRef = useRef(null);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const shell = internalRef.current;
|
|
16
|
-
if (!shell) return;
|
|
17
|
-
let cancelled = false;
|
|
18
|
-
requestAnimationFrame(()=>{
|
|
19
|
-
requestAnimationFrame(()=>{
|
|
20
|
-
if (cancelled) return;
|
|
21
|
-
const remote = shell.querySelector('[data-slot="app-shell-remote"]');
|
|
22
|
-
if (!remote) {
|
|
23
|
-
setPhase('done');
|
|
24
|
-
onRevealedRef.current?.();
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const shellRect = shell.getBoundingClientRect();
|
|
28
|
-
const remoteRect = remote.getBoundingClientRect();
|
|
29
|
-
setTargetRect({
|
|
30
|
-
top: remoteRect.top - shellRect.top,
|
|
31
|
-
left: remoteRect.left - shellRect.left
|
|
32
|
-
});
|
|
33
|
-
setPhase('revealing');
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
return ()=>{
|
|
37
|
-
cancelled = true;
|
|
38
|
-
};
|
|
39
|
-
}, [
|
|
40
|
-
phase
|
|
41
|
-
]);
|
|
42
|
-
const handleTransitionEnd = useCallback((e)=>{
|
|
43
|
-
if ('top' === e.propertyName) {
|
|
44
|
-
setPhase('done');
|
|
45
|
-
onRevealedRef.current?.();
|
|
46
|
-
}
|
|
47
|
-
}, []);
|
|
48
|
-
const appeared = appearedProp ?? 'done' === phase;
|
|
12
|
+
const { phase, targetRect, expandStyle, appeared, handleRevealOverlayTransitionEnd, handleExpandTransitionEnd } = useShellAnimation(internalRef, {
|
|
13
|
+
reveal,
|
|
14
|
+
expandFrom,
|
|
15
|
+
onRevealed,
|
|
16
|
+
appearedProp
|
|
17
|
+
});
|
|
49
18
|
const shellContext = useMemo(()=>({
|
|
50
19
|
appeared
|
|
51
20
|
}), [
|
|
@@ -61,16 +30,19 @@ const AppShell = ({ ref, className, children, reveal, onRevealed, appeared: appe
|
|
|
61
30
|
"data-slot": "app-shell",
|
|
62
31
|
"data-testid": testId,
|
|
63
32
|
className: cn('relative grid h-screen overscroll-none [grid-template-areas:"header_header""rail_remote"] [grid-template-columns:auto_1fr] [grid-template-rows:auto_1fr] bg-component-app-shell-bg', className),
|
|
33
|
+
style: expandStyle,
|
|
34
|
+
onTransitionEnd: expandFrom ? handleExpandTransitionEnd : void 0,
|
|
64
35
|
children: [
|
|
65
36
|
children,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
37
|
+
expandFrom && /*#__PURE__*/ jsx(ExpandOverlay, {
|
|
38
|
+
phase: phase,
|
|
39
|
+
expandFrom: expandFrom,
|
|
40
|
+
targetRect: targetRect
|
|
41
|
+
}),
|
|
42
|
+
!expandFrom && /*#__PURE__*/ jsx(RevealOverlay, {
|
|
43
|
+
phase: phase,
|
|
44
|
+
targetRect: targetRect,
|
|
45
|
+
onTransitionEnd: handleRevealOverlayTransitionEnd
|
|
74
46
|
})
|
|
75
47
|
]
|
|
76
48
|
})
|
|
@@ -10,7 +10,7 @@ const AppShellHeader = ({ ref, className, children, ...props })=>{
|
|
|
10
10
|
ref: ref,
|
|
11
11
|
"data-slot": "app-shell-header",
|
|
12
12
|
"data-testid": testId,
|
|
13
|
-
className: cn('[grid-area:header] transition-opacity duration-
|
|
13
|
+
className: cn('[grid-area:header] transition-opacity duration-500 ease-in-out', !appeared && 'opacity-0', className),
|
|
14
14
|
children: children
|
|
15
15
|
});
|
|
16
16
|
};
|
|
@@ -10,7 +10,7 @@ const AppShellRail = ({ ref, className, children, ...props })=>{
|
|
|
10
10
|
ref: ref,
|
|
11
11
|
"data-slot": "app-shell-rail",
|
|
12
12
|
"data-testid": testId,
|
|
13
|
-
className: cn('[grid-area:rail] transition-opacity duration-
|
|
13
|
+
className: cn('[grid-area:rail] transition-opacity duration-500 ease-in-out', !appeared && 'opacity-0', className),
|
|
14
14
|
children: children
|
|
15
15
|
});
|
|
16
16
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { AppShellExpandFrom } from './AppShell';
|
|
3
|
+
import type { RevealPhase } from './useShellAnimation';
|
|
4
|
+
interface ExpandOverlayProps {
|
|
5
|
+
phase: RevealPhase;
|
|
6
|
+
expandFrom: AppShellExpandFrom;
|
|
7
|
+
targetRect: {
|
|
8
|
+
top: number;
|
|
9
|
+
left: number;
|
|
10
|
+
} | null;
|
|
11
|
+
}
|
|
12
|
+
export declare const ExpandOverlay: FC<ExpandOverlayProps>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { EXPAND_BORDER_MS, HERO_EASE } from "./constants.js";
|
|
3
|
+
const EXPANDING_TRANSITION = [
|
|
4
|
+
'top',
|
|
5
|
+
'left',
|
|
6
|
+
'right',
|
|
7
|
+
'bottom',
|
|
8
|
+
'border-radius',
|
|
9
|
+
'border-right-width',
|
|
10
|
+
'border-bottom-width'
|
|
11
|
+
].map((prop)=>`${prop} ${EXPAND_BORDER_MS}ms ${HERO_EASE}`).join(', ');
|
|
12
|
+
const ExpandOverlay = ({ phase, expandFrom, targetRect })=>{
|
|
13
|
+
if ('initial' !== phase && 'expanding' !== phase) return null;
|
|
14
|
+
const { width, height, borderRadius = 0 } = expandFrom;
|
|
15
|
+
const isInitial = 'initial' === phase;
|
|
16
|
+
const halfW = `calc(50% - ${width / 2}px)`;
|
|
17
|
+
const halfH = `calc(50% - ${height / 2}px)`;
|
|
18
|
+
return /*#__PURE__*/ jsx("div", {
|
|
19
|
+
className: "pointer-events-none absolute z-50 border-solid border-border-primary-light",
|
|
20
|
+
style: {
|
|
21
|
+
top: isInitial ? halfH : `${targetRect?.top ?? 0}px`,
|
|
22
|
+
left: isInitial ? halfW : `${targetRect?.left ?? 0}px`,
|
|
23
|
+
right: isInitial ? halfW : 0,
|
|
24
|
+
bottom: isInitial ? halfH : 0,
|
|
25
|
+
borderTopWidth: 1,
|
|
26
|
+
borderLeftWidth: 1,
|
|
27
|
+
borderRightWidth: isInitial ? 1 : 0,
|
|
28
|
+
borderBottomWidth: isInitial ? 1 : 0,
|
|
29
|
+
borderRadius: isInitial ? borderRadius : '12px 0px 0px 0px',
|
|
30
|
+
opacity: 1,
|
|
31
|
+
transition: isInitial ? void 0 : EXPANDING_TRANSITION
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
ExpandOverlay.displayName = 'ExpandOverlay';
|
|
36
|
+
export { ExpandOverlay };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { RevealPhase } from './useShellAnimation';
|
|
3
|
+
interface RevealOverlayProps {
|
|
4
|
+
phase: RevealPhase;
|
|
5
|
+
targetRect: {
|
|
6
|
+
top: number;
|
|
7
|
+
left: number;
|
|
8
|
+
} | null;
|
|
9
|
+
onTransitionEnd: (e: React.TransitionEvent) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare const RevealOverlay: FC<RevealOverlayProps>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../utils/cn.js";
|
|
3
|
+
import { EMPHASIZED_DECEL, MORPH_MS } from "./constants.js";
|
|
4
|
+
const REVEALING_TRANSITION = [
|
|
5
|
+
'top',
|
|
6
|
+
'left',
|
|
7
|
+
'border-radius'
|
|
8
|
+
].map((prop)=>`${prop} ${MORPH_MS}ms ${EMPHASIZED_DECEL}`).join(', ');
|
|
9
|
+
const RevealOverlay = ({ phase, targetRect, onTransitionEnd })=>{
|
|
10
|
+
if ('done' === phase) return null;
|
|
11
|
+
const isRevealing = 'revealing' === phase;
|
|
12
|
+
return /*#__PURE__*/ jsx("div", {
|
|
13
|
+
className: cn('bg-bg-page-bg pointer-events-none z-50 absolute right-0 bottom-0', isRevealing && 'rounded-tl-12'),
|
|
14
|
+
style: {
|
|
15
|
+
top: isRevealing && targetRect ? targetRect.top : 0,
|
|
16
|
+
left: isRevealing && targetRect ? targetRect.left : 0,
|
|
17
|
+
transition: isRevealing ? REVEALING_TRANSITION : void 0
|
|
18
|
+
},
|
|
19
|
+
onTransitionEnd: onTransitionEnd
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
RevealOverlay.displayName = 'RevealOverlay';
|
|
23
|
+
export { RevealOverlay };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const EMPHASIZED_DECEL = "cubic-bezier(0.05, 0.7, 0.1, 1)";
|
|
2
|
+
export declare const HERO_EASE = "cubic-bezier(0.4, 0, 0.2, 1)";
|
|
3
|
+
export declare const MORPH_MS = 400;
|
|
4
|
+
export declare const EXPAND_MS = 560;
|
|
5
|
+
export declare const EXPAND_BORDER_MS = 400;
|
|
6
|
+
export declare const SKELETON_DELAY = 250;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const EMPHASIZED_DECEL = 'cubic-bezier(0.05, 0.7, 0.1, 1)';
|
|
2
|
+
const HERO_EASE = 'cubic-bezier(0.4, 0, 0.2, 1)';
|
|
3
|
+
const MORPH_MS = 400;
|
|
4
|
+
const EXPAND_MS = 560;
|
|
5
|
+
const EXPAND_BORDER_MS = 400;
|
|
6
|
+
const SKELETON_DELAY = 250;
|
|
7
|
+
export { EMPHASIZED_DECEL, EXPAND_BORDER_MS, EXPAND_MS, HERO_EASE, MORPH_MS, SKELETON_DELAY };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AppShell, type AppShellProps } from './AppShell';
|
|
1
|
+
export { AppShell, type AppShellExpandFrom, type AppShellProps } from './AppShell';
|
|
2
2
|
export { AppShellHeader, type AppShellHeaderProps } from './AppShellHeader';
|
|
3
3
|
export { AppShellRail, type AppShellRailProps } from './AppShellRail';
|
|
4
4
|
export { AppShellRemote, type AppShellRemoteProps } from './AppShellRemote';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const HeaderActions: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Bell, ChevronUpDown } from "../../../icons/index.js";
|
|
3
|
+
import { Button } from "../../Button/index.js";
|
|
4
|
+
import { Code } from "../../Code/index.js";
|
|
5
|
+
import { Kbd } from "../../Kbd/index.js";
|
|
6
|
+
import { Text } from "../../Text/index.js";
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../../Tooltip/index.js";
|
|
8
|
+
import { TopHeaderSeparator } from "../../TopHeader/index.js";
|
|
9
|
+
import { QuickHelpDropdown } from "./_storyQuickHelpDropdown.js";
|
|
10
|
+
const HeaderActions = ()=>/*#__PURE__*/ jsxs(Fragment, {
|
|
11
|
+
children: [
|
|
12
|
+
/*#__PURE__*/ jsxs(Button, {
|
|
13
|
+
variant: "ghost",
|
|
14
|
+
size: "small",
|
|
15
|
+
color: "neutral",
|
|
16
|
+
className: "p-4 gap-6 rounded-6",
|
|
17
|
+
children: [
|
|
18
|
+
/*#__PURE__*/ jsx(Code, {
|
|
19
|
+
size: "s",
|
|
20
|
+
color: "secondary",
|
|
21
|
+
children: "Search Wallarm"
|
|
22
|
+
}),
|
|
23
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
24
|
+
size: "xsmall",
|
|
25
|
+
children: "⌘ K"
|
|
26
|
+
})
|
|
27
|
+
]
|
|
28
|
+
}),
|
|
29
|
+
/*#__PURE__*/ jsx(TopHeaderSeparator, {}),
|
|
30
|
+
/*#__PURE__*/ jsxs(Button, {
|
|
31
|
+
variant: "ghost",
|
|
32
|
+
size: "small",
|
|
33
|
+
color: "neutral",
|
|
34
|
+
className: "py-4 rounded-6",
|
|
35
|
+
children: [
|
|
36
|
+
/*#__PURE__*/ jsx(Text, {
|
|
37
|
+
size: "xs",
|
|
38
|
+
weight: "medium",
|
|
39
|
+
children: "Tenant Name"
|
|
40
|
+
}),
|
|
41
|
+
/*#__PURE__*/ jsx("span", {
|
|
42
|
+
className: "text-text-tertiary mx-[-2px]",
|
|
43
|
+
children: "•"
|
|
44
|
+
}),
|
|
45
|
+
/*#__PURE__*/ jsx(Code, {
|
|
46
|
+
size: "s",
|
|
47
|
+
color: "secondary",
|
|
48
|
+
children: "12345"
|
|
49
|
+
}),
|
|
50
|
+
/*#__PURE__*/ jsx(ChevronUpDown, {
|
|
51
|
+
className: "!icon-sm"
|
|
52
|
+
})
|
|
53
|
+
]
|
|
54
|
+
}),
|
|
55
|
+
/*#__PURE__*/ jsxs(Tooltip, {
|
|
56
|
+
children: [
|
|
57
|
+
/*#__PURE__*/ jsx(TooltipTrigger, {
|
|
58
|
+
asChild: true,
|
|
59
|
+
children: /*#__PURE__*/ jsx(Button, {
|
|
60
|
+
variant: "ghost",
|
|
61
|
+
size: "small",
|
|
62
|
+
color: "neutral",
|
|
63
|
+
"aria-label": "Wallarm Updates",
|
|
64
|
+
children: /*#__PURE__*/ jsx(Bell, {})
|
|
65
|
+
})
|
|
66
|
+
}),
|
|
67
|
+
/*#__PURE__*/ jsx(TooltipContent, {
|
|
68
|
+
children: "Wallarm updates"
|
|
69
|
+
})
|
|
70
|
+
]
|
|
71
|
+
}),
|
|
72
|
+
/*#__PURE__*/ jsx(QuickHelpDropdown, {})
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
export { HeaderActions };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { Theme } from '../../ThemeProvider';
|
|
3
|
+
import { type SidebarMode } from './_storyAccountDropdown';
|
|
4
|
+
import { type Product } from './_storyLib';
|
|
5
|
+
interface NavRailFooterContentProps {
|
|
6
|
+
activeProduct: Product;
|
|
7
|
+
sidebarMode: SidebarMode;
|
|
8
|
+
onSidebarModeChange: (mode: SidebarMode) => void;
|
|
9
|
+
theme: Theme;
|
|
10
|
+
onThemeChange: (theme: Theme) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const NavRailFooterContent: FC<NavRailFooterContentProps>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Settings } from "../../../icons/index.js";
|
|
3
|
+
import { NavRailFooter, NavRailItem } from "../../NavRail/index.js";
|
|
4
|
+
import { AccountDropdown } from "./_storyAccountDropdown.js";
|
|
5
|
+
import { navigateToProduct } from "./_storyLib.js";
|
|
6
|
+
const NavRailFooterContent = ({ activeProduct, sidebarMode, onSidebarModeChange, theme, onThemeChange })=>/*#__PURE__*/ jsxs(NavRailFooter, {
|
|
7
|
+
children: [
|
|
8
|
+
/*#__PURE__*/ jsx(NavRailItem, {
|
|
9
|
+
icon: Settings,
|
|
10
|
+
label: "Settings",
|
|
11
|
+
shortcut: [
|
|
12
|
+
'G',
|
|
13
|
+
'S'
|
|
14
|
+
],
|
|
15
|
+
active: 'settings' === activeProduct,
|
|
16
|
+
onClick: ()=>navigateToProduct('settings')
|
|
17
|
+
}),
|
|
18
|
+
/*#__PURE__*/ jsx(AccountDropdown, {
|
|
19
|
+
sidebarMode: sidebarMode,
|
|
20
|
+
onSidebarModeChange: onSidebarModeChange,
|
|
21
|
+
theme: theme,
|
|
22
|
+
onThemeChange: onThemeChange
|
|
23
|
+
})
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
export { NavRailFooterContent };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CircleDashed } from "../../../icons/index.js";
|
|
3
|
+
import { NavRailItem } from "../../NavRail/index.js";
|
|
4
|
+
import { navigateToProduct } from "./_storyLib.js";
|
|
5
|
+
const ProductNavItems = ({ activeProduct })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
6
|
+
children: [
|
|
7
|
+
/*#__PURE__*/ jsx(NavRailItem, {
|
|
8
|
+
icon: CircleDashed,
|
|
9
|
+
label: "Edge",
|
|
10
|
+
shortcut: [
|
|
11
|
+
'G',
|
|
12
|
+
'E'
|
|
13
|
+
],
|
|
14
|
+
active: 'edge' === activeProduct,
|
|
15
|
+
onClick: ()=>navigateToProduct('edge')
|
|
16
|
+
}),
|
|
17
|
+
/*#__PURE__*/ jsx(NavRailItem, {
|
|
18
|
+
icon: CircleDashed,
|
|
19
|
+
label: "AI Hypervisor",
|
|
20
|
+
shortcut: [
|
|
21
|
+
'G',
|
|
22
|
+
'A'
|
|
23
|
+
],
|
|
24
|
+
active: 'ai-hypervisor' === activeProduct,
|
|
25
|
+
onClick: ()=>navigateToProduct('ai-hypervisor')
|
|
26
|
+
}),
|
|
27
|
+
/*#__PURE__*/ jsx(NavRailItem, {
|
|
28
|
+
icon: CircleDashed,
|
|
29
|
+
label: "Infra Discovery",
|
|
30
|
+
shortcut: [
|
|
31
|
+
'G',
|
|
32
|
+
'I'
|
|
33
|
+
],
|
|
34
|
+
active: 'infra-discovery' === activeProduct,
|
|
35
|
+
onClick: ()=>navigateToProduct('infra-discovery')
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ jsx(NavRailItem, {
|
|
38
|
+
icon: CircleDashed,
|
|
39
|
+
label: "Security Testing",
|
|
40
|
+
shortcut: [
|
|
41
|
+
'G',
|
|
42
|
+
'T'
|
|
43
|
+
],
|
|
44
|
+
active: 'security-testing' === activeProduct,
|
|
45
|
+
onClick: ()=>navigateToProduct('security-testing')
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
export { ProductNavItems };
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export { AccountDropdown, type SidebarMode } from './_storyAccountDropdown';
|
|
2
2
|
export { RemoteForProduct } from './_storyConfigRenderer';
|
|
3
|
+
export { HeaderActions } from './_storyHeaderActions';
|
|
3
4
|
export { HomeContent } from './_storyHomeContent';
|
|
4
5
|
export { deriveProduct, navigateToProduct } from './_storyLib';
|
|
5
6
|
export { aiHypervisorNavConfig, edgeNavConfig, infraDiscoveryNavConfig, securityTestingNavConfig, settingsNavConfig, } from './_storyNavConfigs';
|
|
7
|
+
export { NavRailFooterContent } from './_storyNavRailFooter';
|
|
8
|
+
export { ProductNavItems } from './_storyProductNavItems';
|
|
6
9
|
export { QuickHelpDropdown } from './_storyQuickHelpDropdown';
|
|
7
10
|
export { RecentDropdown } from './_storyRecentDropdown';
|
|
8
11
|
export { WallarmLogo } from './_storyWallarmLogo';
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { AccountDropdown } from "./_storyAccountDropdown.js";
|
|
2
2
|
import { RemoteForProduct } from "./_storyConfigRenderer.js";
|
|
3
|
+
import { HeaderActions } from "./_storyHeaderActions.js";
|
|
3
4
|
import { HomeContent } from "./_storyHomeContent.js";
|
|
4
5
|
import { deriveProduct, navigateToProduct } from "./_storyLib.js";
|
|
5
6
|
import { aiHypervisorNavConfig, edgeNavConfig, infraDiscoveryNavConfig, securityTestingNavConfig, settingsNavConfig } from "./_storyNavConfigs.js";
|
|
7
|
+
import { NavRailFooterContent } from "./_storyNavRailFooter.js";
|
|
8
|
+
import { ProductNavItems } from "./_storyProductNavItems.js";
|
|
6
9
|
import { QuickHelpDropdown } from "./_storyQuickHelpDropdown.js";
|
|
7
10
|
import { RecentDropdown } from "./_storyRecentDropdown.js";
|
|
8
11
|
import { WallarmLogo } from "./_storyWallarmLogo.js";
|
|
9
|
-
export { AccountDropdown, HomeContent, QuickHelpDropdown, RecentDropdown, RemoteForProduct, WallarmLogo, aiHypervisorNavConfig, deriveProduct, edgeNavConfig, infraDiscoveryNavConfig, navigateToProduct, securityTestingNavConfig, settingsNavConfig };
|
|
12
|
+
export { AccountDropdown, HeaderActions, HomeContent, NavRailFooterContent, ProductNavItems, QuickHelpDropdown, RecentDropdown, RemoteForProduct, WallarmLogo, aiHypervisorNavConfig, deriveProduct, edgeNavConfig, infraDiscoveryNavConfig, navigateToProduct, securityTestingNavConfig, settingsNavConfig };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { AppShellExpandFrom } from './AppShell';
|
|
3
|
+
export type RevealPhase = 'initial' | 'expanding' | 'revealing' | 'done';
|
|
4
|
+
interface UseShellAnimationOptions {
|
|
5
|
+
reveal?: boolean;
|
|
6
|
+
expandFrom?: AppShellExpandFrom;
|
|
7
|
+
onRevealed?: () => void;
|
|
8
|
+
appearedProp?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function useShellAnimation(shellRef: RefObject<HTMLDivElement | null>, options: UseShellAnimationOptions): {
|
|
11
|
+
phase: RevealPhase;
|
|
12
|
+
targetRect: {
|
|
13
|
+
top: number;
|
|
14
|
+
left: number;
|
|
15
|
+
} | null;
|
|
16
|
+
expandStyle: import("react").CSSProperties | undefined;
|
|
17
|
+
appeared: boolean;
|
|
18
|
+
handleRevealOverlayTransitionEnd: (e: React.TransitionEvent) => void;
|
|
19
|
+
handleExpandTransitionEnd: (e: React.TransitionEvent) => void;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { EXPAND_BORDER_MS, EXPAND_MS, HERO_EASE, SKELETON_DELAY } from "./constants.js";
|
|
3
|
+
function queryRemoteRect(shell) {
|
|
4
|
+
const remote = shell.querySelector('[data-slot="app-shell-remote"]');
|
|
5
|
+
if (!remote) return null;
|
|
6
|
+
const shellRect = shell.getBoundingClientRect();
|
|
7
|
+
const remoteRect = remote.getBoundingClientRect();
|
|
8
|
+
return {
|
|
9
|
+
top: remoteRect.top - shellRect.top,
|
|
10
|
+
left: remoteRect.left - shellRect.left
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function computeExpandStyle(expandFrom, phase, targetRect) {
|
|
14
|
+
const { width, height, borderRadius = 0 } = expandFrom;
|
|
15
|
+
switch(phase){
|
|
16
|
+
case 'initial':
|
|
17
|
+
return {
|
|
18
|
+
clipPath: `inset(calc(50% - ${height / 2}px) calc(50% - ${width / 2}px) round ${borderRadius}px)`
|
|
19
|
+
};
|
|
20
|
+
case 'expanding':
|
|
21
|
+
if (targetRect) return {
|
|
22
|
+
clipPath: `inset(${targetRect.top}px 0px 0px ${targetRect.left}px round 12px 0px 0px 0px)`,
|
|
23
|
+
transition: `clip-path ${EXPAND_BORDER_MS}ms ${HERO_EASE}`
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
clipPath: 'inset(0 0 round 0px)',
|
|
27
|
+
transition: `clip-path ${EXPAND_BORDER_MS}ms ${HERO_EASE}`
|
|
28
|
+
};
|
|
29
|
+
default:
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function useShellAnimation(shellRef, options) {
|
|
34
|
+
const { reveal, expandFrom, onRevealed, appearedProp } = options;
|
|
35
|
+
const onRevealedRef = useRef(onRevealed);
|
|
36
|
+
onRevealedRef.current = onRevealed;
|
|
37
|
+
const hasAnimation = Boolean(expandFrom || reveal);
|
|
38
|
+
const [phase, setPhase] = useState(hasAnimation ? 'initial' : 'done');
|
|
39
|
+
const [targetRect, setTargetRect] = useState(null);
|
|
40
|
+
useEffect(()=>{
|
|
41
|
+
if ('initial' !== phase) return;
|
|
42
|
+
let cancelled = false;
|
|
43
|
+
const scheduleDoubleRaf = (callback)=>{
|
|
44
|
+
requestAnimationFrame(()=>{
|
|
45
|
+
requestAnimationFrame(()=>{
|
|
46
|
+
if (!cancelled) callback();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
scheduleDoubleRaf(expandFrom ? ()=>{
|
|
51
|
+
const shell = shellRef.current;
|
|
52
|
+
if (shell) {
|
|
53
|
+
const rect = queryRemoteRect(shell);
|
|
54
|
+
if (rect) setTargetRect(rect);
|
|
55
|
+
}
|
|
56
|
+
setPhase('expanding');
|
|
57
|
+
} : ()=>{
|
|
58
|
+
const shell = shellRef.current;
|
|
59
|
+
if (!shell) return;
|
|
60
|
+
const rect = queryRemoteRect(shell);
|
|
61
|
+
if (!rect) {
|
|
62
|
+
setPhase('done');
|
|
63
|
+
onRevealedRef.current?.();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
setTargetRect(rect);
|
|
67
|
+
setPhase('revealing');
|
|
68
|
+
});
|
|
69
|
+
return ()=>{
|
|
70
|
+
cancelled = true;
|
|
71
|
+
};
|
|
72
|
+
}, [
|
|
73
|
+
phase,
|
|
74
|
+
expandFrom,
|
|
75
|
+
shellRef
|
|
76
|
+
]);
|
|
77
|
+
const handleRevealOverlayTransitionEnd = useCallback((e)=>{
|
|
78
|
+
if ('top' === e.propertyName) {
|
|
79
|
+
setPhase('done');
|
|
80
|
+
onRevealedRef.current?.();
|
|
81
|
+
}
|
|
82
|
+
}, []);
|
|
83
|
+
const handleExpandTransitionEnd = useCallback((e)=>{
|
|
84
|
+
if ('clip-path' === e.propertyName) {
|
|
85
|
+
setPhase('done');
|
|
86
|
+
onRevealedRef.current?.();
|
|
87
|
+
}
|
|
88
|
+
}, []);
|
|
89
|
+
const expandStyle = useMemo(()=>expandFrom ? computeExpandStyle(expandFrom, phase, targetRect) : void 0, [
|
|
90
|
+
expandFrom,
|
|
91
|
+
phase,
|
|
92
|
+
targetRect
|
|
93
|
+
]);
|
|
94
|
+
const isDone = 'done' === phase;
|
|
95
|
+
const [appearedDelayed, setAppearedDelayed] = useState(!hasAnimation);
|
|
96
|
+
useEffect(()=>{
|
|
97
|
+
if (!isDone || !hasAnimation) return;
|
|
98
|
+
const delay = expandFrom ? EXPAND_MS - EXPAND_BORDER_MS : SKELETON_DELAY;
|
|
99
|
+
const id = setTimeout(()=>setAppearedDelayed(true), delay);
|
|
100
|
+
return ()=>clearTimeout(id);
|
|
101
|
+
}, [
|
|
102
|
+
isDone,
|
|
103
|
+
hasAnimation,
|
|
104
|
+
expandFrom
|
|
105
|
+
]);
|
|
106
|
+
const appeared = appearedProp ?? (hasAnimation ? appearedDelayed : true);
|
|
107
|
+
return {
|
|
108
|
+
phase,
|
|
109
|
+
targetRect,
|
|
110
|
+
expandStyle,
|
|
111
|
+
appeared,
|
|
112
|
+
handleRevealOverlayTransitionEnd,
|
|
113
|
+
handleExpandTransitionEnd
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export { useShellAnimation };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"generatedAt": "2026-06-
|
|
2
|
+
"version": "0.54.0",
|
|
3
|
+
"generatedAt": "2026-06-04T22:49:52.517Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Accordion",
|
|
@@ -3724,6 +3724,11 @@
|
|
|
3724
3724
|
"type": "boolean | undefined",
|
|
3725
3725
|
"required": false
|
|
3726
3726
|
},
|
|
3727
|
+
{
|
|
3728
|
+
"name": "expandFrom",
|
|
3729
|
+
"type": "AppShellExpandFrom | undefined",
|
|
3730
|
+
"required": false
|
|
3731
|
+
},
|
|
3727
3732
|
{
|
|
3728
3733
|
"name": "appeared",
|
|
3729
3734
|
"type": "boolean | undefined",
|
|
@@ -4831,7 +4836,15 @@
|
|
|
4831
4836
|
"examples": [
|
|
4832
4837
|
{
|
|
4833
4838
|
"name": "Basic",
|
|
4834
|
-
"code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [loading, setLoading] = useState(true);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const
|
|
4839
|
+
"code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [loading, setLoading] = useState(true);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const { theme, setTheme } = useTheme();\n const collapsed = sidebarMode === 'adaptive' && activeProduct !== 'home';\n\n return (\n <AppShell>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n {loading ? (\n <>\n <Skeleton width='150px' height='20px' rounded={6} />\n <TopHeaderSeparator />\n <Skeleton width='150px' height='20px' rounded={6} />\n\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant='ghost'\n size='small'\n color='neutral'\n aria-label='Wallarm Updates'\n >\n <Bell />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Wallarm updates</TooltipContent>\n </Tooltip>\n\n <QuickHelpDropdown />\n </>\n ) : (\n <HeaderActions />\n )}\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail collapsed={collapsed}>\n <NavRailBody>\n <NavRailItem\n icon={Home}\n label='Home'\n shortcut={['G', 'H']}\n active={activeProduct === 'home'}\n onClick={() => navigateToProduct('home')}\n />\n <RecentDropdown />\n\n <NavRailSeparator />\n\n {loading ? <NavRailSkeleton /> : <ProductNavItems activeProduct={activeProduct} />}\n </NavRailBody>\n\n <NavRailFooterContent\n activeProduct={activeProduct}\n sidebarMode={sidebarMode}\n onSidebarModeChange={setSidebarMode}\n theme={theme}\n onThemeChange={setTheme}\n />\n </NavRail>\n </AppShellRail>\n\n <AppShellRemote>\n <div className='flex gap-8 absolute top-4 right-4 z-10'>\n <Button variant='ghost' size='small' color='neutral' onClick={() => setLoading(v => !v)}>\n {loading ? 'Finish loading' : 'Start loading'}\n </Button>\n </div>\n\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n );\n}"
|
|
4840
|
+
},
|
|
4841
|
+
{
|
|
4842
|
+
"name": "RevealFlow",
|
|
4843
|
+
"code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [splashDone, setSplashDone] = useState(false);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const [revealKey, setRevealKey] = useState(0);\n const { theme, setTheme } = useTheme();\n const collapsed = sidebarMode === 'adaptive' && activeProduct !== 'home';\n\n useEffect(() => {\n if (splashDone) return;\n const timer = setTimeout(() => setSplashDone(true), 2000);\n return () => clearTimeout(timer);\n }, [splashDone]);\n\n const handleReplay = () => {\n setSplashDone(false);\n setRevealKey(k => k + 1);\n };\n\n if (!splashDone) {\n return (\n <div key={revealKey} className='h-screen w-screen bg-bg-page-bg'>\n <SplashScreen />\n </div>\n );\n }\n\n return (\n <AppShell key={revealKey} reveal>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n <HeaderActions />\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail collapsed={collapsed}>\n <NavRailBody>\n <NavRailItem\n icon={Home}\n label='Home'\n shortcut={['G', 'H']}\n active={activeProduct === 'home'}\n onClick={() => navigateToProduct('home')}\n />\n <RecentDropdown />\n\n <NavRailSeparator />\n\n <ProductNavItems activeProduct={activeProduct} />\n </NavRailBody>\n\n <NavRailFooterContent\n activeProduct={activeProduct}\n sidebarMode={sidebarMode}\n onSidebarModeChange={setSidebarMode}\n theme={theme}\n onThemeChange={setTheme}\n />\n </NavRail>\n </AppShellRail>\n\n <AppShellRemote>\n <div className='flex gap-8 absolute top-4 right-4 z-10'>\n <Button variant='ghost' size='small' color='neutral' onClick={handleReplay}>\n Replay animation\n </Button>\n </div>\n\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n );\n}"
|
|
4844
|
+
},
|
|
4845
|
+
{
|
|
4846
|
+
"name": "LoginFlow",
|
|
4847
|
+
"code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [splashVisible, setSplashVisible] = useState(true);\n const [showShell, setShowShell] = useState(false);\n const [revealed, setRevealed] = useState(false);\n const [flowKey, setFlowKey] = useState(0);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const { theme, setTheme } = useTheme();\n const collapsed = sidebarMode === 'adaptive' && activeProduct !== 'home';\n\n useEffect(() => {\n if (!splashVisible) return;\n const timer = setTimeout(() => setSplashVisible(false), 2000);\n return () => clearTimeout(timer);\n }, [splashVisible]);\n\n const handleSignIn = () => {\n setShowShell(true);\n };\n\n const handleReplay = () => {\n setShowShell(false);\n setRevealed(false);\n setSplashVisible(true);\n setFlowKey(k => k + 1);\n };\n\n return (\n <div\n key={flowKey}\n className='relative h-screen w-screen overflow-hidden bg-component-app-shell-bg'\n >\n {!revealed && (\n <AnimatedBackground\n className='absolute inset-0'\n style={{\n opacity: showShell ? 0 : 1,\n transition: showShell ? 'opacity 400ms cubic-bezier(0.4, 0, 0.2, 1)' : undefined,\n }}\n />\n )}\n\n {!showShell && (\n <div className='absolute inset-0 flex items-center justify-center z-10'>\n <SplashScreen\n visible={splashVisible}\n shrinkTarget={CARD_DIMENSIONS}\n className='bg-bg-page-bg shadow-lg'\n >\n <div className='flex h-full w-full flex-col items-center justify-center gap-16 p-24'>\n <Text size='xl'>Sign In</Text>\n <div className='flex w-full flex-col gap-12'>\n <Input placeholder='Email' />\n <Input placeholder='Password' type='password' />\n </div>\n <Button variant='primary' color='brand' className='w-full' onClick={handleSignIn}>\n Sign In\n </Button>\n </div>\n </SplashScreen>\n </div>\n )}\n\n {showShell && (\n <div className='absolute inset-0'>\n <AppShell expandFrom={CARD_DIMENSIONS} onRevealed={() => setRevealed(true)}>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n <HeaderActions />\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail collapsed={collapsed}>\n <NavRailBody>\n <NavRailItem\n icon={Home}\n label='Home'\n active={activeProduct === 'home'}\n onClick={() => navigateToProduct('home')}\n />\n\n <NavRailSeparator />\n\n <ProductNavItems activeProduct={activeProduct} />\n </NavRailBody>\n <NavRailFooterContent\n activeProduct={activeProduct}\n sidebarMode={sidebarMode}\n onSidebarModeChange={setSidebarMode}\n theme={theme}\n onThemeChange={setTheme}\n />\n </NavRail>\n </AppShellRail>\n\n <AppShellRemote>\n <div className='flex gap-8 absolute top-4 right-4 z-10'>\n <Button variant='ghost' size='small' color='neutral' onClick={handleReplay}>\n Replay animation\n </Button>\n </div>\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n </div>\n )}\n </div>\n );\n}"
|
|
4835
4848
|
}
|
|
4836
4849
|
]
|
|
4837
4850
|
},
|