@beydesign/storybook 0.2.30 → 0.3.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.
Files changed (80) hide show
  1. package/dist/index.d.ts +0 -2
  2. package/dist/index.js +0 -2
  3. package/dist/stories/{Drawer → Navigation/Drawer}/Drawer.d.ts +4 -0
  4. package/dist/stories/Navigation/Drawer/Drawer.js +81 -0
  5. package/dist/stories/{Drawer → Navigation/Drawer}/Drawer.stories.js +1 -1
  6. package/dist/stories/Navigation/Drawer/DrawerBody.d.ts +9 -0
  7. package/dist/stories/Navigation/Drawer/DrawerBody.js +73 -0
  8. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCard.js +3 -3
  9. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCard.stories.js +1 -1
  10. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCardSkeleton.js +1 -1
  11. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCardSkeleton.stories.js +1 -1
  12. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerNavItem.d.ts +1 -1
  13. package/dist/stories/Navigation/Drawer/DrawerNavItem.js +22 -0
  14. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerNavItem.stories.js +1 -1
  15. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfile.js +3 -3
  16. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfile.stories.js +1 -1
  17. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfileSkeleton.js +1 -1
  18. package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfileSkeleton.stories.js +1 -1
  19. package/dist/stories/{Drawer → Navigation/Drawer}/index.d.ts +1 -0
  20. package/dist/stories/{Drawer → Navigation/Drawer}/index.js +1 -0
  21. package/dist/stories/{Drawer → Navigation/Drawer}/types.d.ts +44 -34
  22. package/dist/stories/Navigation/Drawer/types.js +7 -0
  23. package/dist/stories/Navigation/MobileNav.d.ts +15 -0
  24. package/dist/stories/Navigation/MobileNav.js +97 -0
  25. package/dist/stories/Navigation/MobileNav.stories.d.ts +25 -0
  26. package/dist/stories/Navigation/MobileNav.stories.js +65 -0
  27. package/dist/stories/Navigation/MobileNavBar.js +3 -13
  28. package/dist/stories/Navigation/MobileNavBar.stories.js +5 -18
  29. package/dist/stories/Navigation/SettingsPage.stories.d.ts +19 -0
  30. package/dist/stories/Navigation/SettingsPage.stories.js +75 -0
  31. package/dist/stories/Navigation/SubNav/SubNav.d.ts +14 -0
  32. package/dist/stories/Navigation/SubNav/SubNav.js +16 -0
  33. package/dist/stories/{Settings/SettingsNav.stories.d.ts → Navigation/SubNav/SubNav.stories.d.ts} +3 -3
  34. package/dist/stories/{Settings/SettingsNav.stories.js → Navigation/SubNav/SubNav.stories.js} +3 -3
  35. package/dist/stories/Navigation/SubNav/SubNavBody.d.ts +10 -0
  36. package/dist/stories/Navigation/SubNav/SubNavBody.js +91 -0
  37. package/dist/stories/Navigation/SubNav/SubNavItem.d.ts +10 -0
  38. package/dist/stories/Navigation/SubNav/SubNavItem.js +12 -0
  39. package/dist/stories/Navigation/SubNav/index.d.ts +4 -0
  40. package/dist/stories/Navigation/SubNav/index.js +4 -0
  41. package/dist/stories/Navigation/SubNav/types.d.ts +54 -0
  42. package/dist/stories/Navigation/index.d.ts +3 -0
  43. package/dist/stories/Navigation/index.js +3 -0
  44. package/dist/stories/Navigation/internal/NavPanel.d.ts +21 -0
  45. package/dist/stories/Navigation/internal/NavPanel.js +45 -0
  46. package/dist/stories/Navigation/internal/NavRow.d.ts +45 -0
  47. package/dist/stories/Navigation/internal/NavRow.js +79 -0
  48. package/dist/stories/Navigation/internal/index.d.ts +11 -0
  49. package/dist/stories/Navigation/internal/index.js +11 -0
  50. package/dist/stories/Navigation/internal/types.d.ts +71 -0
  51. package/dist/stories/Navigation/internal/types.js +30 -0
  52. package/dist/stories/Navigation/navigation.fixtures.d.ts +22 -0
  53. package/dist/stories/Navigation/navigation.fixtures.js +64 -0
  54. package/dist/stories/Navigation/types.d.ts +24 -1
  55. package/package.json +1 -1
  56. package/dist/stories/Drawer/Drawer.js +0 -163
  57. package/dist/stories/Drawer/DrawerNavItem.js +0 -87
  58. package/dist/stories/Drawer/types.js +0 -29
  59. package/dist/stories/Settings/SettingsNav.d.ts +0 -10
  60. package/dist/stories/Settings/SettingsNav.js +0 -97
  61. package/dist/stories/Settings/SettingsNavItem.d.ts +0 -9
  62. package/dist/stories/Settings/SettingsNavItem.js +0 -75
  63. package/dist/stories/Settings/SettingsPage.stories.d.ts +0 -11
  64. package/dist/stories/Settings/SettingsPage.stories.js +0 -93
  65. package/dist/stories/Settings/index.d.ts +0 -3
  66. package/dist/stories/Settings/index.js +0 -3
  67. package/dist/stories/Settings/types.d.ts +0 -68
  68. /package/dist/stories/{Drawer → Navigation/Drawer}/Drawer.stories.d.ts +0 -0
  69. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCard.d.ts +0 -0
  70. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCard.stories.d.ts +0 -0
  71. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCardSkeleton.d.ts +0 -0
  72. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerCreditsCardSkeleton.stories.d.ts +0 -0
  73. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerIcons.d.ts +0 -0
  74. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerIcons.js +0 -0
  75. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerNavItem.stories.d.ts +0 -0
  76. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfile.d.ts +0 -0
  77. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfile.stories.d.ts +0 -0
  78. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfileSkeleton.d.ts +0 -0
  79. /package/dist/stories/{Drawer → Navigation/Drawer}/DrawerProfileSkeleton.stories.d.ts +0 -0
  80. /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/Drawer/Drawer',
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 '../Typography';
5
- import { Popover, ProgressIndicator, ProgressIndicatorMode } from '../UI';
6
- import { getIconComponent } from '../../utils';
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/Drawer/DrawerCreditsCard',
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 '../UI';
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/Drawer/DrawerCreditsCardSkeleton',
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. All colours resolve to theme tokens, so it adapts to light and dark.
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/Drawer/DrawerNavItem',
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 '../UI';
6
- import { Typography, TypographySize, TypographyWeight } from '../Typography';
7
- import { getIconComponent } from '../../utils';
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/Drawer/DrawerProfile',
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 '../UI';
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/Drawer/DrawerProfileSkeleton',
5
+ title: 'Design System/Components/Navigation/DrawerProfileSkeleton',
6
6
  component: DrawerProfileSkeleton,
7
7
  parameters: {
8
8
  layout: 'centered',
@@ -1,4 +1,5 @@
1
1
  export * from './Drawer';
2
+ export * from './DrawerBody';
2
3
  export * from './DrawerNavItem';
3
4
  export * from './DrawerCreditsCard';
4
5
  export * from './DrawerCreditsCardSkeleton';
@@ -1,4 +1,5 @@
1
1
  export * from './Drawer';
2
+ export * from './DrawerBody';
2
3
  export * from './DrawerNavItem';
3
4
  export * from './DrawerCreditsCard';
4
5
  export * from './DrawerCreditsCardSkeleton';
@@ -1,31 +1,13 @@
1
1
  import { CSSProperties } from 'react';
2
- import { IconName } from '../../utils';
2
+ import { IconName } from '../../../utils';
3
+ import type { NavItemHierarchy, NavItemState, NavPanelSize, SubNavConfig } from '../internal/types';
3
4
  /**
4
- * Expanded / collapsed size shared across the Drawer and its sub-components.
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 declare enum DrawerSize {
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?: DrawerNavItemState;
20
+ state?: NavItemState;
39
21
  /** Expanded (icon + label) or Collapsed (icon only). */
40
- size?: DrawerSize;
22
+ size?: NavPanelSize;
41
23
  /** Top-level (`First`) or nested (`Secondary`) item. */
42
- hierarchy?: DrawerNavItemHierarchy;
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?: DrawerSize;
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?: DrawerSize;
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?: DrawerSize;
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?: DrawerSize;
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?: DrawerNavItemHierarchy;
132
+ hierarchy?: NavItemHierarchy;
146
133
  /** Force a state. When omitted it is derived from `activeKey` / `disabled`. */
147
- state?: DrawerNavItemState;
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;