@beydesign/storybook 0.2.3 → 0.2.6

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 (41) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/stories/Drawer/Drawer.d.ts +10 -0
  4. package/dist/stories/Drawer/Drawer.js +161 -0
  5. package/dist/stories/Drawer/Drawer.stories.d.ts +9 -0
  6. package/dist/stories/Drawer/Drawer.stories.js +107 -0
  7. package/dist/stories/Drawer/DrawerCreditsCard.d.ts +9 -0
  8. package/dist/stories/Drawer/DrawerCreditsCard.js +143 -0
  9. package/dist/stories/Drawer/DrawerCreditsCard.stories.d.ts +8 -0
  10. package/dist/stories/Drawer/DrawerCreditsCard.stories.js +45 -0
  11. package/dist/stories/Drawer/DrawerIcons.d.ts +14 -0
  12. package/dist/stories/Drawer/DrawerIcons.js +7 -0
  13. package/dist/stories/Drawer/DrawerNavItem.d.ts +8 -0
  14. package/dist/stories/Drawer/DrawerNavItem.js +87 -0
  15. package/dist/stories/Drawer/DrawerNavItem.stories.d.ts +11 -0
  16. package/dist/stories/Drawer/DrawerNavItem.stories.js +82 -0
  17. package/dist/stories/Drawer/DrawerProfile.d.ts +10 -0
  18. package/dist/stories/Drawer/DrawerProfile.js +170 -0
  19. package/dist/stories/Drawer/DrawerProfile.stories.d.ts +9 -0
  20. package/dist/stories/Drawer/DrawerProfile.stories.js +60 -0
  21. package/dist/stories/Drawer/index.d.ts +6 -0
  22. package/dist/stories/Drawer/index.js +6 -0
  23. package/dist/stories/Drawer/types.d.ts +164 -0
  24. package/dist/stories/Drawer/types.js +29 -0
  25. package/dist/stories/Form/TextInput.js +9 -3
  26. package/dist/stories/Form/TextInput.stories.d.ts +23 -0
  27. package/dist/stories/Form/TextInput.stories.js +33 -0
  28. package/dist/stories/Form/types.d.ts +7 -0
  29. package/dist/stories/UI/Menu.js +1 -1
  30. package/dist/stories/UI/Popover.d.ts +10 -0
  31. package/dist/stories/UI/Popover.js +66 -0
  32. package/dist/stories/UI/Popover.stories.d.ts +7 -0
  33. package/dist/stories/UI/Popover.stories.js +62 -0
  34. package/dist/stories/UI/ProgressIndicator.js +7 -3
  35. package/dist/stories/UI/ProgressIndicator.stories.d.ts +1 -0
  36. package/dist/stories/UI/ProgressIndicator.stories.js +9 -0
  37. package/dist/stories/UI/index.d.ts +1 -0
  38. package/dist/stories/UI/index.js +1 -0
  39. package/dist/stories/UI/types.d.ts +49 -2
  40. package/dist/stories/UI/types.js +10 -0
  41. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './stories/Buttons';
7
7
  export * from './stories/Typography';
8
8
  export * from './stories/Credits';
9
9
  export * from './stories/Navigation';
10
+ export * from './stories/Drawer';
10
11
  export * from './stories/Typography';
11
12
  export * from './stories/UI';
12
13
  export * from './stories/StripeProduct';
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export * from './stories/Buttons';
7
7
  export * from './stories/Typography';
8
8
  export * from './stories/Credits';
9
9
  export * from './stories/Navigation';
10
+ export * from './stories/Drawer';
10
11
  export * from './stories/Typography';
11
12
  export * from './stories/UI';
12
13
  export * from './stories/StripeProduct';
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { DrawerProps } from './types';
3
+ /**
4
+ * Composed application sidebar ("Drawer"): logo header, navigation list,
5
+ * credits card and user-profile footer, with a floating collapse/expand
6
+ * toggle. Collapse state can be controlled (`collapsed` + `onToggle`) or left
7
+ * uncontrolled (`defaultCollapsed`). Every colour, space and radius resolves to
8
+ * `@beydesign/tokens` CSS variables, so it renders correctly in light and dark.
9
+ */
10
+ export declare const Drawer: React.FC<DrawerProps>;
@@ -0,0 +1,161 @@
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, DrawerNavItemState, DrawerNavItemHierarchy, } from './types';
5
+ import { DrawerNavItem } from './DrawerNavItem';
6
+ import { DrawerCreditsCard } from './DrawerCreditsCard';
7
+ import { DrawerProfile } from './DrawerProfile';
8
+ import { Logo, LogoVariant } from '../UI';
9
+ import { getIconComponent } from '../../utils';
10
+ const EXPANDED_WIDTH = '238px';
11
+ const COLLAPSED_WIDTH = '68px';
12
+ /** Composed from the semantic `shadow-sm` design token. */
13
+ 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)';
14
+ /**
15
+ * Non-clipping outer shell. The toggle lives here (a sibling of the scroll
16
+ * area) so it can overhang the right edge without being clipped by the panel's
17
+ * `overflow`.
18
+ */
19
+ const Shell = styled.aside `
20
+ position: relative;
21
+ flex-shrink: 0;
22
+ height: 100vh;
23
+ width: ${({ $collapsed }) => ($collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH)};
24
+ transition: width 0.3s ease-in-out;
25
+ `;
26
+ const Scroll = styled.div `
27
+ box-sizing: border-box;
28
+ display: flex;
29
+ flex-direction: column;
30
+ height: 100%;
31
+ width: 100%;
32
+ padding: var(--spacing-tokens-size-in-px-spacing-spacing-xl, 16px)
33
+ var(--spacing-tokens-size-in-px-spacing-spacing-xl, 16px)
34
+ var(--spacing-tokens-size-in-px-spacing-spacing-2xl, 24px);
35
+ background-color: var(--color-background-bg-page-contrast);
36
+ border-right: 1px solid var(--color-border-border-subtle);
37
+ overflow-x: hidden;
38
+ overflow-y: auto;
39
+
40
+ /* Theme-aware thin scrollbar */
41
+ scrollbar-width: thin;
42
+ scrollbar-color: var(--color-border-border-component) transparent;
43
+
44
+ &::-webkit-scrollbar {
45
+ width: 6px;
46
+ }
47
+ &::-webkit-scrollbar-track {
48
+ background: transparent;
49
+ }
50
+ &::-webkit-scrollbar-thumb {
51
+ background-color: var(--color-border-border-component);
52
+ border-radius: var(--spacing-tokens-size-in-px-radius-radius-full);
53
+ }
54
+ &::-webkit-scrollbar-thumb:hover {
55
+ background-color: var(--color-text-text-subtle);
56
+ }
57
+ `;
58
+ const LogoHeader = styled.div `
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: ${({ $collapsed }) => $collapsed ? 'center' : 'flex-start'};
62
+ height: 38px;
63
+ min-height: 38px;
64
+ margin-bottom: 40px;
65
+ `;
66
+ const Nav = styled.nav `
67
+ display: flex;
68
+ flex-direction: column;
69
+ flex: 1;
70
+ justify-content: space-between;
71
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-2xl, 24px);
72
+ `;
73
+ const NavTop = styled.div `
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-lg, 12px);
77
+ `;
78
+ const Footer = styled.div `
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-2xl, 24px);
82
+ `;
83
+ const FooterSlot = styled.div `
84
+ display: flex;
85
+ justify-content: center;
86
+ `;
87
+ const ToggleButton = styled.button `
88
+ appearance: none;
89
+ position: absolute;
90
+ top: 52px;
91
+ right: -16px;
92
+ z-index: 2;
93
+ display: inline-flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ width: 32px;
97
+ height: 32px;
98
+ padding: 0;
99
+ border: 1px solid var(--color-border-border-subtle);
100
+ border-radius: var(--spacing-tokens-size-in-px-radius-radius-full);
101
+ background-color: var(--color-background-bg-element);
102
+ color: var(--color-text-text-paragraph);
103
+ cursor: pointer;
104
+ box-shadow: ${SHADOW_SM};
105
+ transition: color 0.2s ease-in-out;
106
+
107
+ &:hover {
108
+ color: var(--color-text-text-clicable);
109
+ }
110
+ `;
111
+ /** Rotates the chevron smoothly between expanded (◀) and collapsed (▶). */
112
+ const ToggleIcon = styled.span `
113
+ display: inline-flex;
114
+ transform: rotate(${({ $collapsed }) => ($collapsed ? '180deg' : '0deg')});
115
+ transition: transform 0.3s ease-in-out;
116
+
117
+ @media (prefers-reduced-motion: reduce) {
118
+ transition: none;
119
+ }
120
+ `;
121
+ const resolveState = (item, activeKey) => {
122
+ if (item.state)
123
+ return item.state;
124
+ if (item.disabled)
125
+ return DrawerNavItemState.Disabled;
126
+ const key = item.key ?? item.text;
127
+ if (activeKey !== undefined && key === activeKey) {
128
+ return DrawerNavItemState.Active;
129
+ }
130
+ return DrawerNavItemState.Inactive;
131
+ };
132
+ /**
133
+ * Composed application sidebar ("Drawer"): logo header, navigation list,
134
+ * credits card and user-profile footer, with a floating collapse/expand
135
+ * toggle. Collapse state can be controlled (`collapsed` + `onToggle`) or left
136
+ * uncontrolled (`defaultCollapsed`). Every colour, space and radius resolves to
137
+ * `@beydesign/tokens` CSS variables, so it renders correctly in light and dark.
138
+ */
139
+ export const Drawer = ({ navItems, activeKey, collapsed, defaultCollapsed = false, onToggle, showToggle = true, credits, profile, logo, className, style, testId, }) => {
140
+ const isControlled = collapsed !== undefined;
141
+ const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed);
142
+ const isCollapsed = isControlled ? collapsed : internalCollapsed;
143
+ const drawerSize = isCollapsed ? DrawerSize.Collapsed : DrawerSize.Expanded;
144
+ const handleToggle = () => {
145
+ const next = !isCollapsed;
146
+ if (!isControlled)
147
+ setInternalCollapsed(next);
148
+ onToggle?.(next);
149
+ };
150
+ const handleItemClick = (item) => {
151
+ if (item.disabled)
152
+ return;
153
+ if (item.link) {
154
+ window.location.href = item.link;
155
+ }
156
+ else {
157
+ item.onClick?.();
158
+ }
159
+ };
160
+ return (_jsxs(Shell, { "$collapsed": isCollapsed, className: className, style: style, "data-testid": testId, children: [_jsxs(Scroll, { 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: drawerSize, hierarchy: item.hierarchy ?? DrawerNavItemHierarchy.First, showBadge: item.showBadge, badgeText: item.badgeText, onClick: () => handleItemClick(item) }, item.key ?? item.text))) }), (credits || profile) && (_jsxs(Footer, { children: [credits && (_jsx(FooterSlot, { "$collapsed": isCollapsed, children: _jsx(DrawerCreditsCard, { ...credits, size: drawerSize }) })), profile && (_jsx(FooterSlot, { "$collapsed": isCollapsed, children: _jsx(DrawerProfile, { ...profile, size: drawerSize }) }))] }))] })] }), 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' }) }) }))] }));
161
+ };
@@ -0,0 +1,9 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Drawer } from './Drawer';
3
+ declare const meta: Meta<typeof Drawer>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Drawer>;
6
+ export declare const Expanded: Story;
7
+ export declare const Collapsed: Story;
8
+ export declare const WithSubItems: Story;
9
+ export declare const NoFooter: Story;
@@ -0,0 +1,107 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Drawer } from './Drawer';
3
+ import { DrawerNavItemHierarchy } from './types';
4
+ const navItems = [
5
+ { key: 'home', text: 'Home', icon: 'House' },
6
+ { key: 'avatars', text: 'Avatars', icon: 'UserFocus' },
7
+ { key: 'agents', text: 'My Agents', icon: 'Headset' },
8
+ { key: 'ttv', text: 'Text-to-Video', icon: 'VideoCamera' },
9
+ { key: 'kb', text: 'Knowledge Base', icon: 'Book' },
10
+ {
11
+ key: 'conversations',
12
+ text: 'Conversations',
13
+ icon: 'ChatTeardropDots',
14
+ },
15
+ { key: 'analytics', text: 'Analytics', icon: 'ChartBar' },
16
+ { key: 'api', text: 'API keys', icon: 'Key' },
17
+ { key: 'developers', text: 'Developers', icon: 'BracketsCurly' },
18
+ ];
19
+ const meta = {
20
+ title: 'Design System/Components/Drawer/Drawer',
21
+ component: Drawer,
22
+ parameters: {
23
+ layout: 'fullscreen',
24
+ design: {
25
+ type: 'figspec',
26
+ url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=6940-28398',
27
+ },
28
+ },
29
+ tags: ['autodocs'],
30
+ argTypes: {
31
+ activeKey: {
32
+ control: 'text',
33
+ description: 'Key (or text) of the currently active nav item',
34
+ },
35
+ defaultCollapsed: {
36
+ control: 'boolean',
37
+ description: 'Initial collapsed state when uncontrolled',
38
+ table: { defaultValue: { summary: 'false' } },
39
+ },
40
+ showToggle: {
41
+ control: 'boolean',
42
+ description: 'Show the floating collapse/expand toggle',
43
+ table: { defaultValue: { summary: 'true' } },
44
+ },
45
+ navItems: { control: false },
46
+ credits: { control: false },
47
+ profile: { control: false },
48
+ },
49
+ args: {
50
+ navItems,
51
+ activeKey: 'home',
52
+ credits: { usedCredits: 500, maxCredits: 2000 },
53
+ profile: {
54
+ name: 'Emma Novak',
55
+ email: 'emma@example.com',
56
+ menuItems: [
57
+ { label: 'Account settings', icon: 'Gear', onClick: () => undefined },
58
+ {
59
+ label: 'Log out',
60
+ icon: 'SignOut',
61
+ destructive: true,
62
+ onClick: () => undefined,
63
+ },
64
+ ],
65
+ },
66
+ },
67
+ decorators: [
68
+ (Story) => (_jsx("div", { style: { height: '100vh', display: 'flex' }, children: _jsx(Story, {}) })),
69
+ ],
70
+ };
71
+ export default meta;
72
+ export const Expanded = {};
73
+ export const Collapsed = {
74
+ args: { defaultCollapsed: true },
75
+ };
76
+ export const WithSubItems = {
77
+ args: {
78
+ activeKey: 'avatars',
79
+ navItems: [
80
+ navItems[0],
81
+ navItems[1],
82
+ {
83
+ key: 'my-avatars',
84
+ text: 'My Avatars',
85
+ icon: 'User',
86
+ hierarchy: DrawerNavItemHierarchy.Secondary,
87
+ },
88
+ {
89
+ key: 'stock-avatars',
90
+ text: 'Stock Avatars',
91
+ icon: 'UsersThree',
92
+ hierarchy: DrawerNavItemHierarchy.Secondary,
93
+ },
94
+ navItems[2],
95
+ {
96
+ key: 'ttv',
97
+ text: 'Text-to-Video',
98
+ icon: 'VideoCamera',
99
+ showBadge: true,
100
+ },
101
+ ...navItems.slice(4),
102
+ ],
103
+ },
104
+ };
105
+ export const NoFooter = {
106
+ args: { credits: undefined, profile: undefined },
107
+ };
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { DrawerCreditsCardProps } from './types';
3
+ /**
4
+ * Compact "Upgrade plan" credits card for the {@link Drawer} footer. Shows a
5
+ * progress bar and a credits caption, with an info tooltip listing the credit
6
+ * conversion rates. When collapsed it shrinks to the gem glyph and reveals the
7
+ * full details (and an upgrade action) in a hover popover. Fully theme-aware.
8
+ */
9
+ export declare const DrawerCreditsCard: React.FC<DrawerCreditsCardProps>;
@@ -0,0 +1,143 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styled from '@emotion/styled';
3
+ import { DrawerSize } from './types';
4
+ import { Typography, TypographySize, TypographyWeight } from '../Typography';
5
+ import { Popover, ProgressIndicator, ProgressIndicatorMode } from '../UI';
6
+ import { getIconComponent } from '../../utils';
7
+ import { GemIcon } from './DrawerIcons';
8
+ const DEFAULT_INFO_LINES = [
9
+ 'Managed Agents 1 Min = 100 Credits',
10
+ 'Speech-to-Video 1 Min = 50 Credits',
11
+ ];
12
+ /** Composed from the semantic `shadow-xs` design token (matches MainButton). */
13
+ const SHADOW_XS = 'var(--global-shadows-shadow-xs-offset-x) var(--global-shadows-shadow-xs-offset-y) var(--global-shadows-shadow-xs-blur) var(--global-shadows-shadow-xs-spread) var(--global-shadows-shadow-xs-color)';
14
+ const DROP_SHADOW_XS = 'drop-shadow(var(--global-shadows-shadow-xs-offset-x) var(--global-shadows-shadow-xs-offset-y) var(--global-shadows-shadow-xs-blur) var(--global-shadows-shadow-xs-color))';
15
+ const Card = styled.div `
16
+ box-sizing: border-box;
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-xs, 4px);
20
+ width: 100%;
21
+ padding: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px)
22
+ var(--spacing-tokens-size-in-px-spacing-spacing-lg, 12px);
23
+ background-color: var(--color-background-bg-card-highlight);
24
+ border: 1px solid var(--color-border-border-divider);
25
+ border-radius: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
26
+ `;
27
+ const HeaderRow = styled.div `
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: space-between;
31
+ width: 100%;
32
+ `;
33
+ const HeaderLeft = styled.div `
34
+ display: flex;
35
+ align-items: center;
36
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
37
+ `;
38
+ const GemBox = styled.span `
39
+ display: inline-flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ width: 36px;
43
+ height: 36px;
44
+ flex-shrink: 0;
45
+ color: var(--color-foreground-fg-brand-primary);
46
+ background-color: var(--color-background-bg-brand-transparent);
47
+ border-radius: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
48
+ cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
49
+ `;
50
+ const UpgradeLink = styled.button `
51
+ appearance: none;
52
+ border: none;
53
+ background: none;
54
+ padding: 0;
55
+ font: inherit;
56
+ cursor: pointer;
57
+ color: var(--color-text-text-title);
58
+ transition: color 0.2s ease-in-out;
59
+
60
+ &:hover {
61
+ color: var(--color-text-text-clicable);
62
+ text-decoration: underline;
63
+ }
64
+ `;
65
+ const InfoButton = styled.button `
66
+ appearance: none;
67
+ display: inline-flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ width: 32px;
71
+ height: 32px;
72
+ flex-shrink: 0;
73
+ padding: var(--spacing-tokens-size-in-px-spacing-spacing-xs, 4px);
74
+ border: none;
75
+ background: none;
76
+ border-radius: var(--spacing-tokens-size-in-px-radius-radius-xs);
77
+ color: var(--color-text-text-clicable);
78
+ filter: ${DROP_SHADOW_XS};
79
+ cursor: pointer;
80
+ transition: color 0.2s ease-in-out;
81
+
82
+ &:hover {
83
+ color: var(--color-text-text-clicable-hover);
84
+ }
85
+ `;
86
+ const ProgressRow = styled.div `
87
+ display: flex;
88
+ align-items: center;
89
+ height: 20px;
90
+ width: 100%;
91
+ `;
92
+ /** Bare credits bar built on the shared ProgressIndicator (bar-only mode). */
93
+ const CreditsBar = ({ used, max }) => (_jsx(ProgressRow, { children: _jsx(ProgressIndicator, { mode: ProgressIndicatorMode.Bar, currentValue: used, maxValue: max, minWidth: 0, animated: true, fillColor: "var(--color-foreground-fg-brand-primary)", backgroundColor: "var(--color-background-bg-element)", style: { width: '100%' } }) }));
94
+ const CollapsedBox = styled.span `
95
+ display: inline-flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ width: 36px;
99
+ height: 36px;
100
+ color: var(--color-foreground-fg-brand-primary);
101
+ background-color: var(--color-background-bg-brand-transparent);
102
+ border-radius: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
103
+ cursor: pointer;
104
+ `;
105
+ /** Dark popover body shown when hovering the collapsed credits box. */
106
+ const PopoverBody = styled.div `
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
110
+ min-width: 220px;
111
+ `;
112
+ const PopoverButton = styled.button `
113
+ appearance: none;
114
+ align-self: flex-start;
115
+ padding: 6px 8px;
116
+ border: 1px solid var(--color-background-bg-brand-primary);
117
+ border-radius: var(--spacing-tokens-size-in-px-radius-radius-xs, 4px);
118
+ font: inherit;
119
+ cursor: pointer;
120
+ background-color: var(--color-background-bg-brand-primary);
121
+ color: var(--color-text-text-on-accent-color);
122
+ box-shadow: ${SHADOW_XS};
123
+ `;
124
+ const PopoverGroup = styled.div `
125
+ display: flex;
126
+ flex-direction: column;
127
+ width: 100%;
128
+ `;
129
+ const InfoLines = ({ lines }) => (_jsx("div", { style: { display: 'flex', flexDirection: 'column' }, children: lines.map((line, i) => (_jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: "var(--color-text-text-white)", children: line }, i))) }));
130
+ /**
131
+ * Compact "Upgrade plan" credits card for the {@link Drawer} footer. Shows a
132
+ * progress bar and a credits caption, with an info tooltip listing the credit
133
+ * conversion rates. When collapsed it shrinks to the gem glyph and reveals the
134
+ * full details (and an upgrade action) in a hover popover. Fully theme-aware.
135
+ */
136
+ export const DrawerCreditsCard = ({ size = DrawerSize.Expanded, usedCredits = 0, maxCredits = 0, upgradeLabel = 'Upgrade plan', creditsCaption, icon, infoLines = DEFAULT_INFO_LINES, onUpgradeClick, className, style, testId, }) => {
137
+ const caption = creditsCaption ?? `${usedCredits} credits used from ${maxCredits}`;
138
+ const glyph = icon ? (getIconComponent(icon, { size: 20 })) : (_jsx(GemIcon, { size: 20 }));
139
+ if (size === DrawerSize.Collapsed) {
140
+ return (_jsx(Popover, { placement: "right", content: _jsxs(PopoverBody, { children: [_jsx(PopoverButton, { type: "button", onClick: () => onUpgradeClick?.(), children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.SemiBold, color: "var(--color-text-text-on-accent-color)", children: upgradeLabel }) }), _jsxs(PopoverGroup, { children: [_jsx(CreditsBar, { used: usedCredits, max: maxCredits }), _jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-white)", children: caption })] }), _jsx(InfoLines, { lines: infoLines })] }), children: _jsx(CollapsedBox, { className: className, style: style, "data-testid": testId, onClick: () => onUpgradeClick?.(), children: glyph }) }));
141
+ }
142
+ return (_jsxs(Card, { className: className, style: style, "data-testid": testId, children: [_jsxs(HeaderRow, { children: [_jsxs(HeaderLeft, { children: [_jsx(GemBox, { children: glyph }), _jsx(UpgradeLink, { type: "button", onClick: () => onUpgradeClick?.(), children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.SemiBold, color: "inherit", children: upgradeLabel }) })] }), _jsx(Popover, { placement: "top", content: _jsx(InfoLines, { lines: infoLines }), children: _jsx(InfoButton, { type: "button", "aria-label": "Credit rate information", children: getIconComponent('WarningCircle', { size: 20 }) }) })] }), _jsx(CreditsBar, { used: usedCredits, max: maxCredits }), _jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-label)", children: caption })] }));
143
+ };
@@ -0,0 +1,8 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { DrawerCreditsCard } from './DrawerCreditsCard';
3
+ declare const meta: Meta<typeof DrawerCreditsCard>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof DrawerCreditsCard>;
6
+ export declare const Default: Story;
7
+ export declare const NearLimit: Story;
8
+ export declare const Collapsed: Story;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { DrawerCreditsCard } from './DrawerCreditsCard';
3
+ import { DrawerSize } from './types';
4
+ const meta = {
5
+ title: 'Design System/Components/Drawer/DrawerCreditsCard',
6
+ component: DrawerCreditsCard,
7
+ parameters: {
8
+ layout: 'centered',
9
+ design: {
10
+ type: 'figspec',
11
+ url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=6940-29126',
12
+ },
13
+ },
14
+ tags: ['autodocs'],
15
+ argTypes: {
16
+ size: {
17
+ control: 'inline-radio',
18
+ options: Object.values(DrawerSize),
19
+ table: { defaultValue: { summary: 'Expanded' } },
20
+ },
21
+ usedCredits: { control: 'number' },
22
+ maxCredits: { control: 'number' },
23
+ upgradeLabel: { control: 'text' },
24
+ },
25
+ args: {
26
+ size: DrawerSize.Expanded,
27
+ usedCredits: 500,
28
+ maxCredits: 2000,
29
+ },
30
+ decorators: [
31
+ (Story) => (_jsx("div", { style: {
32
+ width: 230,
33
+ padding: 16,
34
+ backgroundColor: 'var(--color-background-bg-page-contrast)',
35
+ }, children: _jsx(Story, {}) })),
36
+ ],
37
+ };
38
+ export default meta;
39
+ export const Default = {};
40
+ export const NearLimit = {
41
+ args: { usedCredits: 1850, maxCredits: 2000 },
42
+ };
43
+ export const Collapsed = {
44
+ args: { size: DrawerSize.Collapsed },
45
+ };
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ interface IconProps {
3
+ /** Pixel size of the square icon. */
4
+ size?: number;
5
+ /** Stroke/fill colour. Defaults to `currentColor` so it inherits theme. */
6
+ color?: string;
7
+ }
8
+ /**
9
+ * Faceted gem glyph used by the credits card. Mirrors the Figma asset but
10
+ * paints with `currentColor` so it stays theme-aware (light violet / dark
11
+ * lemon) instead of a hardcoded brand hex.
12
+ */
13
+ export declare const GemIcon: React.FC<IconProps>;
14
+ export {};
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Faceted gem glyph used by the credits card. Mirrors the Figma asset but
4
+ * paints with `currentColor` so it stays theme-aware (light violet / dark
5
+ * lemon) instead of a hardcoded brand hex.
6
+ */
7
+ export const GemIcon = ({ size = 20, color = 'currentColor', }) => (_jsxs("svg", { width: size, height: size, viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [_jsx("path", { opacity: "0.2", d: "M18.75 8.125L10 17.5L6.25 8.125L10 3.125H14.375L18.75 8.125Z", fill: color }), _jsx("path", { d: "M19.2173 7.71328L14.8423 2.71328C14.7838 2.64647 14.7118 2.59289 14.631 2.55612C14.5501 2.51934 14.4624 2.50021 14.3736 2.5H5.62359C5.53479 2.50021 5.44706 2.51934 5.36623 2.55612C5.2854 2.59289 5.21334 2.64647 5.15484 2.71328L0.779842 7.71328C0.677778 7.82981 0.622625 7.98009 0.625079 8.13498C0.627532 8.28987 0.687419 8.43832 0.793124 8.55156L9.54312 17.9266C9.6016 17.9893 9.67236 18.0392 9.75098 18.0734C9.82961 18.1076 9.91442 18.1252 10.0002 18.1252C10.0859 18.1252 10.1707 18.1076 10.2493 18.0734C10.328 18.0392 10.3987 17.9893 10.4572 17.9266L19.2072 8.55156C19.3125 8.43793 19.3718 8.28926 19.3737 8.13438C19.3755 7.97949 19.3198 7.82943 19.2173 7.71328ZM17.3712 7.5H14.0611L11.2486 3.75H14.09L17.3712 7.5ZM5.82515 8.75L8.17906 14.6352L2.68687 8.75H5.82515ZM12.8252 8.75L9.99859 15.8172L7.17203 8.75H12.8252ZM7.49859 7.5L9.99859 4.16641L12.4986 7.5H7.49859ZM14.172 8.75H17.3103L11.8181 14.6352L14.172 8.75ZM5.90719 3.75H8.74859L5.93609 7.5H2.62594L5.90719 3.75Z", fill: color })] }));
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { DrawerNavItemProps } from './types';
3
+ /**
4
+ * A single navigation row for the {@link Drawer}. Supports active / inactive /
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.
7
+ */
8
+ export declare const DrawerNavItem: React.FC<DrawerNavItemProps>;
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styled from '@emotion/styled';
3
+ import { DrawerNavItemState, DrawerNavItemHierarchy, DrawerSize, } from './types';
4
+ import { Typography, TypographySize, TypographyWeight } from '../Typography';
5
+ import { Badge, BadgeColor, BadgeSize } from '../UI';
6
+ import { getIconComponent } from '../../utils';
7
+ const ACTIVE_FG = 'var(--color-text-text-clicable)';
8
+ const INACTIVE_FG = 'var(--color-text-text-subtle)';
9
+ const ACTIVE_BG = 'var(--color-background-bg-brand-transparent)';
10
+ const HOVER_BG = 'var(--color-background-bg-brand-transparent-hover)';
11
+ const foreground = (state) => state === DrawerNavItemState.Active || state === DrawerNavItemState.Hover
12
+ ? ACTIVE_FG
13
+ : INACTIVE_FG;
14
+ const background = (state) => {
15
+ if (state === DrawerNavItemState.Active)
16
+ return ACTIVE_BG;
17
+ if (state === DrawerNavItemState.Hover)
18
+ return HOVER_BG;
19
+ return 'transparent';
20
+ };
21
+ const StyledNavItem = styled.button `
22
+ appearance: none;
23
+ font: inherit;
24
+ text-align: left;
25
+ border: none;
26
+ box-sizing: border-box;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: ${({ $collapsed }) => $collapsed ? 'center' : 'flex-start'};
30
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
31
+ width: ${({ $collapsed }) => ($collapsed ? '36px' : '100%')};
32
+ height: ${({ $collapsed }) => ($collapsed ? '36px' : 'auto')};
33
+ align-self: ${({ $collapsed }) => ($collapsed ? 'center' : 'stretch')};
34
+ padding: ${({ $collapsed, $hierarchy }) => {
35
+ if ($collapsed)
36
+ return '0';
37
+ return $hierarchy === DrawerNavItemHierarchy.Secondary
38
+ ? 'var(--spacing-tokens-size-in-px-spacing-spacing-xs, 4px) var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px)'
39
+ : 'var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px)';
40
+ }};
41
+ border-radius: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
42
+ color: ${({ $state }) => foreground($state)};
43
+ background-color: ${({ $state }) => background($state)};
44
+ opacity: ${({ $state }) => $state === DrawerNavItemState.Disabled ? 0.5 : 1};
45
+ cursor: ${({ $state }) => $state === DrawerNavItemState.Disabled ? 'not-allowed' : 'pointer'};
46
+ transition:
47
+ background-color 0.2s ease-in-out,
48
+ color 0.2s ease-in-out,
49
+ padding 0.3s ease-in-out;
50
+
51
+ &:hover {
52
+ ${({ $state }) => $state === DrawerNavItemState.Inactive
53
+ ? `background-color: ${HOVER_BG}; color: ${ACTIVE_FG};`
54
+ : ''}
55
+ }
56
+ `;
57
+ const IconSlot = styled.span `
58
+ display: inline-flex;
59
+ flex-shrink: 0;
60
+ color: inherit;
61
+ `;
62
+ const Label = styled.span `
63
+ display: inline-flex;
64
+ flex: 1 1 0;
65
+ min-width: 0;
66
+ white-space: nowrap;
67
+ overflow: hidden;
68
+ text-overflow: ellipsis;
69
+ color: inherit;
70
+ `;
71
+ /**
72
+ * A single navigation row for the {@link Drawer}. Supports active / inactive /
73
+ * hover / disabled states, first / secondary hierarchy and expanded / collapsed
74
+ * sizes. All colours resolve to theme tokens, so it adapts to light and dark.
75
+ */
76
+ export const DrawerNavItem = ({ text, icon, state = DrawerNavItemState.Inactive, size = DrawerSize.Expanded, hierarchy = DrawerNavItemHierarchy.First, showBadge = false, badgeText = 'Coming Soon', onClick, className, style, testId, }) => {
77
+ const isCollapsed = size === DrawerSize.Collapsed;
78
+ const isDisabled = state === DrawerNavItemState.Disabled;
79
+ const isSecondary = hierarchy === DrawerNavItemHierarchy.Secondary;
80
+ const handleClick = () => {
81
+ if (!isDisabled)
82
+ onClick?.();
83
+ };
84
+ return (_jsxs(StyledNavItem, { type: "button", "$state": state, "$collapsed": isCollapsed, "$hierarchy": hierarchy, className: className, style: style, onClick: handleClick, disabled: isDisabled, title: isCollapsed ? text : undefined, "data-testid": testId, children: [_jsx(IconSlot, { children: getIconComponent(icon, { size: 20 }) }), !isCollapsed && (_jsxs(_Fragment, { children: [_jsx(Label, { children: _jsx(Typography, { size: isSecondary ? TypographySize.TextXS : TypographySize.TextS, weight: isSecondary
85
+ ? TypographyWeight.Medium
86
+ : TypographyWeight.SemiBold, color: "inherit", children: text }) }), showBadge && (_jsx(Badge, { size: BadgeSize.xs, icon: "ClockCountdown", showIcon: true, text: badgeText, color: BadgeColor.Default }))] }))] }));
87
+ };
@@ -0,0 +1,11 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { DrawerNavItem } from './DrawerNavItem';
3
+ declare const meta: Meta<typeof DrawerNavItem>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof DrawerNavItem>;
6
+ export declare const Active: Story;
7
+ export declare const Inactive: Story;
8
+ export declare const Hover: Story;
9
+ export declare const Disabled: Story;
10
+ export declare const Secondary: Story;
11
+ export declare const Collapsed: Story;