@beydesign/storybook 0.2.30 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/stories/{Drawer → Navigation/Drawer}/Drawer.d.ts +4 -0
- package/dist/stories/Navigation/Drawer/Drawer.js +81 -0
- package/dist/stories/{Drawer → Navigation/Drawer}/Drawer.stories.js +1 -1
- package/dist/stories/Navigation/Drawer/DrawerBody.d.ts +9 -0
- package/dist/stories/Navigation/Drawer/DrawerBody.js +73 -0
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCard.js +3 -3
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCard.stories.js +1 -1
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCardSkeleton.js +1 -1
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCardSkeleton.stories.js +1 -1
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerNavItem.d.ts +1 -1
- package/dist/stories/Navigation/Drawer/DrawerNavItem.js +22 -0
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerNavItem.stories.js +1 -1
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfile.js +3 -3
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfile.stories.js +1 -1
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfileSkeleton.js +1 -1
- package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfileSkeleton.stories.js +1 -1
- package/dist/stories/{Drawer → Navigation/Drawer}/index.d.ts +1 -0
- package/dist/stories/{Drawer → Navigation/Drawer}/index.js +1 -0
- package/dist/stories/{Drawer → Navigation/Drawer}/types.d.ts +44 -34
- package/dist/stories/Navigation/Drawer/types.js +7 -0
- package/dist/stories/Navigation/MobileNav.d.ts +15 -0
- package/dist/stories/Navigation/MobileNav.js +97 -0
- package/dist/stories/Navigation/MobileNav.stories.d.ts +25 -0
- package/dist/stories/Navigation/MobileNav.stories.js +65 -0
- package/dist/stories/Navigation/MobileNavBar.js +3 -13
- package/dist/stories/Navigation/MobileNavBar.stories.js +5 -18
- package/dist/stories/Navigation/SettingsPage.stories.d.ts +19 -0
- package/dist/stories/Navigation/SettingsPage.stories.js +75 -0
- package/dist/stories/Navigation/SubNav/SubNav.d.ts +14 -0
- package/dist/stories/Navigation/SubNav/SubNav.js +16 -0
- package/dist/stories/{Settings/SettingsNav.stories.d.ts → Navigation/SubNav/SubNav.stories.d.ts} +3 -3
- package/dist/stories/{Settings/SettingsNav.stories.js → Navigation/SubNav/SubNav.stories.js} +3 -3
- package/dist/stories/Navigation/SubNav/SubNavBody.d.ts +10 -0
- package/dist/stories/Navigation/SubNav/SubNavBody.js +91 -0
- package/dist/stories/Navigation/SubNav/SubNavItem.d.ts +10 -0
- package/dist/stories/Navigation/SubNav/SubNavItem.js +12 -0
- package/dist/stories/Navigation/SubNav/index.d.ts +4 -0
- package/dist/stories/Navigation/SubNav/index.js +4 -0
- package/dist/stories/Navigation/SubNav/types.d.ts +54 -0
- package/dist/stories/Navigation/index.d.ts +3 -0
- package/dist/stories/Navigation/index.js +3 -0
- package/dist/stories/Navigation/internal/NavPanel.d.ts +21 -0
- package/dist/stories/Navigation/internal/NavPanel.js +45 -0
- package/dist/stories/Navigation/internal/NavRow.d.ts +45 -0
- package/dist/stories/Navigation/internal/NavRow.js +79 -0
- package/dist/stories/Navigation/internal/index.d.ts +11 -0
- package/dist/stories/Navigation/internal/index.js +11 -0
- package/dist/stories/Navigation/internal/types.d.ts +71 -0
- package/dist/stories/Navigation/internal/types.js +30 -0
- package/dist/stories/Navigation/navigation.fixtures.d.ts +22 -0
- package/dist/stories/Navigation/navigation.fixtures.js +64 -0
- package/dist/stories/Navigation/types.d.ts +24 -1
- package/dist/stories/UI/PromoBanner.js +2 -2
- package/dist/stories/UI/types.d.ts +4 -0
- package/package.json +1 -1
- package/dist/stories/Drawer/Drawer.js +0 -163
- package/dist/stories/Drawer/DrawerNavItem.js +0 -87
- package/dist/stories/Drawer/types.js +0 -29
- package/dist/stories/Settings/SettingsNav.d.ts +0 -10
- package/dist/stories/Settings/SettingsNav.js +0 -97
- package/dist/stories/Settings/SettingsNavItem.d.ts +0 -9
- package/dist/stories/Settings/SettingsNavItem.js +0 -75
- package/dist/stories/Settings/SettingsPage.stories.d.ts +0 -11
- package/dist/stories/Settings/SettingsPage.stories.js +0 -93
- package/dist/stories/Settings/index.d.ts +0 -3
- package/dist/stories/Settings/index.js +0 -3
- package/dist/stories/Settings/types.d.ts +0 -68
- /package/dist/stories/{Drawer → Navigation/Drawer}/Drawer.stories.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCard.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCard.stories.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCardSkeleton.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCardSkeleton.stories.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerIcons.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerIcons.js +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerNavItem.stories.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfile.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfile.stories.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfileSkeleton.d.ts +0 -0
- /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfileSkeleton.stories.d.ts +0 -0
- /package/dist/stories/{Settings → Navigation/SubNav}/types.js +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,6 @@ export * from './stories/Buttons';
|
|
|
8
8
|
export * from './stories/Typography';
|
|
9
9
|
export * from './stories/Credits';
|
|
10
10
|
export * from './stories/Navigation';
|
|
11
|
-
export * from './stories/Drawer';
|
|
12
|
-
export * from './stories/Settings';
|
|
13
11
|
export * from './stories/Typography';
|
|
14
12
|
export * from './stories/UI';
|
|
15
13
|
export * from './stories/StripeProduct';
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,6 @@ export * from './stories/Buttons';
|
|
|
8
8
|
export * from './stories/Typography';
|
|
9
9
|
export * from './stories/Credits';
|
|
10
10
|
export * from './stories/Navigation';
|
|
11
|
-
export * from './stories/Drawer';
|
|
12
|
-
export * from './stories/Settings';
|
|
13
11
|
export * from './stories/Typography';
|
|
14
12
|
export * from './stories/UI';
|
|
15
13
|
export * from './stories/StripeProduct';
|
|
@@ -6,5 +6,9 @@ import { DrawerProps } from './types';
|
|
|
6
6
|
* toggle. Collapse state can be controlled (`collapsed` + `onToggle`) or left
|
|
7
7
|
* uncontrolled (`defaultCollapsed`). Every colour, space and radius resolves to
|
|
8
8
|
* `@beydesign/tokens` CSS variables, so it renders correctly in light and dark.
|
|
9
|
+
*
|
|
10
|
+
* Below the mobile breakpoint there is no room for this beside a settings
|
|
11
|
+
* sub-nav — use `MobileNav`, which renders the same {@link DrawerBody} as one
|
|
12
|
+
* pane of a single overlay.
|
|
9
13
|
*/
|
|
10
14
|
export declare const Drawer: React.FC<DrawerProps>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import styled from '@emotion/styled';
|
|
4
|
+
import { DrawerSize } from './types';
|
|
5
|
+
import { DrawerBody } from './DrawerBody';
|
|
6
|
+
import { NavPanel } from '../internal';
|
|
7
|
+
import { getIconComponent } from '../../../utils';
|
|
8
|
+
const EXPANDED_WIDTH = '238px';
|
|
9
|
+
const COLLAPSED_WIDTH = '68px';
|
|
10
|
+
/** Composed from the semantic `shadow-sm` design token. */
|
|
11
|
+
const SHADOW_SM = 'var(--global-shadows-shadow-sm-offset-x) var(--global-shadows-shadow-sm-offset-y) var(--global-shadows-shadow-sm-blur) var(--global-shadows-shadow-sm-spread) var(--global-shadows-shadow-sm-color)';
|
|
12
|
+
/**
|
|
13
|
+
* Non-clipping outer shell. The toggle lives here (a sibling of the panel) so
|
|
14
|
+
* it can overhang the right edge without being clipped by the panel's
|
|
15
|
+
* `overflow`.
|
|
16
|
+
*/
|
|
17
|
+
const Shell = styled.aside `
|
|
18
|
+
position: relative;
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
height: 100vh;
|
|
21
|
+
width: ${({ $collapsed }) => ($collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH)};
|
|
22
|
+
transition: width 0.3s ease-in-out;
|
|
23
|
+
`;
|
|
24
|
+
const ToggleButton = styled.button `
|
|
25
|
+
appearance: none;
|
|
26
|
+
position: absolute;
|
|
27
|
+
top: 52px;
|
|
28
|
+
right: -16px;
|
|
29
|
+
z-index: 2;
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
width: 32px;
|
|
34
|
+
height: 32px;
|
|
35
|
+
padding: 0;
|
|
36
|
+
border: 1px solid var(--color-border-border-subtle);
|
|
37
|
+
border-radius: var(--spacing-tokens-size-in-px-radius-radius-full);
|
|
38
|
+
background-color: var(--color-background-bg-element);
|
|
39
|
+
color: var(--color-text-text-paragraph);
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
box-shadow: ${SHADOW_SM};
|
|
42
|
+
transition: color 0.2s ease-in-out;
|
|
43
|
+
|
|
44
|
+
&:hover {
|
|
45
|
+
color: var(--color-text-text-clicable);
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
/** Rotates the chevron smoothly between expanded (◀) and collapsed (▶). */
|
|
49
|
+
const ToggleIcon = styled.span `
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
transform: rotate(${({ $collapsed }) => ($collapsed ? '180deg' : '0deg')});
|
|
52
|
+
transition: transform 0.3s ease-in-out;
|
|
53
|
+
|
|
54
|
+
@media (prefers-reduced-motion: reduce) {
|
|
55
|
+
transition: none;
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
/**
|
|
59
|
+
* Composed application sidebar ("Drawer"): logo header, navigation list,
|
|
60
|
+
* credits card and user-profile footer, with a floating collapse/expand
|
|
61
|
+
* toggle. Collapse state can be controlled (`collapsed` + `onToggle`) or left
|
|
62
|
+
* uncontrolled (`defaultCollapsed`). Every colour, space and radius resolves to
|
|
63
|
+
* `@beydesign/tokens` CSS variables, so it renders correctly in light and dark.
|
|
64
|
+
*
|
|
65
|
+
* Below the mobile breakpoint there is no room for this beside a settings
|
|
66
|
+
* sub-nav — use `MobileNav`, which renders the same {@link DrawerBody} as one
|
|
67
|
+
* pane of a single overlay.
|
|
68
|
+
*/
|
|
69
|
+
export const Drawer = ({ navItems, activeKey, collapsed, defaultCollapsed = false, onToggle, showToggle = true, credits, creditsLoading = false, profile, profileLoading = false, logo, className, style, testId, }) => {
|
|
70
|
+
const isControlled = collapsed !== undefined;
|
|
71
|
+
const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed);
|
|
72
|
+
const isCollapsed = isControlled ? collapsed : internalCollapsed;
|
|
73
|
+
const drawerSize = isCollapsed ? DrawerSize.Collapsed : DrawerSize.Expanded;
|
|
74
|
+
const handleToggle = () => {
|
|
75
|
+
const next = !isCollapsed;
|
|
76
|
+
if (!isControlled)
|
|
77
|
+
setInternalCollapsed(next);
|
|
78
|
+
onToggle?.(next);
|
|
79
|
+
};
|
|
80
|
+
return (_jsxs(Shell, { "$collapsed": isCollapsed, className: className, style: style, "data-testid": testId, children: [_jsx(NavPanel, { children: _jsx(DrawerBody, { navItems: navItems, activeKey: activeKey, size: drawerSize, credits: credits, creditsLoading: creditsLoading, profile: profile, profileLoading: profileLoading, logo: logo }) }), showToggle && (_jsx(ToggleButton, { type: "button", onClick: handleToggle, "aria-label": isCollapsed ? 'Expand sidebar' : 'Collapse sidebar', children: _jsx(ToggleIcon, { "$collapsed": isCollapsed, children: getIconComponent('CaretLeft', { size: 16, weight: 'bold' }) }) }))] }));
|
|
81
|
+
};
|
|
@@ -17,7 +17,7 @@ const navItems = [
|
|
|
17
17
|
{ key: 'developers', text: 'Developers', icon: 'BracketsCurly' },
|
|
18
18
|
];
|
|
19
19
|
const meta = {
|
|
20
|
-
title: 'Design System/Components/
|
|
20
|
+
title: 'Design System/Components/Navigation/Drawer',
|
|
21
21
|
component: Drawer,
|
|
22
22
|
parameters: {
|
|
23
23
|
layout: 'fullscreen',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DrawerBodyProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* The Drawer's content — logo header, navigation list, credits card and profile
|
|
5
|
+
* footer — without any panel chrome. `Drawer` wraps it in a `NavPanel` beside a
|
|
6
|
+
* collapse toggle; `MobileNav` renders the very same body as the root pane of
|
|
7
|
+
* its overlay, which is why the two never drift apart.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DrawerBody: React.FC<DrawerBodyProps>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { DrawerNavItemHierarchy, DrawerNavItemState, DrawerSize, } from './types';
|
|
4
|
+
import { DrawerNavItem } from './DrawerNavItem';
|
|
5
|
+
import { DrawerCreditsCard } from './DrawerCreditsCard';
|
|
6
|
+
import { DrawerCreditsCardSkeleton } from './DrawerCreditsCardSkeleton';
|
|
7
|
+
import { DrawerProfile } from './DrawerProfile';
|
|
8
|
+
import { DrawerProfileSkeleton } from './DrawerProfileSkeleton';
|
|
9
|
+
import { Logo, LogoVariant } from '../../UI';
|
|
10
|
+
const LogoHeader = styled.div `
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: ${({ $collapsed }) => $collapsed ? 'center' : 'flex-start'};
|
|
14
|
+
height: 38px;
|
|
15
|
+
min-height: 38px;
|
|
16
|
+
margin-bottom: 40px;
|
|
17
|
+
`;
|
|
18
|
+
const Nav = styled.nav `
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
flex: 1;
|
|
22
|
+
justify-content: space-between;
|
|
23
|
+
gap: var(--spacing-tokens-size-in-px-spacing-spacing-2xl, 24px);
|
|
24
|
+
`;
|
|
25
|
+
const NavTop = styled.div `
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
gap: var(--spacing-tokens-size-in-px-spacing-spacing-lg, 12px);
|
|
29
|
+
`;
|
|
30
|
+
const Footer = styled.div `
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
gap: var(--spacing-tokens-size-in-px-spacing-spacing-2xl, 24px);
|
|
34
|
+
`;
|
|
35
|
+
const FooterSlot = styled.div `
|
|
36
|
+
display: flex;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
`;
|
|
39
|
+
const resolveState = (item, activeKey) => {
|
|
40
|
+
if (item.state)
|
|
41
|
+
return item.state;
|
|
42
|
+
if (item.disabled)
|
|
43
|
+
return DrawerNavItemState.Disabled;
|
|
44
|
+
const key = item.key ?? item.text;
|
|
45
|
+
if (activeKey !== undefined && key === activeKey) {
|
|
46
|
+
return DrawerNavItemState.Active;
|
|
47
|
+
}
|
|
48
|
+
return DrawerNavItemState.Inactive;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The Drawer's content — logo header, navigation list, credits card and profile
|
|
52
|
+
* footer — without any panel chrome. `Drawer` wraps it in a `NavPanel` beside a
|
|
53
|
+
* collapse toggle; `MobileNav` renders the very same body as the root pane of
|
|
54
|
+
* its overlay, which is why the two never drift apart.
|
|
55
|
+
*/
|
|
56
|
+
export const DrawerBody = ({ navItems, activeKey, size = DrawerSize.Expanded, credits, creditsLoading = false, profile, profileLoading = false, logo, onSubNavOpen, }) => {
|
|
57
|
+
const isCollapsed = size === DrawerSize.Collapsed;
|
|
58
|
+
const handleItemClick = (item) => {
|
|
59
|
+
if (item.disabled)
|
|
60
|
+
return;
|
|
61
|
+
if (item.subNav && onSubNavOpen) {
|
|
62
|
+
onSubNavOpen(item);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (item.link) {
|
|
66
|
+
window.location.href = item.link;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
item.onClick?.();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return (_jsxs(_Fragment, { children: [_jsx(LogoHeader, { "$collapsed": isCollapsed, children: _jsx(Logo, { variant: isCollapsed ? LogoVariant.Icon : LogoVariant.Horizontal, color: logo?.color ?? 'var(--color-foreground-fg-brand-logo)', gradient: logo?.gradient, svgUrl: logo?.svgUrl }) }), _jsxs(Nav, { children: [_jsx(NavTop, { children: navItems.map((item) => (_jsx(DrawerNavItem, { text: item.text, icon: item.icon, state: resolveState(item, activeKey), size: size, hierarchy: item.hierarchy ?? DrawerNavItemHierarchy.First, showBadge: item.showBadge, badgeText: item.badgeText, showSubNavCaret: Boolean(item.subNav && onSubNavOpen), onClick: () => handleItemClick(item) }, item.key ?? item.text))) }), (credits || creditsLoading || profile || profileLoading) && (_jsxs(Footer, { children: [(credits || creditsLoading) && (_jsx(FooterSlot, { children: creditsLoading ? (_jsx(DrawerCreditsCardSkeleton, { size: size })) : (_jsx(DrawerCreditsCard, { ...credits, size: size })) })), (profile || profileLoading) && (_jsx(FooterSlot, { children: profileLoading ? (_jsx(DrawerProfileSkeleton, { size: size })) : (profile && _jsx(DrawerProfile, { ...profile, size: size })) }))] }))] })] }));
|
|
73
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import styled from '@emotion/styled';
|
|
3
3
|
import { DrawerSize } from './types';
|
|
4
|
-
import { Typography, TypographySize, TypographyWeight } from '
|
|
5
|
-
import { Popover, ProgressIndicator, ProgressIndicatorMode } from '
|
|
6
|
-
import { getIconComponent } from '
|
|
4
|
+
import { Typography, TypographySize, TypographyWeight } from '../../Typography';
|
|
5
|
+
import { Popover, ProgressIndicator, ProgressIndicatorMode } from '../../UI';
|
|
6
|
+
import { getIconComponent } from '../../../utils';
|
|
7
7
|
import { GemIcon } from './DrawerIcons';
|
|
8
8
|
const DEFAULT_INFO_LINES = [
|
|
9
9
|
'Managed Agents 1 Min = 100 Credits',
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { DrawerCreditsCard } from './DrawerCreditsCard';
|
|
3
3
|
import { DrawerSize } from './types';
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'Design System/Components/
|
|
5
|
+
title: 'Design System/Components/Navigation/DrawerCreditsCard',
|
|
6
6
|
component: DrawerCreditsCard,
|
|
7
7
|
parameters: {
|
|
8
8
|
layout: 'centered',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import styled from '@emotion/styled';
|
|
3
|
-
import { SkeletonLoader } from '
|
|
3
|
+
import { SkeletonLoader } from '../../UI';
|
|
4
4
|
import { DrawerSize } from './types';
|
|
5
5
|
/** Mirrors {@link DrawerCreditsCard}'s Card so the skeleton snaps in place. */
|
|
6
6
|
const Card = styled.div `
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { DrawerCreditsCardSkeleton } from './DrawerCreditsCardSkeleton';
|
|
3
3
|
import { DrawerSize } from './types';
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'Design System/Components/
|
|
5
|
+
title: 'Design System/Components/Navigation/DrawerCreditsCardSkeleton',
|
|
6
6
|
component: DrawerCreditsCardSkeleton,
|
|
7
7
|
parameters: {
|
|
8
8
|
layout: 'centered',
|
|
@@ -3,6 +3,6 @@ import { DrawerNavItemProps } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* A single navigation row for the {@link Drawer}. Supports active / inactive /
|
|
5
5
|
* hover / disabled states, first / secondary hierarchy and expanded / collapsed
|
|
6
|
-
* sizes
|
|
6
|
+
* sizes — a top-level configuration of the shared {@link NavRow}.
|
|
7
7
|
*/
|
|
8
8
|
export declare const DrawerNavItem: React.FC<DrawerNavItemProps>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { DrawerNavItemState, DrawerNavItemHierarchy, DrawerSize, } from './types';
|
|
4
|
+
import { NavRow } from '../internal';
|
|
5
|
+
import { TypographySize, TypographyWeight } from '../../Typography';
|
|
6
|
+
import { getIconComponent } from '../../../utils';
|
|
7
|
+
const FIRST_PADDING = 'var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px)';
|
|
8
|
+
const SECONDARY_PADDING = 'var(--spacing-tokens-size-in-px-spacing-spacing-xs, 4px) var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px)';
|
|
9
|
+
const Caret = styled.span `
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
flex-shrink: 0;
|
|
12
|
+
color: inherit;
|
|
13
|
+
`;
|
|
14
|
+
/**
|
|
15
|
+
* A single navigation row for the {@link Drawer}. Supports active / inactive /
|
|
16
|
+
* hover / disabled states, first / secondary hierarchy and expanded / collapsed
|
|
17
|
+
* sizes — a top-level configuration of the shared {@link NavRow}.
|
|
18
|
+
*/
|
|
19
|
+
export const DrawerNavItem = ({ text, icon, state = DrawerNavItemState.Inactive, size = DrawerSize.Expanded, hierarchy = DrawerNavItemHierarchy.First, showBadge = false, badgeText = 'Coming Soon', showSubNavCaret = false, onClick, className, style, testId, }) => {
|
|
20
|
+
const isSecondary = hierarchy === DrawerNavItemHierarchy.Secondary;
|
|
21
|
+
return (_jsx(NavRow, { text: text, icon: icon, state: state, collapsed: size === DrawerSize.Collapsed, iconSize: 20, typographySize: isSecondary ? TypographySize.TextXS : TypographySize.TextS, typographyWeight: isSecondary ? TypographyWeight.Medium : TypographyWeight.SemiBold, padding: isSecondary ? SECONDARY_PADDING : FIRST_PADDING, showBadge: showBadge, badgeText: badgeText, trailing: showSubNavCaret ? (_jsx(Caret, { children: getIconComponent('CaretRight', { size: 16 }) })) : undefined, onClick: onClick, className: className, style: style, testId: testId }));
|
|
22
|
+
};
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { DrawerNavItem } from './DrawerNavItem';
|
|
3
3
|
import { DrawerNavItemState, DrawerNavItemHierarchy, DrawerSize, } from './types';
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'Design System/Components/
|
|
5
|
+
title: 'Design System/Components/Navigation/DrawerNavItem',
|
|
6
6
|
component: DrawerNavItem,
|
|
7
7
|
parameters: {
|
|
8
8
|
layout: 'centered',
|
|
@@ -2,9 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import styled from '@emotion/styled';
|
|
4
4
|
import { DrawerSize } from './types';
|
|
5
|
-
import { Avatar, AvatarSize, AvatarStyle } from '
|
|
6
|
-
import { Typography, TypographySize, TypographyWeight } from '
|
|
7
|
-
import { getIconComponent } from '
|
|
5
|
+
import { Avatar, AvatarSize, AvatarStyle } from '../../UI';
|
|
6
|
+
import { Typography, TypographySize, TypographyWeight } from '../../Typography';
|
|
7
|
+
import { getIconComponent } from '../../../utils';
|
|
8
8
|
const Container = styled.div `
|
|
9
9
|
display: flex;
|
|
10
10
|
flex-direction: column;
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { DrawerProfile } from './DrawerProfile';
|
|
3
3
|
import { DrawerSize } from './types';
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'Design System/Components/
|
|
5
|
+
title: 'Design System/Components/Navigation/DrawerProfile',
|
|
6
6
|
component: DrawerProfile,
|
|
7
7
|
parameters: {
|
|
8
8
|
layout: 'centered',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import styled from '@emotion/styled';
|
|
3
|
-
import { SkeletonLoader } from '
|
|
3
|
+
import { SkeletonLoader } from '../../UI';
|
|
4
4
|
import { DrawerSize } from './types';
|
|
5
5
|
/**
|
|
6
6
|
* Mirrors {@link DrawerProfile}'s clickable Row so the skeleton snaps in place:
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { DrawerProfileSkeleton } from './DrawerProfileSkeleton';
|
|
3
3
|
import { DrawerSize } from './types';
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'Design System/Components/
|
|
5
|
+
title: 'Design System/Components/Navigation/DrawerProfileSkeleton',
|
|
6
6
|
component: DrawerProfileSkeleton,
|
|
7
7
|
parameters: {
|
|
8
8
|
layout: 'centered',
|
|
@@ -1,31 +1,13 @@
|
|
|
1
1
|
import { CSSProperties } from 'react';
|
|
2
|
-
import { IconName } from '
|
|
2
|
+
import { IconName } from '../../../utils';
|
|
3
|
+
import type { NavItemHierarchy, NavItemState, NavPanelSize, SubNavConfig } from '../internal/types';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* The canonical declarations live in `../internal/types` so that the shared
|
|
6
|
+
* `NavRow` and `NavPanel` primitives can consume them without depending on the
|
|
7
|
+
* Drawer. They are re-exported here under their established public names.
|
|
8
|
+
* (`SubNavConfig` and friends are re-exported from `../SubNav`, their own home.)
|
|
5
9
|
*/
|
|
6
|
-
export
|
|
7
|
-
Expanded = "Expanded",
|
|
8
|
-
Collapsed = "Collapsed"
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Visual state of a single navigation item.
|
|
12
|
-
* `Hover` is mostly useful for documentation/Storybook — at runtime an
|
|
13
|
-
* `Inactive` item also picks up the hover styling on real pointer hover.
|
|
14
|
-
*/
|
|
15
|
-
export declare enum DrawerNavItemState {
|
|
16
|
-
Active = "Active",
|
|
17
|
-
Inactive = "Inactive",
|
|
18
|
-
Hover = "Hover",
|
|
19
|
-
Disabled = "Disabled"
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Hierarchy of a navigation item. `First` is a top-level entry (36px row),
|
|
23
|
-
* `Secondary` is a nested sub-item (28px row, lighter weight).
|
|
24
|
-
*/
|
|
25
|
-
export declare enum DrawerNavItemHierarchy {
|
|
26
|
-
First = "First",
|
|
27
|
-
Secondary = "Secondary"
|
|
28
|
-
}
|
|
10
|
+
export { NavItemState as DrawerNavItemState, NavPanelSize as DrawerSize, NavItemHierarchy as DrawerNavItemHierarchy, } from '../internal/types';
|
|
29
11
|
/**
|
|
30
12
|
* Props for the standalone {@link DrawerNavItem} component.
|
|
31
13
|
*/
|
|
@@ -35,15 +17,20 @@ export interface DrawerNavItemProps {
|
|
|
35
17
|
/** Phosphor icon name rendered to the left of the label. */
|
|
36
18
|
icon: IconName;
|
|
37
19
|
/** Visual state of the item. */
|
|
38
|
-
state?:
|
|
20
|
+
state?: NavItemState;
|
|
39
21
|
/** Expanded (icon + label) or Collapsed (icon only). */
|
|
40
|
-
size?:
|
|
22
|
+
size?: NavPanelSize;
|
|
41
23
|
/** Top-level (`First`) or nested (`Secondary`) item. */
|
|
42
|
-
hierarchy?:
|
|
24
|
+
hierarchy?: NavItemHierarchy;
|
|
43
25
|
/** Show a trailing badge (e.g. "Coming Soon"). Hidden when collapsed. */
|
|
44
26
|
showBadge?: boolean;
|
|
45
27
|
/** Text for the trailing badge. */
|
|
46
28
|
badgeText?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Render a trailing caret marking this item as opening a sub-navigation.
|
|
31
|
+
* Hidden when collapsed.
|
|
32
|
+
*/
|
|
33
|
+
showSubNavCaret?: boolean;
|
|
47
34
|
/** Click handler. Not fired when the item is disabled. */
|
|
48
35
|
onClick?: () => void;
|
|
49
36
|
className?: string;
|
|
@@ -56,7 +43,7 @@ export interface DrawerNavItemProps {
|
|
|
56
43
|
*/
|
|
57
44
|
export interface DrawerCreditsCardProps {
|
|
58
45
|
/** Expanded (full card) or Collapsed (icon only, details in a popover). */
|
|
59
|
-
size?:
|
|
46
|
+
size?: NavPanelSize;
|
|
60
47
|
/** Credits consumed so far. */
|
|
61
48
|
usedCredits?: number;
|
|
62
49
|
/** Total credits available in the plan. */
|
|
@@ -82,7 +69,7 @@ export interface DrawerCreditsCardProps {
|
|
|
82
69
|
*/
|
|
83
70
|
export interface DrawerCreditsCardSkeletonProps {
|
|
84
71
|
/** Expanded (full card) or Collapsed (icon-only box). */
|
|
85
|
-
size?:
|
|
72
|
+
size?: NavPanelSize;
|
|
86
73
|
className?: string;
|
|
87
74
|
style?: CSSProperties;
|
|
88
75
|
testId?: string;
|
|
@@ -103,7 +90,7 @@ export interface DrawerProfileMenuItem {
|
|
|
103
90
|
*/
|
|
104
91
|
export interface DrawerProfileProps {
|
|
105
92
|
/** Expanded (avatar + text) or Collapsed (avatar only). */
|
|
106
|
-
size?:
|
|
93
|
+
size?: NavPanelSize;
|
|
107
94
|
/** Display name. */
|
|
108
95
|
name: string;
|
|
109
96
|
/** Secondary line, usually the email. */
|
|
@@ -129,7 +116,7 @@ export interface DrawerProfileProps {
|
|
|
129
116
|
*/
|
|
130
117
|
export interface DrawerProfileSkeletonProps {
|
|
131
118
|
/** Expanded (avatar + text lines) or Collapsed (avatar circle only). */
|
|
132
|
-
size?:
|
|
119
|
+
size?: NavPanelSize;
|
|
133
120
|
className?: string;
|
|
134
121
|
style?: CSSProperties;
|
|
135
122
|
testId?: string;
|
|
@@ -142,15 +129,24 @@ export interface DrawerNavItemConfig {
|
|
|
142
129
|
key?: string;
|
|
143
130
|
text: string;
|
|
144
131
|
icon: IconName;
|
|
145
|
-
hierarchy?:
|
|
132
|
+
hierarchy?: NavItemHierarchy;
|
|
146
133
|
/** Force a state. When omitted it is derived from `activeKey` / `disabled`. */
|
|
147
|
-
state?:
|
|
134
|
+
state?: NavItemState;
|
|
148
135
|
disabled?: boolean;
|
|
149
136
|
showBadge?: boolean;
|
|
150
137
|
badgeText?: string;
|
|
151
138
|
/** When set, clicking navigates to this URL. */
|
|
152
139
|
link?: string;
|
|
153
140
|
onClick?: () => void;
|
|
141
|
+
/**
|
|
142
|
+
* A secondary navigation this item drills into (e.g. the Settings sub-nav).
|
|
143
|
+
*
|
|
144
|
+
* On desktop the item navigates as usual and the sub-nav is rendered as its
|
|
145
|
+
* own panel beside the Drawer (`SubNav`). Where there is only room for
|
|
146
|
+
* one panel — `MobileNav` — the item instead grows a trailing caret and
|
|
147
|
+
* pushes this config as a second pane.
|
|
148
|
+
*/
|
|
149
|
+
subNav?: SubNavConfig;
|
|
154
150
|
}
|
|
155
151
|
/**
|
|
156
152
|
* Props for the composed {@link Drawer} component.
|
|
@@ -186,3 +182,17 @@ export interface DrawerProps {
|
|
|
186
182
|
style?: CSSProperties;
|
|
187
183
|
testId?: string;
|
|
188
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Props for the {@link DrawerBody} — the Drawer's content without any panel
|
|
187
|
+
* chrome, so it can be dropped into the `MobileNav` overlay as its root pane.
|
|
188
|
+
*/
|
|
189
|
+
export interface DrawerBodyProps extends Pick<DrawerProps, 'navItems' | 'activeKey' | 'credits' | 'creditsLoading' | 'profile' | 'profileLoading' | 'logo'> {
|
|
190
|
+
/** Expanded (icon + label) or Collapsed (icon only). */
|
|
191
|
+
size?: NavPanelSize;
|
|
192
|
+
/**
|
|
193
|
+
* When provided, items carrying a `subNav` render a trailing caret and call
|
|
194
|
+
* this instead of navigating. The desktop `Drawer` omits it, so those items
|
|
195
|
+
* behave like any other nav entry.
|
|
196
|
+
*/
|
|
197
|
+
onSubNavOpen?: (item: DrawerNavItemConfig) => void;
|
|
198
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical declarations live in `../internal/types` so that the shared
|
|
3
|
+
* `NavRow` and `NavPanel` primitives can consume them without depending on the
|
|
4
|
+
* Drawer. They are re-exported here under their established public names.
|
|
5
|
+
* (`SubNavConfig` and friends are re-exported from `../SubNav`, their own home.)
|
|
6
|
+
*/
|
|
7
|
+
export { NavItemState as DrawerNavItemState, NavPanelSize as DrawerSize, NavItemHierarchy as DrawerNavItemHierarchy, } from '../internal/types';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MobileNavProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* The single navigation panel for mobile, where there is no room for the
|
|
5
|
+
* `Drawer` and a settings sub-nav side by side. It slides in over the page and
|
|
6
|
+
* holds both as pushable panes: the root pane is the very same {@link DrawerBody}
|
|
7
|
+
* the desktop `Drawer` renders, and any nav item carrying a `subNav` grows a
|
|
8
|
+
* trailing caret that pushes that config as a {@link SubNavBody} pane with
|
|
9
|
+
* a back button. Nothing is duplicated — only the chrome around the bodies
|
|
10
|
+
* changes between desktop and mobile.
|
|
11
|
+
*
|
|
12
|
+
* `MobileNavBar` renders this behind its hamburger; use it directly if the app
|
|
13
|
+
* already has its own mobile header.
|
|
14
|
+
*/
|
|
15
|
+
export declare const MobileNav: React.FC<MobileNavProps>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
3
|
+
import { Drawer as MuiDrawer } from '@mui/material';
|
|
4
|
+
import styled from '@emotion/styled';
|
|
5
|
+
import { keyframes } from '@emotion/react';
|
|
6
|
+
import { DrawerBody } from './Drawer';
|
|
7
|
+
import { SubNavBody } from './SubNav';
|
|
8
|
+
import { NavPanel } from './internal';
|
|
9
|
+
const DEFAULT_WIDTH = '315px';
|
|
10
|
+
const slideForward = keyframes `
|
|
11
|
+
from { transform: translateX(16px); opacity: 0; }
|
|
12
|
+
to { transform: translateX(0); opacity: 1; }
|
|
13
|
+
`;
|
|
14
|
+
const slideBack = keyframes `
|
|
15
|
+
from { transform: translateX(-16px); opacity: 0; }
|
|
16
|
+
to { transform: translateX(0); opacity: 1; }
|
|
17
|
+
`;
|
|
18
|
+
/**
|
|
19
|
+
* Hosts one pane at a time, growing to fill the panel so the body's own flex
|
|
20
|
+
* chain (logo header, then a nav that pushes the footer down) still works.
|
|
21
|
+
*
|
|
22
|
+
* Keep the default `min-height: auto`: on a short viewport the pane must refuse
|
|
23
|
+
* to shrink below its content, otherwise the panel scrolls to a height that
|
|
24
|
+
* excludes the profile footer and clips it.
|
|
25
|
+
*/
|
|
26
|
+
const Pane = styled.div `
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
flex: 1 0 auto;
|
|
30
|
+
animation: ${({ $forward }) => ($forward ? slideForward : slideBack)} 0.25s
|
|
31
|
+
ease-out;
|
|
32
|
+
|
|
33
|
+
@media (prefers-reduced-motion: reduce) {
|
|
34
|
+
animation: none;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
/**
|
|
38
|
+
* The single navigation panel for mobile, where there is no room for the
|
|
39
|
+
* `Drawer` and a settings sub-nav side by side. It slides in over the page and
|
|
40
|
+
* holds both as pushable panes: the root pane is the very same {@link DrawerBody}
|
|
41
|
+
* the desktop `Drawer` renders, and any nav item carrying a `subNav` grows a
|
|
42
|
+
* trailing caret that pushes that config as a {@link SubNavBody} pane with
|
|
43
|
+
* a back button. Nothing is duplicated — only the chrome around the bodies
|
|
44
|
+
* changes between desktop and mobile.
|
|
45
|
+
*
|
|
46
|
+
* `MobileNavBar` renders this behind its hamburger; use it directly if the app
|
|
47
|
+
* already has its own mobile header.
|
|
48
|
+
*/
|
|
49
|
+
export const MobileNav = ({ open, onOpenChange, navItems, activeKey, credits, creditsLoading, profile, profileLoading, logo, width = DEFAULT_WIDTH, className, style, testId, }) => {
|
|
50
|
+
const [subNav, setSubNav] = useState(null);
|
|
51
|
+
const close = useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
52
|
+
/** Navigating away should dismiss the overlay, whichever pane it happened in. */
|
|
53
|
+
const closingNavItems = useMemo(() => navItems.map((item) => ({
|
|
54
|
+
...item,
|
|
55
|
+
onClick: () => {
|
|
56
|
+
item.onClick?.();
|
|
57
|
+
close();
|
|
58
|
+
},
|
|
59
|
+
})), [navItems, close]);
|
|
60
|
+
const closingSubNav = useMemo(() => {
|
|
61
|
+
if (!subNav)
|
|
62
|
+
return null;
|
|
63
|
+
return {
|
|
64
|
+
...subNav,
|
|
65
|
+
sections: subNav.sections.map((section) => ({
|
|
66
|
+
...section,
|
|
67
|
+
items: section.items.map((item) => ({
|
|
68
|
+
...item,
|
|
69
|
+
onClick: () => {
|
|
70
|
+
item.onClick?.();
|
|
71
|
+
close();
|
|
72
|
+
},
|
|
73
|
+
})),
|
|
74
|
+
})),
|
|
75
|
+
};
|
|
76
|
+
}, [subNav, close]);
|
|
77
|
+
const handleSubNavOpen = (item) => {
|
|
78
|
+
if (item.subNav)
|
|
79
|
+
setSubNav(item.subNav);
|
|
80
|
+
};
|
|
81
|
+
return (_jsx(MuiDrawer, { anchor: "left", open: open, onClose: close, className: className,
|
|
82
|
+
/* Reset to the root pane only once the panel is fully out of sight. */
|
|
83
|
+
SlideProps: { onExited: () => setSubNav(null) }, PaperProps: {
|
|
84
|
+
sx: {
|
|
85
|
+
width,
|
|
86
|
+
backgroundColor: 'transparent',
|
|
87
|
+
boxShadow: 'none',
|
|
88
|
+
border: 'none',
|
|
89
|
+
overflow: 'hidden',
|
|
90
|
+
},
|
|
91
|
+
style,
|
|
92
|
+
}, sx: {
|
|
93
|
+
'& .MuiBackdrop-root': {
|
|
94
|
+
backgroundColor: 'var(--color-background-bg-overlay-2)',
|
|
95
|
+
},
|
|
96
|
+
}, "data-testid": testId, children: _jsx(NavPanel, { "$borderColor": "var(--color-border-border-divider)", children: closingSubNav ? (_jsx(Pane, { "$forward": true, children: _jsx(SubNavBody, { title: closingSubNav.title, sections: closingSubNav.sections, activeKey: closingSubNav.activeKey, onBack: () => setSubNav(null) }) }, "sub-nav")) : (_jsx(Pane, { "$forward": false, children: _jsx(DrawerBody, { navItems: closingNavItems, activeKey: activeKey, credits: credits, creditsLoading: creditsLoading, profile: profile, profileLoading: profileLoading, logo: logo, onSubNavOpen: handleSubNavOpen }) }, "root")) }) }));
|
|
97
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { MobileNav } from './MobileNav';
|
|
3
|
+
/**
|
|
4
|
+
* Drives the overlay the way an app does: a trigger button, and `open` state
|
|
5
|
+
* the component reports back through `onOpenChange`.
|
|
6
|
+
*/
|
|
7
|
+
declare const MobileNavExample: ({ navItems, activeKey, ...rest }: Partial<React.ComponentProps<typeof MobileNav>>) => import("react").JSX.Element;
|
|
8
|
+
declare const meta: Meta<typeof MobileNavExample>;
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof MobileNavExample>;
|
|
11
|
+
/**
|
|
12
|
+
* The root pane. "Settings" carries a `subNav`, so it renders a trailing caret
|
|
13
|
+
* and pushes instead of navigating.
|
|
14
|
+
*/
|
|
15
|
+
export declare const Default: Story;
|
|
16
|
+
/**
|
|
17
|
+
* Without a `subNav` anywhere in `navItems` there is nothing to drill into, so
|
|
18
|
+
* no item shows a caret and the overlay is a plain single-pane nav.
|
|
19
|
+
*/
|
|
20
|
+
export declare const WithoutSubNav: Story;
|
|
21
|
+
/**
|
|
22
|
+
* A sub-nav is not a settings-only idea — any item can declare one. Here the
|
|
23
|
+
* developer entry drills into a subset of the same sections.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CustomSubNav: Story;
|