@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/stories/Drawer/Drawer.d.ts +10 -0
- package/dist/stories/Drawer/Drawer.js +161 -0
- package/dist/stories/Drawer/Drawer.stories.d.ts +9 -0
- package/dist/stories/Drawer/Drawer.stories.js +107 -0
- package/dist/stories/Drawer/DrawerCreditsCard.d.ts +9 -0
- package/dist/stories/Drawer/DrawerCreditsCard.js +143 -0
- package/dist/stories/Drawer/DrawerCreditsCard.stories.d.ts +8 -0
- package/dist/stories/Drawer/DrawerCreditsCard.stories.js +45 -0
- package/dist/stories/Drawer/DrawerIcons.d.ts +14 -0
- package/dist/stories/Drawer/DrawerIcons.js +7 -0
- package/dist/stories/Drawer/DrawerNavItem.d.ts +8 -0
- package/dist/stories/Drawer/DrawerNavItem.js +87 -0
- package/dist/stories/Drawer/DrawerNavItem.stories.d.ts +11 -0
- package/dist/stories/Drawer/DrawerNavItem.stories.js +82 -0
- package/dist/stories/Drawer/DrawerProfile.d.ts +10 -0
- package/dist/stories/Drawer/DrawerProfile.js +170 -0
- package/dist/stories/Drawer/DrawerProfile.stories.d.ts +9 -0
- package/dist/stories/Drawer/DrawerProfile.stories.js +60 -0
- package/dist/stories/Drawer/index.d.ts +6 -0
- package/dist/stories/Drawer/index.js +6 -0
- package/dist/stories/Drawer/types.d.ts +164 -0
- package/dist/stories/Drawer/types.js +29 -0
- package/dist/stories/Form/TextInput.js +9 -3
- package/dist/stories/Form/TextInput.stories.d.ts +23 -0
- package/dist/stories/Form/TextInput.stories.js +33 -0
- package/dist/stories/Form/types.d.ts +7 -0
- package/dist/stories/UI/Menu.js +1 -1
- package/dist/stories/UI/Popover.d.ts +10 -0
- package/dist/stories/UI/Popover.js +66 -0
- package/dist/stories/UI/Popover.stories.d.ts +7 -0
- package/dist/stories/UI/Popover.stories.js +62 -0
- package/dist/stories/UI/ProgressIndicator.js +7 -3
- package/dist/stories/UI/ProgressIndicator.stories.d.ts +1 -0
- package/dist/stories/UI/ProgressIndicator.stories.js +9 -0
- package/dist/stories/UI/index.d.ts +1 -0
- package/dist/stories/UI/index.js +1 -0
- package/dist/stories/UI/types.d.ts +49 -2
- package/dist/stories/UI/types.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DrawerNavItem } from './DrawerNavItem';
|
|
3
|
+
import { DrawerNavItemState, DrawerNavItemHierarchy, DrawerSize, } from './types';
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Design System/Components/Drawer/DrawerNavItem',
|
|
6
|
+
component: DrawerNavItem,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
design: {
|
|
10
|
+
type: 'figspec',
|
|
11
|
+
url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=6940-28399',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
argTypes: {
|
|
16
|
+
text: { control: 'text', description: 'Label text' },
|
|
17
|
+
icon: { control: 'text', description: 'Phosphor icon name' },
|
|
18
|
+
state: {
|
|
19
|
+
control: 'select',
|
|
20
|
+
options: Object.values(DrawerNavItemState),
|
|
21
|
+
description: 'Visual state',
|
|
22
|
+
table: { defaultValue: { summary: 'Inactive' } },
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
control: 'inline-radio',
|
|
26
|
+
options: Object.values(DrawerSize),
|
|
27
|
+
description: 'Expanded (icon + label) or Collapsed (icon only)',
|
|
28
|
+
table: { defaultValue: { summary: 'Expanded' } },
|
|
29
|
+
},
|
|
30
|
+
hierarchy: {
|
|
31
|
+
control: 'inline-radio',
|
|
32
|
+
options: Object.values(DrawerNavItemHierarchy),
|
|
33
|
+
description: 'Top-level (First) or nested (Secondary)',
|
|
34
|
+
table: { defaultValue: { summary: 'First' } },
|
|
35
|
+
},
|
|
36
|
+
showBadge: { control: 'boolean' },
|
|
37
|
+
},
|
|
38
|
+
args: {
|
|
39
|
+
text: 'Avatars',
|
|
40
|
+
icon: 'UserFocus',
|
|
41
|
+
state: DrawerNavItemState.Inactive,
|
|
42
|
+
size: DrawerSize.Expanded,
|
|
43
|
+
hierarchy: DrawerNavItemHierarchy.First,
|
|
44
|
+
},
|
|
45
|
+
decorators: [
|
|
46
|
+
(Story) => (_jsx("div", { style: {
|
|
47
|
+
width: 206,
|
|
48
|
+
padding: 16,
|
|
49
|
+
backgroundColor: 'var(--color-background-bg-page-contrast)',
|
|
50
|
+
}, children: _jsx(Story, {}) })),
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
export default meta;
|
|
54
|
+
export const Active = {
|
|
55
|
+
args: { state: DrawerNavItemState.Active, text: 'Home', icon: 'House' },
|
|
56
|
+
};
|
|
57
|
+
export const Inactive = {
|
|
58
|
+
args: { state: DrawerNavItemState.Inactive },
|
|
59
|
+
};
|
|
60
|
+
export const Hover = {
|
|
61
|
+
args: { state: DrawerNavItemState.Hover },
|
|
62
|
+
};
|
|
63
|
+
export const Disabled = {
|
|
64
|
+
args: { state: DrawerNavItemState.Disabled, showBadge: true },
|
|
65
|
+
};
|
|
66
|
+
export const Secondary = {
|
|
67
|
+
args: {
|
|
68
|
+
hierarchy: DrawerNavItemHierarchy.Secondary,
|
|
69
|
+
text: 'My Avatars',
|
|
70
|
+
icon: 'User',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
export const Collapsed = {
|
|
74
|
+
args: { size: DrawerSize.Collapsed, state: DrawerNavItemState.Active },
|
|
75
|
+
decorators: [
|
|
76
|
+
(Story) => (_jsx("div", { style: {
|
|
77
|
+
width: 68,
|
|
78
|
+
padding: 16,
|
|
79
|
+
backgroundColor: 'var(--color-background-bg-page-contrast)',
|
|
80
|
+
}, children: _jsx(Story, {}) })),
|
|
81
|
+
],
|
|
82
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DrawerProfileProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* User profile footer for the {@link Drawer}: avatar plus name and email.
|
|
5
|
+
* Collapses to just the avatar. When `menuItems` are supplied, clicking the
|
|
6
|
+
* profile drops an inline menu directly below the row (e.g. a "Log out"
|
|
7
|
+
* action), styled like the drawer's nav items so it reads as part of the drawer
|
|
8
|
+
* rather than a floating overlay. The profile row itself stays in place.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DrawerProfile: React.FC<DrawerProfileProps>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import styled from '@emotion/styled';
|
|
4
|
+
import { DrawerSize } from './types';
|
|
5
|
+
import { Avatar, AvatarSize, AvatarStyle } from '../UI';
|
|
6
|
+
import { Typography, TypographySize, TypographyWeight } from '../Typography';
|
|
7
|
+
import { getIconComponent } from '../../utils';
|
|
8
|
+
const Container = styled.div `
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
width: 100%;
|
|
12
|
+
`;
|
|
13
|
+
const Row = styled.div `
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
|
|
17
|
+
width: 100%;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
justify-content: ${({ $collapsed }) => $collapsed ? 'center' : 'flex-start'};
|
|
20
|
+
padding: ${({ $clickable, $collapsed }) => $clickable && !$collapsed
|
|
21
|
+
? 'var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px)'
|
|
22
|
+
: '0'};
|
|
23
|
+
border-radius: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
|
|
24
|
+
cursor: ${({ $clickable }) => ($clickable ? 'pointer' : 'default')};
|
|
25
|
+
background-color: ${({ $active }) => $active ? 'var(--color-background-bg-element)' : 'transparent'};
|
|
26
|
+
transition: background-color 0.15s ease-in-out;
|
|
27
|
+
|
|
28
|
+
&:hover {
|
|
29
|
+
background-color: ${({ $clickable }) => $clickable ? 'var(--color-background-bg-element)' : 'transparent'};
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
const TextColumn = styled.div `
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
min-width: 0;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
`;
|
|
38
|
+
const TextLine = styled.span `
|
|
39
|
+
display: block;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
text-overflow: ellipsis;
|
|
43
|
+
`;
|
|
44
|
+
/** Trailing caret on the profile row that flips when the menu is open. */
|
|
45
|
+
const Chevron = styled.span `
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
flex-shrink: 0;
|
|
48
|
+
margin-left: auto;
|
|
49
|
+
color: var(--color-text-text-subtle);
|
|
50
|
+
transform: rotate(${({ $open }) => ($open ? '180deg' : '0deg')});
|
|
51
|
+
transition: transform 0.25s ease-in-out;
|
|
52
|
+
|
|
53
|
+
@media (prefers-reduced-motion: reduce) {
|
|
54
|
+
transition: none;
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
/**
|
|
58
|
+
* Collapsible wrapper for the menu that drops below the profile row. Animates
|
|
59
|
+
* height with the `grid-template-rows: 0fr -> 1fr` trick so it reveals/hides
|
|
60
|
+
* smoothly without measuring the content.
|
|
61
|
+
*/
|
|
62
|
+
const Menu = styled.div `
|
|
63
|
+
display: grid;
|
|
64
|
+
grid-template-rows: ${({ $open }) => ($open ? '1fr' : '0fr')};
|
|
65
|
+
transition: grid-template-rows 0.25s ease-in-out;
|
|
66
|
+
|
|
67
|
+
@media (prefers-reduced-motion: reduce) {
|
|
68
|
+
transition: none;
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
const MenuClip = styled.div `
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
min-height: 0;
|
|
74
|
+
`;
|
|
75
|
+
const MenuList = styled.div `
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
gap: var(--spacing-tokens-size-in-px-spacing-spacing-xxs, 2px);
|
|
79
|
+
padding-top: var(--spacing-tokens-size-in-px-spacing-spacing-xs, 4px);
|
|
80
|
+
margin-top: var(--spacing-tokens-size-in-px-spacing-spacing-xs, 4px);
|
|
81
|
+
border-top: 1px solid var(--color-border-border-subtle);
|
|
82
|
+
`;
|
|
83
|
+
/** Menu rows reuse the nav-item look so they read as part of the drawer. */
|
|
84
|
+
const MenuItemButton = styled.button `
|
|
85
|
+
appearance: none;
|
|
86
|
+
border: none;
|
|
87
|
+
background: none;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
|
|
91
|
+
width: 100%;
|
|
92
|
+
box-sizing: border-box;
|
|
93
|
+
justify-content: ${({ $collapsed }) => $collapsed ? 'center' : 'flex-start'};
|
|
94
|
+
padding: var(--spacing-tokens-size-in-px-spacing-spacing-md, 8px);
|
|
95
|
+
border-radius: var(--spacing-tokens-size-in-px-radius-radius-xs);
|
|
96
|
+
font: inherit;
|
|
97
|
+
text-align: left;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
color: ${({ $destructive }) => $destructive
|
|
100
|
+
? 'var(--color-text-text-error)'
|
|
101
|
+
: 'var(--color-text-text-title)'};
|
|
102
|
+
transition: background-color 0.15s ease-in-out;
|
|
103
|
+
|
|
104
|
+
&:hover {
|
|
105
|
+
background-color: var(--color-background-bg-element);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
&:focus-visible {
|
|
109
|
+
outline: 2px solid var(--color-border-border-component);
|
|
110
|
+
outline-offset: -2px;
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
/**
|
|
114
|
+
* User profile footer for the {@link Drawer}: avatar plus name and email.
|
|
115
|
+
* Collapses to just the avatar. When `menuItems` are supplied, clicking the
|
|
116
|
+
* profile drops an inline menu directly below the row (e.g. a "Log out"
|
|
117
|
+
* action), styled like the drawer's nav items so it reads as part of the drawer
|
|
118
|
+
* rather than a floating overlay. The profile row itself stays in place.
|
|
119
|
+
*/
|
|
120
|
+
export const DrawerProfile = ({ size = DrawerSize.Expanded, name, email, avatarImage, avatarText, menuItems, onClick, className, style, testId, }) => {
|
|
121
|
+
const isCollapsed = size === DrawerSize.Collapsed;
|
|
122
|
+
const hasMenu = Boolean(menuItems && menuItems.length > 0);
|
|
123
|
+
const [open, setOpen] = useState(false);
|
|
124
|
+
const containerRef = useRef(null);
|
|
125
|
+
// Close the inline menu on outside click or Escape.
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!open)
|
|
128
|
+
return;
|
|
129
|
+
const handlePointer = (event) => {
|
|
130
|
+
if (!containerRef.current?.contains(event.target)) {
|
|
131
|
+
setOpen(false);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const handleKey = (event) => {
|
|
135
|
+
if (event.key === 'Escape')
|
|
136
|
+
setOpen(false);
|
|
137
|
+
};
|
|
138
|
+
document.addEventListener('mousedown', handlePointer);
|
|
139
|
+
document.addEventListener('keydown', handleKey);
|
|
140
|
+
return () => {
|
|
141
|
+
document.removeEventListener('mousedown', handlePointer);
|
|
142
|
+
document.removeEventListener('keydown', handleKey);
|
|
143
|
+
};
|
|
144
|
+
}, [open]);
|
|
145
|
+
const clickable = hasMenu || Boolean(onClick);
|
|
146
|
+
const handleRowClick = () => {
|
|
147
|
+
if (hasMenu) {
|
|
148
|
+
setOpen((prev) => !prev);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
onClick?.();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const handleItemClick = (item) => {
|
|
155
|
+
setOpen(false);
|
|
156
|
+
item.onClick?.();
|
|
157
|
+
};
|
|
158
|
+
return (_jsxs(Container, { ref: containerRef, className: className, style: style, "data-testid": testId, children: [_jsxs(Row, { "$collapsed": isCollapsed, "$clickable": clickable, "$active": hasMenu && open, onClick: handleRowClick, role: clickable ? 'button' : undefined, tabIndex: clickable ? 0 : undefined, "aria-expanded": hasMenu ? open : undefined, onKeyDown: clickable
|
|
159
|
+
? (event) => {
|
|
160
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
handleRowClick();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
: undefined, children: [_jsx(Avatar, { size: AvatarSize.sm, border: true, avatarStyle: avatarImage ? AvatarStyle.Picture : AvatarStyle.Letters, image: avatarImage, text: avatarText ?? name }), !isCollapsed && (_jsxs(_Fragment, { children: [_jsxs(TextColumn, { children: [_jsx(TextLine, { children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.SemiBold, color: "var(--color-text-text-title)", children: name }) }), email && (_jsx(TextLine, { children: _jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-paragraph)", children: email }) }))] }), hasMenu && (_jsx(Chevron, { "$open": open, children: getIconComponent('CaretDown', { size: 16, weight: 'bold' }) }))] }))] }), hasMenu && (_jsx(Menu, { "$open": open, "aria-hidden": !open, children: _jsx(MenuClip, { children: _jsx(MenuList, { role: "menu", children: menuItems?.map((item, index) => (_jsxs(MenuItemButton, { type: "button", role: "menuitem", tabIndex: open ? 0 : -1, "$destructive": Boolean(item.destructive), "$collapsed": isCollapsed, title: isCollapsed ? item.label : undefined, onClick: () => handleItemClick(item), children: [item.icon &&
|
|
166
|
+
getIconComponent(item.icon, {
|
|
167
|
+
size: 20,
|
|
168
|
+
color: 'currentColor',
|
|
169
|
+
}), !isCollapsed && (_jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: "inherit", children: item.label }))] }, index))) }) }) }))] }));
|
|
170
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { DrawerProfile } from './DrawerProfile';
|
|
3
|
+
declare const meta: Meta<typeof DrawerProfile>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof DrawerProfile>;
|
|
6
|
+
export declare const WithInitials: Story;
|
|
7
|
+
export declare const WithMenu: Story;
|
|
8
|
+
export declare const WithImage: Story;
|
|
9
|
+
export declare const Collapsed: Story;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DrawerProfile } from './DrawerProfile';
|
|
3
|
+
import { DrawerSize } from './types';
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Design System/Components/Drawer/DrawerProfile',
|
|
6
|
+
component: DrawerProfile,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
design: {
|
|
10
|
+
type: 'figspec',
|
|
11
|
+
url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=6940-28477',
|
|
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
|
+
name: { control: 'text' },
|
|
22
|
+
email: { control: 'text' },
|
|
23
|
+
avatarImage: { control: 'text' },
|
|
24
|
+
},
|
|
25
|
+
args: {
|
|
26
|
+
size: DrawerSize.Expanded,
|
|
27
|
+
name: 'Emma Novak',
|
|
28
|
+
email: 'emma@example.com',
|
|
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 WithInitials = {};
|
|
40
|
+
export const WithMenu = {
|
|
41
|
+
args: {
|
|
42
|
+
menuItems: [
|
|
43
|
+
{ label: 'Account settings', icon: 'Gear', onClick: () => undefined },
|
|
44
|
+
{
|
|
45
|
+
label: 'Log out',
|
|
46
|
+
icon: 'SignOut',
|
|
47
|
+
destructive: true,
|
|
48
|
+
onClick: () => undefined,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
export const WithImage = {
|
|
54
|
+
args: {
|
|
55
|
+
avatarImage: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=64&h=64&fit=crop&crop=faces',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
export const Collapsed = {
|
|
59
|
+
args: { size: DrawerSize.Collapsed },
|
|
60
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
import { IconName } from '../../utils';
|
|
3
|
+
/**
|
|
4
|
+
* Expanded / collapsed size shared across the Drawer and its sub-components.
|
|
5
|
+
*/
|
|
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
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Props for the standalone {@link DrawerNavItem} component.
|
|
31
|
+
*/
|
|
32
|
+
export interface DrawerNavItemProps {
|
|
33
|
+
/** Label text. Hidden (and used as the hover tooltip) when collapsed. */
|
|
34
|
+
text: string;
|
|
35
|
+
/** Phosphor icon name rendered to the left of the label. */
|
|
36
|
+
icon: IconName;
|
|
37
|
+
/** Visual state of the item. */
|
|
38
|
+
state?: DrawerNavItemState;
|
|
39
|
+
/** Expanded (icon + label) or Collapsed (icon only). */
|
|
40
|
+
size?: DrawerSize;
|
|
41
|
+
/** Top-level (`First`) or nested (`Secondary`) item. */
|
|
42
|
+
hierarchy?: DrawerNavItemHierarchy;
|
|
43
|
+
/** Show a trailing badge (e.g. "Coming Soon"). Hidden when collapsed. */
|
|
44
|
+
showBadge?: boolean;
|
|
45
|
+
/** Text for the trailing badge. */
|
|
46
|
+
badgeText?: string;
|
|
47
|
+
/** Click handler. Not fired when the item is disabled. */
|
|
48
|
+
onClick?: () => void;
|
|
49
|
+
className?: string;
|
|
50
|
+
style?: CSSProperties;
|
|
51
|
+
/** Renders as `data-testid` for testing. */
|
|
52
|
+
testId?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Props for the standalone {@link DrawerCreditsCard} component.
|
|
56
|
+
*/
|
|
57
|
+
export interface DrawerCreditsCardProps {
|
|
58
|
+
/** Expanded (full card) or Collapsed (icon only, details in a popover). */
|
|
59
|
+
size?: DrawerSize;
|
|
60
|
+
/** Credits consumed so far. */
|
|
61
|
+
usedCredits?: number;
|
|
62
|
+
/** Total credits available in the plan. */
|
|
63
|
+
maxCredits?: number;
|
|
64
|
+
/** Title/link label. Defaults to "Upgrade plan". */
|
|
65
|
+
upgradeLabel?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Overrides the auto-generated "{used} credits used from {max}" caption.
|
|
68
|
+
*/
|
|
69
|
+
creditsCaption?: string;
|
|
70
|
+
/** Leading icon. Defaults to the gem glyph. */
|
|
71
|
+
icon?: IconName;
|
|
72
|
+
/** Lines shown in the info tooltip / collapsed popover. */
|
|
73
|
+
infoLines?: string[];
|
|
74
|
+
/** Fired when the title or upgrade button is clicked. */
|
|
75
|
+
onUpgradeClick?: () => void;
|
|
76
|
+
className?: string;
|
|
77
|
+
style?: CSSProperties;
|
|
78
|
+
testId?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* An item in the {@link DrawerProfile} click menu.
|
|
82
|
+
*/
|
|
83
|
+
export interface DrawerProfileMenuItem {
|
|
84
|
+
label: string;
|
|
85
|
+
/** Optional leading Phosphor icon. */
|
|
86
|
+
icon?: IconName;
|
|
87
|
+
/** Render in the error/destructive colour (e.g. "Log out"). */
|
|
88
|
+
destructive?: boolean;
|
|
89
|
+
onClick?: () => void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Props for the standalone {@link DrawerProfile} footer component.
|
|
93
|
+
*/
|
|
94
|
+
export interface DrawerProfileProps {
|
|
95
|
+
/** Expanded (avatar + text) or Collapsed (avatar only). */
|
|
96
|
+
size?: DrawerSize;
|
|
97
|
+
/** Display name. */
|
|
98
|
+
name: string;
|
|
99
|
+
/** Secondary line, usually the email. */
|
|
100
|
+
email?: string;
|
|
101
|
+
/** Avatar image URL. When omitted, initials are rendered instead. */
|
|
102
|
+
avatarImage?: string;
|
|
103
|
+
/** Overrides the initials source (defaults to `name`). */
|
|
104
|
+
avatarText?: string;
|
|
105
|
+
/**
|
|
106
|
+
* When provided, clicking the profile drops an inline menu with these items
|
|
107
|
+
* (e.g. a "Log out" action) directly below the profile row instead of firing
|
|
108
|
+
* `onClick`.
|
|
109
|
+
*/
|
|
110
|
+
menuItems?: DrawerProfileMenuItem[];
|
|
111
|
+
/** Click handler for the whole profile row (ignored when `menuItems` is set). */
|
|
112
|
+
onClick?: () => void;
|
|
113
|
+
className?: string;
|
|
114
|
+
style?: CSSProperties;
|
|
115
|
+
testId?: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A single entry in the {@link Drawer}'s `navItems` list.
|
|
119
|
+
*/
|
|
120
|
+
export interface DrawerNavItemConfig {
|
|
121
|
+
/** Stable identifier used to resolve the active item (falls back to `text`). */
|
|
122
|
+
key?: string;
|
|
123
|
+
text: string;
|
|
124
|
+
icon: IconName;
|
|
125
|
+
hierarchy?: DrawerNavItemHierarchy;
|
|
126
|
+
/** Force a state. When omitted it is derived from `activeKey` / `disabled`. */
|
|
127
|
+
state?: DrawerNavItemState;
|
|
128
|
+
disabled?: boolean;
|
|
129
|
+
showBadge?: boolean;
|
|
130
|
+
badgeText?: string;
|
|
131
|
+
/** When set, clicking navigates to this URL. */
|
|
132
|
+
link?: string;
|
|
133
|
+
onClick?: () => void;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Props for the composed {@link Drawer} component.
|
|
137
|
+
*/
|
|
138
|
+
export interface DrawerProps {
|
|
139
|
+
/** Navigation entries rendered in the scrollable top section. */
|
|
140
|
+
navItems: DrawerNavItemConfig[];
|
|
141
|
+
/** Key (or text) of the currently active item. */
|
|
142
|
+
activeKey?: string;
|
|
143
|
+
/** Controlled collapsed state. Provide together with `onToggle`. */
|
|
144
|
+
collapsed?: boolean;
|
|
145
|
+
/** Initial collapsed state when uncontrolled. Defaults to `false`. */
|
|
146
|
+
defaultCollapsed?: boolean;
|
|
147
|
+
/** Fired with the next collapsed value whenever the toggle is pressed. */
|
|
148
|
+
onToggle?: (collapsed: boolean) => void;
|
|
149
|
+
/** Show the floating collapse/expand toggle. Defaults to `true`. */
|
|
150
|
+
showToggle?: boolean;
|
|
151
|
+
/** Credits card configuration. Omit to hide the card. */
|
|
152
|
+
credits?: Omit<DrawerCreditsCardProps, 'size' | 'className' | 'style'>;
|
|
153
|
+
/** User profile footer configuration. Omit to hide the footer. */
|
|
154
|
+
profile?: Omit<DrawerProfileProps, 'size' | 'className' | 'style'>;
|
|
155
|
+
/** Logo appearance overrides. */
|
|
156
|
+
logo?: {
|
|
157
|
+
color?: string;
|
|
158
|
+
gradient?: string;
|
|
159
|
+
svgUrl?: string;
|
|
160
|
+
};
|
|
161
|
+
className?: string;
|
|
162
|
+
style?: CSSProperties;
|
|
163
|
+
testId?: string;
|
|
164
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expanded / collapsed size shared across the Drawer and its sub-components.
|
|
3
|
+
*/
|
|
4
|
+
export var DrawerSize;
|
|
5
|
+
(function (DrawerSize) {
|
|
6
|
+
DrawerSize["Expanded"] = "Expanded";
|
|
7
|
+
DrawerSize["Collapsed"] = "Collapsed";
|
|
8
|
+
})(DrawerSize || (DrawerSize = {}));
|
|
9
|
+
/**
|
|
10
|
+
* Visual state of a single navigation item.
|
|
11
|
+
* `Hover` is mostly useful for documentation/Storybook — at runtime an
|
|
12
|
+
* `Inactive` item also picks up the hover styling on real pointer hover.
|
|
13
|
+
*/
|
|
14
|
+
export var DrawerNavItemState;
|
|
15
|
+
(function (DrawerNavItemState) {
|
|
16
|
+
DrawerNavItemState["Active"] = "Active";
|
|
17
|
+
DrawerNavItemState["Inactive"] = "Inactive";
|
|
18
|
+
DrawerNavItemState["Hover"] = "Hover";
|
|
19
|
+
DrawerNavItemState["Disabled"] = "Disabled";
|
|
20
|
+
})(DrawerNavItemState || (DrawerNavItemState = {}));
|
|
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 var DrawerNavItemHierarchy;
|
|
26
|
+
(function (DrawerNavItemHierarchy) {
|
|
27
|
+
DrawerNavItemHierarchy["First"] = "First";
|
|
28
|
+
DrawerNavItemHierarchy["Secondary"] = "Secondary";
|
|
29
|
+
})(DrawerNavItemHierarchy || (DrawerNavItemHierarchy = {}));
|
|
@@ -4,7 +4,7 @@ import { Box } from '@mui/material';
|
|
|
4
4
|
import { Typography, TypographySize, TypographyWeight } from '../Typography';
|
|
5
5
|
import { HintBubble, HintBubbleSize } from '../UI';
|
|
6
6
|
import { getIconComponent } from '../../utils';
|
|
7
|
-
export const TextInput = ({ label, value, onChange, placeholder, type = 'text', hintText, errorText, disabled, trailingText, style, hintBubbleText, hintBubbleProps, required, labelTrailingElement, labelContainerStyle, showPasswordToggle = true, }) => {
|
|
7
|
+
export const TextInput = ({ label, value, onChange, placeholder, type = 'text', hintText, errorText, disabled, trailingText, style, hintBubbleText, hintBubbleProps, required, labelTrailingElement, labelContainerStyle, showPasswordToggle = true, fullWidth = false, leadingIcon, }) => {
|
|
8
8
|
const [focused, setFocused] = useState(false);
|
|
9
9
|
const [showPassword, setShowPassword] = useState(false);
|
|
10
10
|
const isPassword = type === 'password';
|
|
@@ -24,13 +24,19 @@ export const TextInput = ({ label, value, onChange, placeholder, type = 'text',
|
|
|
24
24
|
const iconColor = disabled
|
|
25
25
|
? 'var(--color-text-text-disabled)'
|
|
26
26
|
: 'var(--color-foreground-fg-secondary)';
|
|
27
|
-
return (_jsx(Box, { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', sx: style, children: _jsxs(Box, { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', minWidth: '260px', children: [label && (_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', sx: labelContainerStyle, children: [_jsx(Typography, { size: TypographySize.HeadingXS, weight: TypographyWeight.SemiBold, required: required, children: label }), labelTrailingElement] })), hintBubbleText && (_jsx(HintBubble, { text: hintBubbleText, size: HintBubbleSize.sm, ...hintBubbleProps })), _jsxs(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', bgcolor: 'var(--color-background-bg-component)', border: errorText
|
|
27
|
+
return (_jsx(Box, { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', width: fullWidth ? '100%' : undefined, minWidth: fullWidth ? 0 : undefined, sx: style, children: _jsxs(Box, { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', width: fullWidth ? '100%' : undefined, minWidth: fullWidth ? 0 : '260px', children: [label && (_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', sx: labelContainerStyle, children: [_jsx(Typography, { size: TypographySize.HeadingXS, weight: TypographyWeight.SemiBold, required: required, children: label }), labelTrailingElement] })), hintBubbleText && (_jsx(HintBubble, { text: hintBubbleText, size: HintBubbleSize.sm, ...hintBubbleProps })), _jsxs(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', bgcolor: 'var(--color-background-bg-component)', border: errorText
|
|
28
28
|
? 'none'
|
|
29
29
|
: '1px solid var(--color-border-border-component)', overflow: 'hidden', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', width: '100%', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs) 0 0 var(--spacing-tokens-size-in-px-spacing-spacing-xs)', border: `${errorText
|
|
30
30
|
? '1px solid var(--color-border-border-error)'
|
|
31
31
|
: focused
|
|
32
32
|
? '1px solid var(--color-border-border-component-active)'
|
|
33
|
-
: 'transparent'}`, children: [
|
|
33
|
+
: 'transparent'}`, children: [leadingIcon &&
|
|
34
|
+
getIconComponent(leadingIcon, {
|
|
35
|
+
width: '20px',
|
|
36
|
+
height: '20px',
|
|
37
|
+
color: iconColor,
|
|
38
|
+
'aria-hidden': 'true',
|
|
39
|
+
}), _jsx("input", { type: inputType, value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, disabled: disabled, style: {
|
|
34
40
|
border: 'none',
|
|
35
41
|
outline: 'none',
|
|
36
42
|
width: '100%',
|
|
@@ -145,11 +145,34 @@ declare const meta: {
|
|
|
145
145
|
};
|
|
146
146
|
};
|
|
147
147
|
};
|
|
148
|
+
fullWidth: {
|
|
149
|
+
control: "boolean";
|
|
150
|
+
description: string;
|
|
151
|
+
table: {
|
|
152
|
+
type: {
|
|
153
|
+
summary: string;
|
|
154
|
+
};
|
|
155
|
+
defaultValue: {
|
|
156
|
+
summary: string;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
leadingIcon: {
|
|
161
|
+
control: "text";
|
|
162
|
+
description: string;
|
|
163
|
+
table: {
|
|
164
|
+
type: {
|
|
165
|
+
summary: string;
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
};
|
|
148
169
|
};
|
|
149
170
|
};
|
|
150
171
|
export default meta;
|
|
151
172
|
type Story = StoryObj<typeof meta>;
|
|
152
173
|
export declare const Default: Story;
|
|
174
|
+
export declare const WithLeadingIcon: Story;
|
|
153
175
|
export declare const Password: Story;
|
|
154
176
|
export declare const PasswordWithoutToggle: Story;
|
|
155
177
|
export declare const Email: Story;
|
|
178
|
+
export declare const SideBySideFullWidth: Story;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
2
|
import { TextInput } from './TextInput';
|
|
2
3
|
const meta = {
|
|
3
4
|
title: 'Design System/Components/Form/TextInput',
|
|
@@ -113,6 +114,21 @@ const meta = {
|
|
|
113
114
|
defaultValue: { summary: 'true' },
|
|
114
115
|
},
|
|
115
116
|
},
|
|
117
|
+
fullWidth: {
|
|
118
|
+
control: 'boolean',
|
|
119
|
+
description: 'Whether the input fills its container and may shrink below the default min-width (use for side-by-side fields)',
|
|
120
|
+
table: {
|
|
121
|
+
type: { summary: 'boolean' },
|
|
122
|
+
defaultValue: { summary: 'false' },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
leadingIcon: {
|
|
126
|
+
control: 'text',
|
|
127
|
+
description: 'Icon rendered before the input value (e.g. a search glass). Accepts a Phosphor icon name.',
|
|
128
|
+
table: {
|
|
129
|
+
type: { summary: 'IconName' },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
116
132
|
},
|
|
117
133
|
};
|
|
118
134
|
export default meta;
|
|
@@ -124,6 +140,14 @@ export const Default = {
|
|
|
124
140
|
hintBubbleText: 'This is a hint bubble',
|
|
125
141
|
},
|
|
126
142
|
};
|
|
143
|
+
export const WithLeadingIcon = {
|
|
144
|
+
args: {
|
|
145
|
+
value: '',
|
|
146
|
+
placeholder: 'Search',
|
|
147
|
+
leadingIcon: 'MagnifyingGlass',
|
|
148
|
+
onChange: () => { },
|
|
149
|
+
},
|
|
150
|
+
};
|
|
127
151
|
export const Password = {
|
|
128
152
|
args: {
|
|
129
153
|
label: 'Password',
|
|
@@ -153,3 +177,12 @@ export const Email = {
|
|
|
153
177
|
hintBubbleText: 'This is a hint bubble',
|
|
154
178
|
},
|
|
155
179
|
};
|
|
180
|
+
export const SideBySideFullWidth = {
|
|
181
|
+
args: {
|
|
182
|
+
label: 'First name',
|
|
183
|
+
value: 'Anna',
|
|
184
|
+
fullWidth: true,
|
|
185
|
+
onChange: () => { },
|
|
186
|
+
},
|
|
187
|
+
render: (args) => (_jsxs("div", { style: { display: 'flex', gap: '8px', width: '388px' }, children: [_jsx("div", { style: { minWidth: 0, flex: 1 }, children: _jsx(TextInput, { ...args, label: "First name", value: "Anna" }) }), _jsx("div", { style: { minWidth: 0, flex: 1 }, children: _jsx(TextInput, { ...args, label: "Last name", value: "Smith" }) })] })),
|
|
188
|
+
};
|