@beydesign/storybook 0.5.1 → 0.5.2
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/stories/Buttons/MainButton.js +1 -1
- package/dist/stories/EntityCard/EntityCard.js +11 -4
- package/dist/stories/EntityCard/EntityCard.stories.d.ts +2 -0
- package/dist/stories/EntityCard/EntityCard.stories.js +40 -0
- package/dist/stories/EntityCard/types.d.ts +8 -0
- package/dist/stories/UI/SelectAvatarCard.d.ts +2 -1
- package/dist/stories/UI/SelectAvatarCard.js +36 -13
- package/dist/stories/UI/SelectAvatarCard.stories.d.ts +4 -3
- package/dist/stories/UI/SelectAvatarCard.stories.js +5 -4
- package/dist/stories/UI/types.d.ts +4 -1
- package/package.json +1 -1
|
@@ -332,5 +332,5 @@ export const MainButton = ({ onClick, text, disabled = false, loading = false, t
|
|
|
332
332
|
(trailingIconSlot ??
|
|
333
333
|
(trailingIcon && getIconComponent(trailingIcon, iconProps)))] }));
|
|
334
334
|
};
|
|
335
|
-
return (_jsx(StyledButton, { onClick: onClick, type: type, disabled: state === ButtonState.Disabled || disabled, "$size": size, "$hierarchy": hierarchy, "$state": state, "$shape": shape, "$destructive": destructive, "$disabled": disabled, "$displayIconMode": displayIconMode, "$sizeStyles": sizeStyles, style: style, id: id, className: className, "data-testid": testId, children: renderContent() }));
|
|
335
|
+
return (_jsx(StyledButton, { onClick: onClick, type: type, disabled: state === ButtonState.Disabled || disabled, "$size": size, "$hierarchy": hierarchy, "$state": state, "$shape": shape, "$destructive": destructive, "$disabled": disabled, "$displayIconMode": displayIconMode, "$sizeStyles": sizeStyles, style: style, id: id, className: className, "aria-label": displayIconMode === ButtonDisplayMode.Only ? text : undefined, "data-testid": testId, children: renderContent() }));
|
|
336
336
|
};
|
|
@@ -45,7 +45,7 @@ const spin = keyframes `
|
|
|
45
45
|
`;
|
|
46
46
|
/** A continuously rotating refresh icon, matching the AgentCard processing spinner. */
|
|
47
47
|
const Spinner = () => (_jsx(Box, { display: 'flex', sx: { animation: `${spin} 1.5s linear infinite` }, children: _jsx(ArrowsClockwise, { size: 24, color: 'var(--color-text-text-white)' }) }));
|
|
48
|
-
export const EntityCard = ({ view = 'avatar', name, image, description = '', showDescription = false, date, aspectRatio, processing = false, error = false, onPreview, previewButtonText, previewIcon = 'Play', previewButtonClassName, stockAgent = false, menuItems = [], showMenu = false, width, testId, }) => {
|
|
48
|
+
export const EntityCard = ({ view = 'avatar', name, image, description = '', showDescription = false, date, aspectRatio, processing = false, error = false, onPreview, onCardClick, selected = false, previewButtonText, previewIcon = 'Play', previewButtonClassName, stockAgent = false, menuItems = [], showMenu = false, width, testId, }) => {
|
|
49
49
|
const [hovered, setHovered] = useState(false);
|
|
50
50
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
51
51
|
const [imageError, setImageError] = useState(false);
|
|
@@ -58,6 +58,9 @@ export const EntityCard = ({ view = 'avatar', name, image, description = '', sho
|
|
|
58
58
|
const isProcessing = processing && !error;
|
|
59
59
|
const canPreview = !!onPreview && !isProcessing && !error;
|
|
60
60
|
const showActions = canPreview || hasMenu;
|
|
61
|
+
const cardAction = onCardClick ?? (canPreview ? onPreview : undefined);
|
|
62
|
+
const cardActionLabel = onCardClick ? name : (previewButtonText ?? name);
|
|
63
|
+
const isSelected = !!cardAction && selected;
|
|
61
64
|
const active = hovered || menuOpen;
|
|
62
65
|
const showBadge = isAgent && stockAgent && !error;
|
|
63
66
|
const showImage = !!image && !imageError && !error;
|
|
@@ -69,16 +72,20 @@ export const EntityCard = ({ view = 'avatar', name, image, description = '', sho
|
|
|
69
72
|
};
|
|
70
73
|
return (_jsxs(Box, { width: cardWidth, display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', bgcolor: active
|
|
71
74
|
? 'var(--color-background-bg-card-highlight-hover)'
|
|
72
|
-
: 'var(--color-background-bg-card-highlight)', border: '1px solid var(--color-border-border-subtle)', boxSizing: 'border-box', overflow: 'hidden', onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), role:
|
|
75
|
+
: 'var(--color-background-bg-card-highlight)', border: '1px solid var(--color-border-border-subtle)', boxSizing: 'border-box', overflow: 'hidden', onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), role: cardAction ? 'button' : undefined, tabIndex: cardAction ? 0 : undefined, "aria-label": cardAction ? cardActionLabel : undefined, "aria-pressed": cardAction ? isSelected : undefined, onClick: cardAction, onKeyDown: cardAction
|
|
73
76
|
? (event) => {
|
|
74
77
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
75
78
|
event.preventDefault();
|
|
76
|
-
|
|
79
|
+
cardAction();
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
: undefined, sx: {
|
|
80
83
|
transition: 'background-color 0.15s ease-in-out',
|
|
81
|
-
cursor:
|
|
84
|
+
cursor: cardAction ? 'pointer' : 'default',
|
|
85
|
+
...(isSelected && {
|
|
86
|
+
outline: '2px solid var(--color-text-text-accent)',
|
|
87
|
+
outlineOffset: '2px',
|
|
88
|
+
}),
|
|
82
89
|
[`&:hover .${ACTIONS_CLASS}, &:focus-within .${ACTIONS_CLASS}`]: {
|
|
83
90
|
opacity: 1,
|
|
84
91
|
pointerEvents: 'auto',
|
|
@@ -6,6 +6,8 @@ type Story = StoryObj<typeof EntityCard>;
|
|
|
6
6
|
export declare const Default: Story;
|
|
7
7
|
export declare const AgentView: Story;
|
|
8
8
|
export declare const WithDescription: Story;
|
|
9
|
+
export declare const Selectable: Story;
|
|
10
|
+
export declare const SelectableSelected: Story;
|
|
9
11
|
export declare const NoActions: Story;
|
|
10
12
|
export declare const NoImage: Story;
|
|
11
13
|
export declare const VideoView: Story;
|
|
@@ -72,6 +72,18 @@ const meta = {
|
|
|
72
72
|
description: 'Preview handler — renders the action button on hover',
|
|
73
73
|
table: { type: { summary: 'function' } },
|
|
74
74
|
},
|
|
75
|
+
onCardClick: {
|
|
76
|
+
description: 'Card click handler — makes the whole card clickable for its primary action, leaving `onPreview` to its action button alone',
|
|
77
|
+
table: { type: { summary: 'function' } },
|
|
78
|
+
},
|
|
79
|
+
selected: {
|
|
80
|
+
control: 'boolean',
|
|
81
|
+
description: 'Marks a clickable card as the picked option — accent outline + `aria-pressed`',
|
|
82
|
+
table: {
|
|
83
|
+
type: { summary: 'boolean' },
|
|
84
|
+
defaultValue: { summary: 'false' },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
75
87
|
previewButtonText: {
|
|
76
88
|
control: 'text',
|
|
77
89
|
description: 'Label for the action button; when set it renders text + icon, otherwise icon-only',
|
|
@@ -168,6 +180,34 @@ export const WithDescription = {
|
|
|
168
180
|
},
|
|
169
181
|
},
|
|
170
182
|
};
|
|
183
|
+
export const Selectable = {
|
|
184
|
+
args: {
|
|
185
|
+
name: 'Nelly',
|
|
186
|
+
image: avatarImage,
|
|
187
|
+
onCardClick: () => { },
|
|
188
|
+
onPreview: () => { },
|
|
189
|
+
},
|
|
190
|
+
parameters: {
|
|
191
|
+
docs: {
|
|
192
|
+
description: {
|
|
193
|
+
story: 'A selectable card — clicking anywhere on it runs `onCardClick` (e.g. picking the avatar), while the hover action button keeps `onPreview` to itself.',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
export const SelectableSelected = {
|
|
199
|
+
args: {
|
|
200
|
+
...Selectable.args,
|
|
201
|
+
selected: true,
|
|
202
|
+
},
|
|
203
|
+
parameters: {
|
|
204
|
+
docs: {
|
|
205
|
+
description: {
|
|
206
|
+
story: 'The picked option — accent outline and `aria-pressed="true"`.',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
171
211
|
export const NoActions = {
|
|
172
212
|
args: {
|
|
173
213
|
name: 'Nelly',
|
|
@@ -31,6 +31,14 @@ export interface EntityCardProps {
|
|
|
31
31
|
error?: boolean;
|
|
32
32
|
/** Preview handler — renders the action button on hover */
|
|
33
33
|
onPreview?: () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Card click handler — makes the whole card clickable for its primary action
|
|
36
|
+
* (e.g. selecting the entity). When set it takes over the card's click and
|
|
37
|
+
* keyboard activation, leaving `onPreview` to its action button alone.
|
|
38
|
+
*/
|
|
39
|
+
onCardClick?: () => void;
|
|
40
|
+
/** Marks a clickable card as the picked option — accent outline + `aria-pressed` */
|
|
41
|
+
selected?: boolean;
|
|
34
42
|
/** Label for the action button; when set it renders text + icon, otherwise icon-only */
|
|
35
43
|
previewButtonText?: string;
|
|
36
44
|
/** Icon for the action button (defaults to `Play`) */
|
|
@@ -4,6 +4,7 @@ import { SelectAvatarCardProps } from './types';
|
|
|
4
4
|
* SelectAvatarCard — a media card for the currently selected avatar. Mirrors the
|
|
5
5
|
* Figma "Selected Avatar" component with its Filled (Default + Hover), Empty and
|
|
6
6
|
* Error states. In the Filled state the Change action is always visible and the
|
|
7
|
-
* Preview action
|
|
7
|
+
* Preview action sits next to it as an icon-only button, expanding to its label
|
|
8
|
+
* over a purple scrim while the card is hovered.
|
|
8
9
|
*/
|
|
9
10
|
export declare const SelectAvatarCard: React.FC<SelectAvatarCardProps>;
|
|
@@ -1,35 +1,44 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { Box } from '@mui/material';
|
|
3
4
|
import { SelectAvatarCardState } from './types';
|
|
4
5
|
import { Typography, TypographySize, TypographyWeight } from '../Typography';
|
|
5
|
-
import { MainButton, ButtonHierarchy, ButtonSize, ButtonShape, } from '../Buttons';
|
|
6
|
+
import { MainButton, ButtonHierarchy, ButtonSize, ButtonShape, ButtonDisplayMode, } from '../Buttons';
|
|
6
7
|
import { getIconComponent } from '../../utils';
|
|
7
8
|
// Scrim behind the avatar name, matching the Figma "name section" gradient
|
|
8
9
|
// (transparent at the top, fading to ~64% #2d2637 at the bottom).
|
|
9
10
|
const NAME_GRADIENT = 'linear-gradient(180deg, rgba(45, 38, 55, 0) 0%, rgba(45, 38, 55, 0.047) 11%, rgba(45, 38, 55, 0.094) 20%, rgba(45, 38, 55, 0.16) 34%, rgba(45, 38, 55, 0.64) 100%)';
|
|
11
|
+
/**
|
|
12
|
+
* Purple scrim laid over the preview while the card is hovered — the second
|
|
13
|
+
* fill on the Figma Hover variant's image. Ramps to 16% #4019b6 over the top
|
|
14
|
+
* fifth, then deepens to 35% of a darker purple at the bottom.
|
|
15
|
+
*/
|
|
16
|
+
const HOVER_OVERLAY = 'linear-gradient(180deg, rgba(64, 25, 182, 0) 0%, rgba(64, 25, 182, 0.16) 18%, rgba(30, 10, 80, 0.35) 100%)';
|
|
10
17
|
/**
|
|
11
18
|
* SelectAvatarCard — a media card for the currently selected avatar. Mirrors the
|
|
12
19
|
* Figma "Selected Avatar" component with its Filled (Default + Hover), Empty and
|
|
13
20
|
* Error states. In the Filled state the Change action is always visible and the
|
|
14
|
-
* Preview action
|
|
21
|
+
* Preview action sits next to it as an icon-only button, expanding to its label
|
|
22
|
+
* over a purple scrim while the card is hovered.
|
|
15
23
|
*/
|
|
16
24
|
export const SelectAvatarCard = ({ state = SelectAvatarCardState.Filled, showHeader = true, headerTitle = 'Selected Avatar', avatarImage, avatarName, showChangeButton = true, changeButtonText = 'Change', onChange, showPreviewButton = true, previewButtonText = 'Preview', onPreview, selectButtonText = 'Select Avatar', onSelect, width = '552px', height = '344px', style, testId, }) => {
|
|
25
|
+
const [hovered, setHovered] = useState(false);
|
|
17
26
|
const isFilled = state === SelectAvatarCardState.Filled;
|
|
18
27
|
const isEmpty = state === SelectAvatarCardState.Empty;
|
|
19
28
|
const isError = state === SelectAvatarCardState.Error;
|
|
29
|
+
// Only a Filled card with something to act on has a Hover variant; without
|
|
30
|
+
// actions it stays in its Default look and does not need tracking.
|
|
31
|
+
const tracksHover = isFilled && (showChangeButton || showPreviewButton);
|
|
32
|
+
const showHoverState = tracksHover && hovered;
|
|
33
|
+
const setHoveredWhenTracked = tracksHover ? setHovered : undefined;
|
|
20
34
|
const mediaBackground = isEmpty
|
|
21
35
|
? 'var(--color-background-bg-element)'
|
|
22
36
|
: isError
|
|
23
37
|
? 'var(--color-background-bg-button-disabled)'
|
|
24
38
|
: 'transparent';
|
|
25
|
-
return (_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", width: width, height: height, boxSizing: "border-box", padding: "var(--spacing-tokens-size-in-px-spacing-spacing-xl)", borderRadius: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", bgcolor: "var(--color-background-bg-page-contrast)", sx: {
|
|
39
|
+
return (_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", width: width, height: height, boxSizing: "border-box", padding: "var(--spacing-tokens-size-in-px-spacing-spacing-xl)", borderRadius: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", bgcolor: "var(--color-background-bg-page-contrast)", onMouseEnter: () => setHoveredWhenTracked?.(true), onMouseLeave: () => setHoveredWhenTracked?.(false), onFocus: () => setHoveredWhenTracked?.(true), onBlur: () => setHoveredWhenTracked?.(false), sx: {
|
|
26
40
|
border: '1px solid var(--color-border-border-subtle)',
|
|
27
41
|
boxShadow: '0px 2px 4px 0px var(--global-shadows-shadow-xs-color)',
|
|
28
|
-
// Reveal the Preview pill when the card is hovered.
|
|
29
|
-
'&:hover .select-avatar-card__preview': {
|
|
30
|
-
opacity: 1,
|
|
31
|
-
visibility: 'visible',
|
|
32
|
-
},
|
|
33
42
|
...style,
|
|
34
43
|
}, "data-testid": testId ?? 'select-avatar-card', children: [showHeader && (_jsx(Box, { display: "flex", alignItems: "center", width: "100%", children: _jsx(Typography, { size: TypographySize.HeadingXS, weight: TypographyWeight.SemiBold, color: "var(--color-text-text-title)", textStyle: { flex: '1 0 0', minWidth: 0, wordBreak: 'break-word' }, children: headerTitle }) })), _jsxs(Box, { position: "relative", flex: "1 0 0", minHeight: 0, width: "100%", overflow: "hidden", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-sm)", bgcolor: mediaBackground, children: [isFilled && (_jsxs(_Fragment, { children: [avatarImage && (_jsx(Box, { component: "img", src: avatarImage, alt: avatarName ?? '', position: "absolute", sx: {
|
|
35
44
|
inset: 0,
|
|
@@ -37,11 +46,25 @@ export const SelectAvatarCard = ({ state = SelectAvatarCardState.Filled, showHea
|
|
|
37
46
|
height: '100%',
|
|
38
47
|
objectFit: 'cover',
|
|
39
48
|
pointerEvents: 'none',
|
|
40
|
-
} })),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
} })), avatarImage && (_jsx(Box, { "aria-hidden": true, position: "absolute", sx: {
|
|
50
|
+
inset: 0,
|
|
51
|
+
background: HOVER_OVERLAY,
|
|
52
|
+
opacity: showHoverState ? 1 : 0,
|
|
53
|
+
transition: 'opacity 0.15s ease-in-out',
|
|
54
|
+
pointerEvents: 'none',
|
|
55
|
+
} })), _jsxs(Box, { position: "absolute", top: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", right: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", display: "flex", alignItems: "flex-start", justifyContent: "flex-end", gap: "10px", children: [showPreviewButton && (_jsx(MainButton, { ...(showHoverState
|
|
56
|
+
? { showLeadingIcon: true, leadingIcon: 'Play' }
|
|
57
|
+
: {
|
|
58
|
+
displayIcon: 'Play',
|
|
59
|
+
displayIconMode: ButtonDisplayMode.Only,
|
|
60
|
+
}), text: previewButtonText, size: ButtonSize.xxs, shape: ButtonShape.Square, hierarchy: ButtonHierarchy.SecondaryTransparent,
|
|
61
|
+
// Figma draws the expanded pill in the button's own hover
|
|
62
|
+
// fill, so the card's hover carries it too.
|
|
63
|
+
style: showHoverState
|
|
64
|
+
? {
|
|
65
|
+
background: 'var(--color-background-bg-button-transparent-hover)',
|
|
66
|
+
}
|
|
67
|
+
: undefined, onClick: onPreview, testId: testId ? `${testId}-preview` : undefined })), showChangeButton && (_jsx(MainButton, { text: changeButtonText, size: ButtonSize.xxs, hierarchy: ButtonHierarchy.WhiteText, showLeadingIcon: true, leadingIcon: "ArrowsLeftRight", onClick: onChange, testId: testId ? `${testId}-change` : undefined }))] }), avatarName && (_jsx(Box, { position: "absolute", left: 0, right: 0, bottom: 0, display: "flex", alignItems: "center", padding: "24px 12px 12px", sx: { background: NAME_GRADIENT }, children: _jsx(Typography, { size: TypographySize.HeadingXS, weight: TypographyWeight.SemiBold, color: "var(--color-text-text-white)", textStyle: {
|
|
45
68
|
flex: '1 0 0',
|
|
46
69
|
minWidth: 0,
|
|
47
70
|
wordBreak: 'break-word',
|
|
@@ -4,8 +4,9 @@ declare const meta: Meta<typeof SelectAvatarCard>;
|
|
|
4
4
|
export default meta;
|
|
5
5
|
type Story = StoryObj<typeof SelectAvatarCard>;
|
|
6
6
|
/**
|
|
7
|
-
* An avatar is selected.
|
|
8
|
-
*
|
|
7
|
+
* An avatar is selected. Change sits next to a collapsed Preview button; hover
|
|
8
|
+
* the card to expand Preview to its label and lay the purple scrim over the
|
|
9
|
+
* preview image.
|
|
9
10
|
*/
|
|
10
11
|
export declare const Default: Story;
|
|
11
12
|
/** No avatar picked yet — a centered call to action to select one. */
|
|
@@ -16,6 +17,6 @@ export declare const Error: Story;
|
|
|
16
17
|
export declare const WithoutHeader: Story;
|
|
17
18
|
/**
|
|
18
19
|
* All four states side by side, matching the Figma frame. Hover the first card
|
|
19
|
-
*
|
|
20
|
+
* for its Hover variant.
|
|
20
21
|
*/
|
|
21
22
|
export declare const AllStates: Story;
|
|
@@ -28,7 +28,7 @@ const meta = {
|
|
|
28
28
|
changeButtonText: { control: 'text' },
|
|
29
29
|
showPreviewButton: {
|
|
30
30
|
control: 'boolean',
|
|
31
|
-
description: '
|
|
31
|
+
description: 'Show the Preview action, expanded on hover (Filled)',
|
|
32
32
|
},
|
|
33
33
|
previewButtonText: { control: 'text' },
|
|
34
34
|
selectButtonText: { control: 'text' },
|
|
@@ -41,8 +41,9 @@ const meta = {
|
|
|
41
41
|
};
|
|
42
42
|
export default meta;
|
|
43
43
|
/**
|
|
44
|
-
* An avatar is selected.
|
|
45
|
-
*
|
|
44
|
+
* An avatar is selected. Change sits next to a collapsed Preview button; hover
|
|
45
|
+
* the card to expand Preview to its label and lay the purple scrim over the
|
|
46
|
+
* preview image.
|
|
46
47
|
*/
|
|
47
48
|
export const Default = {
|
|
48
49
|
args: {
|
|
@@ -72,7 +73,7 @@ export const WithoutHeader = {
|
|
|
72
73
|
};
|
|
73
74
|
/**
|
|
74
75
|
* All four states side by side, matching the Figma frame. Hover the first card
|
|
75
|
-
*
|
|
76
|
+
* for its Hover variant.
|
|
76
77
|
*/
|
|
77
78
|
export const AllStates = {
|
|
78
79
|
parameters: { layout: 'padded' },
|
|
@@ -550,7 +550,10 @@ export interface SelectAvatarCardProps {
|
|
|
550
550
|
changeButtonText?: string;
|
|
551
551
|
/** Called when the Change button is clicked. */
|
|
552
552
|
onChange?: () => void;
|
|
553
|
-
/**
|
|
553
|
+
/**
|
|
554
|
+
* Show the Preview action in the `Filled` state — icon-only at rest, expanding
|
|
555
|
+
* to its label while the card is hovered. Defaults to `true`.
|
|
556
|
+
*/
|
|
554
557
|
showPreviewButton?: boolean;
|
|
555
558
|
/** Preview button label. Defaults to "Preview". */
|
|
556
559
|
previewButtonText?: string;
|