@beydesign/storybook 0.2.6 → 0.2.8

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 CHANGED
@@ -8,6 +8,7 @@ export * from './stories/Typography';
8
8
  export * from './stories/Credits';
9
9
  export * from './stories/Navigation';
10
10
  export * from './stories/Drawer';
11
+ export * from './stories/Settings';
11
12
  export * from './stories/Typography';
12
13
  export * from './stories/UI';
13
14
  export * from './stories/StripeProduct';
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export * from './stories/Typography';
8
8
  export * from './stories/Credits';
9
9
  export * from './stories/Navigation';
10
10
  export * from './stories/Drawer';
11
+ export * from './stories/Settings';
11
12
  export * from './stories/Typography';
12
13
  export * from './stories/UI';
13
14
  export * from './stories/StripeProduct';
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import { MobileNavBarProps } from './types';
3
+ export declare const MobileNavBar: React.FC<MobileNavBarProps>;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Drawer as MuiDrawer } from '@mui/material';
4
+ import { Logo } from '../UI';
5
+ import { Drawer } from '../Drawer';
6
+ import { getIconComponent } from '../../utils';
7
+ const shadowXs = 'var(--global-shadows-shadow-xs-offset-x) ' +
8
+ 'var(--global-shadows-shadow-xs-offset-y) ' +
9
+ 'var(--global-shadows-shadow-xs-blur) ' +
10
+ 'var(--global-shadows-shadow-xs-spread) ' +
11
+ 'var(--global-shadows-shadow-xs-color)';
12
+ const NavIconButton = ({ icon, ariaLabel, onClick, }) => (_jsx(Box, { component: 'button', type: 'button', onClick: onClick, "aria-label": ariaLabel, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', border: '1px solid var(--color-border-border-component)', borderRadius: 'var(--spacing-tokens-size-in-px-radius-radius-xs)', sx: {
13
+ backgroundColor: 'transparent',
14
+ cursor: 'pointer',
15
+ boxShadow: shadowXs,
16
+ '&:focus-visible': {
17
+ outline: '2px solid var(--color-border-border-component-active)',
18
+ outlineOffset: '2px',
19
+ },
20
+ }, children: getIconComponent(icon, {
21
+ width: '20px',
22
+ height: '20px',
23
+ color: 'var(--color-foreground-fg-secondary)',
24
+ 'aria-hidden': 'true',
25
+ }) }));
26
+ export const MobileNavBar = ({ onSearchClick, drawerProps, showSearch = true, logoProps, style, className, }) => {
27
+ const [open, setOpen] = useState(false);
28
+ const handleMenuClick = () => {
29
+ if (drawerProps)
30
+ setOpen(true);
31
+ };
32
+ const closeDrawer = () => setOpen(false);
33
+ const navItems = drawerProps?.navItems.map((item) => ({
34
+ ...item,
35
+ onClick: () => {
36
+ item.onClick?.();
37
+ closeDrawer();
38
+ },
39
+ }));
40
+ return (_jsxs(_Fragment, { children: [_jsxs(Box, { className: className, sx: style, boxSizing: 'border-box', display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', width: '100%', height: '60px', paddingX: 'var(--spacing-tokens-size-in-px-spacing-spacing-xl)', borderBottom: '1px solid var(--color-border-border-component)', bgcolor: 'var(--color-background-bg-page)', children: [_jsx(Box, { display: 'flex', alignItems: 'center', sx: { '& svg': { display: 'block', height: '20px', width: 'auto' } }, children: _jsx(Logo, { color: 'var(--color-foreground-fg-brand-logo)', ...logoProps }) }), _jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', children: [showSearch && (_jsx(NavIconButton, { icon: 'MagnifyingGlass', ariaLabel: 'Search', onClick: onSearchClick })), _jsx(NavIconButton, { icon: 'List', ariaLabel: 'Open menu', onClick: handleMenuClick })] })] }), drawerProps && navItems && (_jsx(MuiDrawer, { anchor: 'left', open: open, onClose: closeDrawer, PaperProps: {
41
+ sx: { backgroundColor: 'transparent', boxShadow: 'none' },
42
+ }, children: _jsx(Drawer, { ...drawerProps, navItems: navItems, showToggle: false }) }))] }));
43
+ };
@@ -0,0 +1,8 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { MobileNavBar } from './MobileNavBar';
3
+ declare const meta: Meta<typeof MobileNavBar>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof meta>;
6
+ export declare const Default: Story;
7
+ export declare const WithoutDrawer: Story;
8
+ export declare const MenuOnly: Story;
@@ -0,0 +1,75 @@
1
+ import { MobileNavBar } from './MobileNavBar';
2
+ const navItems = [
3
+ { key: 'home', text: 'Home', icon: 'House' },
4
+ { key: 'avatars', text: 'Avatars', icon: 'UserFocus' },
5
+ { key: 'agents', text: 'My Agents', icon: 'Headset' },
6
+ { key: 'ttv', text: 'Text-to-Video', icon: 'VideoCamera' },
7
+ { key: 'kb', text: 'Knowledge Base', icon: 'Book' },
8
+ { key: 'conversations', text: 'Conversations', icon: 'ChatTeardropDots' },
9
+ { key: 'analytics', text: 'Analytics', icon: 'ChartBar' },
10
+ { key: 'api', text: 'API keys', icon: 'Key' },
11
+ { key: 'developers', text: 'Developers', icon: 'BracketsCurly' },
12
+ ];
13
+ const drawerProps = {
14
+ navItems,
15
+ activeKey: 'kb',
16
+ credits: { usedCredits: 500, maxCredits: 2000 },
17
+ profile: {
18
+ name: 'Emma Novak',
19
+ email: 'emma@example.com',
20
+ },
21
+ };
22
+ const meta = {
23
+ title: 'Design System/Components/Navigation/MobileNavBar',
24
+ component: MobileNavBar,
25
+ parameters: {
26
+ layout: 'fullscreen',
27
+ design: {
28
+ type: 'figspec',
29
+ url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=7079-12927',
30
+ },
31
+ },
32
+ tags: ['autodocs'],
33
+ argTypes: {
34
+ onSearchClick: {
35
+ description: 'Callback fired when the search button is clicked',
36
+ table: { type: { summary: 'function' } },
37
+ },
38
+ drawerProps: {
39
+ control: false,
40
+ description: 'Drawer config. When provided, the menu button opens the navigation drawer as an overlay.',
41
+ table: { type: { summary: 'DrawerProps' } },
42
+ },
43
+ showSearch: {
44
+ control: 'boolean',
45
+ description: 'Whether to show the search button',
46
+ table: {
47
+ type: { summary: 'boolean' },
48
+ defaultValue: { summary: 'true' },
49
+ },
50
+ },
51
+ logoProps: {
52
+ control: 'object',
53
+ description: 'Logo customization props',
54
+ table: { type: { summary: 'object' } },
55
+ },
56
+ },
57
+ };
58
+ export default meta;
59
+ export const Default = {
60
+ args: {
61
+ showSearch: true,
62
+ drawerProps,
63
+ },
64
+ };
65
+ export const WithoutDrawer = {
66
+ args: {
67
+ showSearch: true,
68
+ },
69
+ };
70
+ export const MenuOnly = {
71
+ args: {
72
+ showSearch: false,
73
+ drawerProps,
74
+ },
75
+ };
@@ -1,4 +1,5 @@
1
1
  export * from './Header';
2
+ export * from './MobileNavBar';
2
3
  export * from './NavItem';
3
4
  export * from './Sidebar';
4
5
  export * from './types';
@@ -1,4 +1,5 @@
1
1
  export * from './Header';
2
+ export * from './MobileNavBar';
2
3
  export * from './NavItem';
3
4
  export * from './Sidebar';
4
5
  export * from './types';
@@ -2,6 +2,7 @@ import { SxProps, Theme } from '@mui/material';
2
2
  import { IconName } from '../../utils';
3
3
  import { AvatarProps } from '../UI';
4
4
  import { CreditsCardProps } from '../Credits';
5
+ import { DrawerProps } from '../Drawer';
5
6
  /**
6
7
  * Header component props
7
8
  */
@@ -184,3 +185,27 @@ export interface ProcessHeaderProps {
184
185
  /** The class name to apply to the component */
185
186
  className?: string;
186
187
  }
188
+ /**
189
+ * Mobile navigation bar props
190
+ */
191
+ export interface MobileNavBarProps {
192
+ /** Callback fired when the search button is clicked */
193
+ onSearchClick?: () => void;
194
+ /**
195
+ * Drawer configuration. When provided, clicking the menu button slides the
196
+ * navigation drawer in as an overlay.
197
+ */
198
+ drawerProps?: DrawerProps;
199
+ /** Whether to show the search button */
200
+ showSearch?: boolean;
201
+ /** Logo customization props */
202
+ logoProps?: {
203
+ gradient?: string;
204
+ color?: string;
205
+ svgUrl?: string;
206
+ };
207
+ /** The style object to apply to the component */
208
+ style?: SxProps<Theme>;
209
+ /** The class name to apply to the component */
210
+ className?: string;
211
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { SettingsNavProps } from './types';
3
+ /**
4
+ * Secondary navigation for the Settings area: a "Settings" heading followed by
5
+ * titled sections of {@link SettingsNavItem} rows. Sits beside the primary
6
+ * {@link Drawer} and only appears on settings pages. Every colour, space and
7
+ * radius resolves to `@beydesign/tokens` CSS variables, so it renders correctly
8
+ * in light and dark.
9
+ */
10
+ export declare const SettingsNav: React.FC<SettingsNavProps>;
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styled from '@emotion/styled';
3
+ import { SettingsNavItem } from './SettingsNavItem';
4
+ import { DrawerNavItemState } from '../Drawer/types';
5
+ import { Typography, TypographySize, TypographyWeight } from '../Typography';
6
+ const WIDTH = '238px';
7
+ const Shell = styled.aside `
8
+ box-sizing: border-box;
9
+ display: flex;
10
+ flex-direction: column;
11
+ flex-shrink: 0;
12
+ width: ${WIDTH};
13
+ height: 100vh;
14
+ padding: var(--spacing-tokens-size-in-px-spacing-spacing-xl, 16px)
15
+ var(--spacing-tokens-size-in-px-spacing-spacing-xl, 16px)
16
+ var(--spacing-tokens-size-in-px-spacing-spacing-2xl, 24px);
17
+ background-color: var(--color-background-bg-page-contrast);
18
+ border-right: 1px solid var(--color-border-border-component);
19
+ overflow-x: hidden;
20
+ overflow-y: auto;
21
+
22
+ scrollbar-width: thin;
23
+ scrollbar-color: var(--color-border-border-component) transparent;
24
+
25
+ &::-webkit-scrollbar {
26
+ width: 6px;
27
+ }
28
+ &::-webkit-scrollbar-track {
29
+ background: transparent;
30
+ }
31
+ &::-webkit-scrollbar-thumb {
32
+ background-color: var(--color-border-border-component);
33
+ border-radius: var(--spacing-tokens-size-in-px-radius-radius-full);
34
+ }
35
+ &::-webkit-scrollbar-thumb:hover {
36
+ background-color: var(--color-text-text-subtle);
37
+ }
38
+ `;
39
+ const Content = styled.div `
40
+ display: flex;
41
+ flex-direction: column;
42
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-2xl, 20px);
43
+ height: 100%;
44
+ `;
45
+ const Header = styled.div `
46
+ padding: 0 var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
47
+ `;
48
+ const Sections = styled.nav `
49
+ display: flex;
50
+ flex-direction: column;
51
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-2xl, 20px);
52
+ `;
53
+ const Section = styled.div `
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
57
+ `;
58
+ const SectionTitle = styled.div `
59
+ padding: 0 var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
60
+ text-transform: uppercase;
61
+ `;
62
+ const Items = styled.div `
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-xs, 4px);
66
+ `;
67
+ const resolveState = (item, activeKey) => {
68
+ if (item.state)
69
+ return item.state;
70
+ if (item.disabled)
71
+ return DrawerNavItemState.Disabled;
72
+ const key = item.key ?? item.text;
73
+ if (activeKey !== undefined && key === activeKey) {
74
+ return DrawerNavItemState.Active;
75
+ }
76
+ return DrawerNavItemState.Inactive;
77
+ };
78
+ /**
79
+ * Secondary navigation for the Settings area: a "Settings" heading followed by
80
+ * titled sections of {@link SettingsNavItem} rows. Sits beside the primary
81
+ * {@link Drawer} and only appears on settings pages. Every colour, space and
82
+ * radius resolves to `@beydesign/tokens` CSS variables, so it renders correctly
83
+ * in light and dark.
84
+ */
85
+ export const SettingsNav = ({ title = 'Settings', sections, activeKey, className, style, testId, }) => {
86
+ const handleItemClick = (item) => {
87
+ if (item.disabled)
88
+ return;
89
+ if (item.link) {
90
+ window.location.href = item.link;
91
+ }
92
+ else {
93
+ item.onClick?.();
94
+ }
95
+ };
96
+ return (_jsx(Shell, { className: className, style: style, "data-testid": testId, children: _jsxs(Content, { children: [_jsx(Header, { children: _jsx(Typography, { size: TypographySize.HeadingS, weight: TypographyWeight.Bold, color: "var(--color-text-text-title)", children: title }) }), _jsx(Sections, { children: sections.map((section, sectionIndex) => (_jsxs(Section, { children: [section.title && (_jsx(SectionTitle, { children: _jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.SemiBold, color: "var(--color-text-text-subtle)", children: section.title }) })), _jsx(Items, { children: section.items.map((item) => (_jsx(SettingsNavItem, { text: item.text, icon: item.icon, state: resolveState(item, activeKey), showBadge: item.showBadge, badgeText: item.badgeText, onClick: () => handleItemClick(item) }, item.key ?? item.text))) })] }, section.key ?? section.title ?? sectionIndex))) })] }) }));
97
+ };
@@ -0,0 +1,7 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { SettingsNav } from './SettingsNav';
3
+ declare const meta: Meta<typeof SettingsNav>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof SettingsNav>;
6
+ export declare const Default: Story;
7
+ export declare const BrandingActive: Story;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { SettingsNav } from './SettingsNav';
3
+ const sections = [
4
+ {
5
+ key: 'business',
6
+ title: 'Your business',
7
+ items: [{ key: 'branding', text: 'Branding', icon: 'StarFour' }],
8
+ },
9
+ {
10
+ key: 'team',
11
+ title: 'Team',
12
+ items: [{ key: 'members', text: 'Members', icon: 'UsersThree' }],
13
+ },
14
+ {
15
+ key: 'billing',
16
+ title: 'Billing & plans',
17
+ items: [
18
+ {
19
+ key: 'subscription',
20
+ text: 'Subscription',
21
+ icon: 'CreditCard',
22
+ },
23
+ { key: 'invoices', text: 'Invoices', icon: 'FileText' },
24
+ ],
25
+ },
26
+ {
27
+ key: 'developers',
28
+ title: 'Developers',
29
+ items: [
30
+ { key: 'api-keys', text: 'API keys', icon: 'Key' },
31
+ { key: 'webhooks', text: 'Webhooks', icon: 'Graph' },
32
+ ],
33
+ },
34
+ ];
35
+ const meta = {
36
+ title: 'Design System/Components/Settings/SettingsNav',
37
+ component: SettingsNav,
38
+ parameters: {
39
+ layout: 'fullscreen',
40
+ design: {
41
+ type: 'figspec',
42
+ url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=7005-34376',
43
+ },
44
+ },
45
+ tags: ['autodocs'],
46
+ argTypes: {
47
+ title: { control: 'text' },
48
+ activeKey: {
49
+ control: 'text',
50
+ description: 'Key (or text) of the currently active nav item',
51
+ },
52
+ sections: { control: false },
53
+ },
54
+ args: {
55
+ title: 'Settings',
56
+ activeKey: 'api-keys',
57
+ sections,
58
+ },
59
+ decorators: [
60
+ (Story) => (_jsx("div", { style: { height: '100vh', display: 'flex' }, children: _jsx(Story, {}) })),
61
+ ],
62
+ };
63
+ export default meta;
64
+ export const Default = {};
65
+ export const BrandingActive = {
66
+ args: { activeKey: 'branding' },
67
+ };
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { SettingsNavItemProps } from './types';
3
+ /**
4
+ * A single navigation row for the {@link SettingsNav}. Deliberately more compact
5
+ * than {@link DrawerNavItem} — 28px tall (4px vertical padding), a 16px icon and
6
+ * a Medium-weight label — to read as secondary navigation. All colours resolve
7
+ * to theme tokens, so it adapts to light and dark.
8
+ */
9
+ export declare const SettingsNavItem: React.FC<SettingsNavItemProps>;
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styled from '@emotion/styled';
3
+ import { DrawerNavItemState } from '../Drawer/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
+ gap: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
30
+ width: 100%;
31
+ padding: var(--spacing-tokens-size-in-px-spacing-spacing-xs, 4px)
32
+ var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
33
+ border-radius: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
34
+ color: ${({ $state }) => foreground($state)};
35
+ background-color: ${({ $state }) => background($state)};
36
+ opacity: ${({ $state }) => $state === DrawerNavItemState.Disabled ? 0.5 : 1};
37
+ cursor: ${({ $state }) => $state === DrawerNavItemState.Disabled ? 'not-allowed' : 'pointer'};
38
+ transition:
39
+ background-color 0.2s ease-in-out,
40
+ color 0.2s ease-in-out;
41
+
42
+ &:hover {
43
+ ${({ $state }) => $state === DrawerNavItemState.Inactive
44
+ ? `background-color: ${HOVER_BG}; color: ${ACTIVE_FG};`
45
+ : ''}
46
+ }
47
+ `;
48
+ const IconSlot = styled.span `
49
+ display: inline-flex;
50
+ flex-shrink: 0;
51
+ color: inherit;
52
+ `;
53
+ const Label = styled.span `
54
+ display: inline-flex;
55
+ flex: 1 1 0;
56
+ min-width: 0;
57
+ white-space: nowrap;
58
+ overflow: hidden;
59
+ text-overflow: ellipsis;
60
+ color: inherit;
61
+ `;
62
+ /**
63
+ * A single navigation row for the {@link SettingsNav}. Deliberately more compact
64
+ * than {@link DrawerNavItem} — 28px tall (4px vertical padding), a 16px icon and
65
+ * a Medium-weight label — to read as secondary navigation. All colours resolve
66
+ * to theme tokens, so it adapts to light and dark.
67
+ */
68
+ export const SettingsNavItem = ({ text, icon, state = DrawerNavItemState.Inactive, showBadge = false, badgeText = 'Coming Soon', onClick, className, style, testId, }) => {
69
+ const isDisabled = state === DrawerNavItemState.Disabled;
70
+ const handleClick = () => {
71
+ if (!isDisabled)
72
+ onClick?.();
73
+ };
74
+ return (_jsxs(StyledNavItem, { type: "button", "$state": state, className: className, style: style, onClick: handleClick, disabled: isDisabled, "aria-current": state === DrawerNavItemState.Active ? 'page' : undefined, "data-testid": testId, children: [_jsx(IconSlot, { children: getIconComponent(icon, { size: 16 }) }), _jsx(Label, { children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Medium, color: "inherit", children: text }) }), showBadge && (_jsx(Badge, { size: BadgeSize.xs, icon: "ClockCountdown", showIcon: true, text: badgeText, color: BadgeColor.Default }))] }));
75
+ };
@@ -0,0 +1,11 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ /**
3
+ * Renders the primary {@link Drawer} beside the {@link SettingsNav} exactly as
4
+ * they compose on a real settings page, with a placeholder content pane. Both
5
+ * navs are interactive.
6
+ */
7
+ declare const SettingsPageExample: () => import("react/jsx-runtime").JSX.Element;
8
+ declare const meta: Meta<typeof SettingsPageExample>;
9
+ export default meta;
10
+ type Story = StoryObj<typeof SettingsPageExample>;
11
+ export declare const DrawerWithSettingsNav: Story;
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { SettingsNav } from './SettingsNav';
4
+ import { Drawer } from '../Drawer';
5
+ import { Typography, TypographySize, TypographyWeight } from '../Typography';
6
+ const drawerNavItems = [
7
+ { key: 'home', text: 'Home', icon: 'House' },
8
+ { key: 'avatars', text: 'Avatars', icon: 'UserFocus' },
9
+ { key: 'agents', text: 'My Agents', icon: 'Headset' },
10
+ { key: 'ttv', text: 'Text-to-Video', icon: 'VideoCamera' },
11
+ { key: 'kb', text: 'Knowledge Base', icon: 'Book' },
12
+ {
13
+ key: 'conversations',
14
+ text: 'Conversations',
15
+ icon: 'ChatTeardropDots',
16
+ },
17
+ { key: 'analytics', text: 'Analytics', icon: 'ChartBar' },
18
+ { key: 'api', text: 'API keys', icon: 'Key' },
19
+ { key: 'developers', text: 'Developers', icon: 'BracketsCurly' },
20
+ ];
21
+ const settingsSections = [
22
+ {
23
+ key: 'business',
24
+ title: 'Your business',
25
+ items: [{ key: 'branding', text: 'Branding', icon: 'StarFour' }],
26
+ },
27
+ {
28
+ key: 'team',
29
+ title: 'Team',
30
+ items: [{ key: 'members', text: 'Members', icon: 'UsersThree' }],
31
+ },
32
+ {
33
+ key: 'billing',
34
+ title: 'Billing & plans',
35
+ items: [
36
+ { key: 'subscription', text: 'Subscription', icon: 'CreditCard' },
37
+ { key: 'invoices', text: 'Invoices', icon: 'FileText' },
38
+ ],
39
+ },
40
+ {
41
+ key: 'developers',
42
+ title: 'Developers',
43
+ items: [
44
+ { key: 'api-keys', text: 'API keys', icon: 'Key' },
45
+ { key: 'webhooks', text: 'Webhooks', icon: 'Graph' },
46
+ ],
47
+ },
48
+ ];
49
+ const labels = {
50
+ branding: 'Branding',
51
+ members: 'Members',
52
+ subscription: 'Subscription',
53
+ invoices: 'Invoices',
54
+ 'api-keys': 'API keys',
55
+ webhooks: 'Webhooks',
56
+ };
57
+ /**
58
+ * Renders the primary {@link Drawer} beside the {@link SettingsNav} exactly as
59
+ * they compose on a real settings page, with a placeholder content pane. Both
60
+ * navs are interactive.
61
+ */
62
+ const SettingsPageExample = () => {
63
+ const [activeSetting, setActiveSetting] = useState('api-keys');
64
+ const sections = settingsSections.map((section) => ({
65
+ ...section,
66
+ items: section.items.map((item) => ({
67
+ ...item,
68
+ onClick: () => setActiveSetting(item.key ?? item.text),
69
+ })),
70
+ }));
71
+ return (_jsxs("div", { style: {
72
+ display: 'flex',
73
+ height: '100vh',
74
+ backgroundColor: 'var(--color-background-bg-page)',
75
+ }, children: [_jsx(Drawer, { navItems: drawerNavItems, activeKey: "api", credits: { usedCredits: 500, maxCredits: 2000 }, profile: { name: 'Emma Novak', email: 'emma@example.com' } }), _jsx(SettingsNav, { sections: sections, activeKey: activeSetting }), _jsxs("main", { style: {
76
+ flex: 1,
77
+ padding: '32px 40px',
78
+ overflowY: 'auto',
79
+ }, children: [_jsx(Typography, { size: TypographySize.HeadingM, weight: TypographyWeight.SemiBold, color: "var(--color-text-text-title)", children: labels[activeSetting] ?? activeSetting }), _jsx("div", { style: { marginTop: 12 }, children: _jsxs(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: "var(--color-text-text-paragraph)", children: ["Content for the \"", labels[activeSetting] ?? activeSetting, "\" settings page renders here."] }) })] })] }));
80
+ };
81
+ const meta = {
82
+ title: 'Design System/Examples/Settings Page',
83
+ component: SettingsPageExample,
84
+ parameters: {
85
+ layout: 'fullscreen',
86
+ design: {
87
+ type: 'figspec',
88
+ url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=7002-33215',
89
+ },
90
+ },
91
+ };
92
+ export default meta;
93
+ export const DrawerWithSettingsNav = {};
@@ -0,0 +1,3 @@
1
+ export * from './SettingsNav';
2
+ export * from './SettingsNavItem';
3
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ export * from './SettingsNav';
2
+ export * from './SettingsNavItem';
3
+ export * from './types';
@@ -0,0 +1,68 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { IconName } from '../../utils';
3
+ import type { DrawerNavItemState } from '../Drawer/types';
4
+ /**
5
+ * Props for the standalone {@link SettingsNavItem} row. Compared to
6
+ * {@link DrawerNavItem} this is a more compact secondary-nav row: 28px tall,
7
+ * 16px icon and Medium-weight label.
8
+ */
9
+ export interface SettingsNavItemProps {
10
+ /** Label text. */
11
+ text: string;
12
+ /** Phosphor icon name rendered to the left of the label. */
13
+ icon: IconName;
14
+ /** Visual state of the item. */
15
+ state?: DrawerNavItemState;
16
+ /** Show a trailing badge (e.g. "Coming Soon"). */
17
+ showBadge?: boolean;
18
+ /** Text for the trailing badge. */
19
+ badgeText?: string;
20
+ /** Click handler. Not fired when the item is disabled. */
21
+ onClick?: () => void;
22
+ className?: string;
23
+ style?: CSSProperties;
24
+ /** Renders as `data-testid` for testing. */
25
+ testId?: string;
26
+ }
27
+ /**
28
+ * A single navigation entry inside a {@link SettingsNavSection}.
29
+ */
30
+ export interface SettingsNavItemConfig {
31
+ /** Stable identifier used to resolve the active item (falls back to `text`). */
32
+ key?: string;
33
+ text: string;
34
+ icon: IconName;
35
+ /** Force a state. When omitted it is derived from `activeKey` / `disabled`. */
36
+ state?: DrawerNavItemState;
37
+ disabled?: boolean;
38
+ showBadge?: boolean;
39
+ badgeText?: string;
40
+ /** When set, clicking navigates to this URL. */
41
+ link?: string;
42
+ onClick?: () => void;
43
+ }
44
+ /**
45
+ * A titled group of {@link SettingsNavItemConfig} rows
46
+ * (e.g. "Your business", "Billing & plans").
47
+ */
48
+ export interface SettingsNavSection {
49
+ /** Stable key for the section (falls back to `title`). */
50
+ key?: string;
51
+ /** Uppercase caption rendered above the section's items. */
52
+ title?: string;
53
+ items: SettingsNavItemConfig[];
54
+ }
55
+ /**
56
+ * Props for the composed {@link SettingsNav} secondary navigation.
57
+ */
58
+ export interface SettingsNavProps {
59
+ /** Heading rendered at the top. Defaults to "Settings". */
60
+ title?: string;
61
+ /** Grouped navigation entries. */
62
+ sections: SettingsNavSection[];
63
+ /** Key (or text) of the currently active item. */
64
+ activeKey?: string;
65
+ className?: string;
66
+ style?: CSSProperties;
67
+ testId?: string;
68
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beydesign/storybook",
3
3
  "private": false,
4
- "version": "0.2.6",
4
+ "version": "0.2.8",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",