@beydesign/storybook 0.5.0 → 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.
@@ -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
  };
@@ -26,7 +26,8 @@ const StyledSocialButton = styled.button `
26
26
  ? '100%'
27
27
  : 'auto'
28
28
  : 'var(--spacing-tokens-size-in-px-spacing-spacing-6xl)'};
29
- padding: 0 var(--spacing-tokens-size-in-px-spacing-spacing-lg);
29
+ /* Figma specs 9px; spacing-md (8px) is the nearest token */
30
+ padding: 0 var(--spacing-tokens-size-in-px-spacing-spacing-md);
30
31
  border: none;
31
32
  border-radius: var(--spacing-tokens-size-in-px-radius-radius-md);
32
33
  background: var(--color-background-bg-card-highlight);
@@ -2,10 +2,25 @@ import React from 'react';
2
2
  interface SocialIconProps {
3
3
  size?: number;
4
4
  }
5
- /** Full-color Google "G" logo. */
5
+ /**
6
+ * Full-color Google "G" logo.
7
+ *
8
+ * Vector data exported from Figma (node 2707:49504). The four segments are
9
+ * positioned so the glyph spans the full 24×24 box, exactly as in the design.
10
+ */
6
11
  export declare const GoogleIcon: React.FC<SocialIconProps>;
7
- /** Full-color Microsoft logo (four squares). */
12
+ /**
13
+ * Full-color Microsoft logo (four squares).
14
+ *
15
+ * Geometry and colors taken from Figma (node 2609:109007): 9×9 squares inset
16
+ * 2px from the 24×24 box with a 2px gutter between them.
17
+ */
8
18
  export declare const MicrosoftIcon: React.FC<SocialIconProps>;
9
- /** Monochrome Apple logo — uses currentColor so it follows the button text color (theme-aware). */
19
+ /**
20
+ * Monochrome Apple logo — uses currentColor so it follows the button text color (theme-aware).
21
+ *
22
+ * Vector data exported from Figma (node 2609:109006); the glyph spans the full
23
+ * 24px height and is inset 2px from the left edge, matching the design.
24
+ */
10
25
  export declare const AppleIcon: React.FC<SocialIconProps>;
11
26
  export {};
@@ -1,7 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /** Full-color Google "G" logo. */
3
- export const GoogleIcon = ({ size = 24 }) => (_jsxs("svg", { width: size, height: size, viewBox: "0 0 48 48", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", children: [_jsx("path", { fill: "#FFC107", d: "M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z" }), _jsx("path", { fill: "#FF3D00", d: "M6.306 14.691l6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 16.318 4 9.656 8.337 6.306 14.691z" }), _jsx("path", { fill: "#4CAF50", d: "M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238C29.211 35.091 26.715 36 24 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z" }), _jsx("path", { fill: "#1976D2", d: "M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 0 1-4.087 5.571l6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z" })] }));
4
- /** Full-color Microsoft logo (four squares). */
5
- export const MicrosoftIcon = ({ size = 24 }) => (_jsxs("svg", { width: size, height: size, viewBox: "0 0 23 23", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", children: [_jsx("path", { fill: "#F25022", d: "M1 1h10v10H1z" }), _jsx("path", { fill: "#7FBA00", d: "M12 1h10v10H12z" }), _jsx("path", { fill: "#00A4EF", d: "M1 12h10v10H1z" }), _jsx("path", { fill: "#FFB900", d: "M12 12h10v10H12z" })] }));
6
- /** Monochrome Apple logo uses currentColor so it follows the button text color (theme-aware). */
7
- export const AppleIcon = ({ size = 24 }) => (_jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M17.05 12.536c-.026-2.682 2.19-3.972 2.29-4.033-1.25-1.827-3.193-2.077-3.883-2.104-1.652-.168-3.225.973-4.063.973-.838 0-2.13-.949-3.504-.923-1.803.027-3.466 1.048-4.393 2.663-1.873 3.246-.479 8.05 1.344 10.682.891 1.288 1.954 2.733 3.348 2.681 1.34-.053 1.846-.866 3.466-.866 1.62 0 2.075.866 3.494.84 1.44-.027 2.355-1.314 3.236-2.606 1.018-1.495 1.438-2.942 1.463-3.017-.032-.013-2.808-1.077-2.835-4.266zM14.78 4.62c.74-.896 1.24-2.142 1.103-3.383-1.066.043-2.357.71-3.122 1.606-.686.795-1.286 2.063-1.124 3.28 1.19.092 2.404-.605 3.143-1.503z" }) }));
2
+ /**
3
+ * Full-color Google "G" logo.
4
+ *
5
+ * Vector data exported from Figma (node 2707:49504). The four segments are
6
+ * positioned so the glyph spans the full 24×24 box, exactly as in the design.
7
+ */
8
+ export const GoogleIcon = ({ size = 24 }) => (_jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", children: [_jsx("g", { transform: "translate(12.24 9.8376)", children: _jsx("path", { fill: "#4285F4", d: "M11.526 2.43836C11.526 1.62263 11.4599 0.802499 11.3188 0H0V4.62098H6.48173C6.21276 6.11133 5.34853 7.42973 4.08305 8.2675V11.2658H7.95004C10.2209 9.17582 11.526 6.08929 11.526 2.43836Z" }) }), _jsx("g", { transform: "translate(1.5168 14.2992)", children: _jsx("path", { fill: "#34A853", d: "M10.7235 9.70053C13.96 9.70053 16.6893 8.63788 18.6779 6.8036L14.811 3.80525C13.7351 4.5372 12.3461 4.95168 10.7279 4.95168C7.59728 4.95168 4.94286 2.83961 3.99045 0H0V3.09094C2.03711 7.14312 6.1863 9.70053 10.7235 9.70053Z" }) }), _jsx("g", { transform: "translate(0.24 6.6144)", children: _jsx("path", { fill: "#FBBC04", d: "M5.26254 7.68547C4.75988 6.19511 4.75988 4.5813 5.26254 3.09094V0H1.2765C-0.425501 3.39078 -0.425501 7.38563 1.2765 10.7764L5.26254 7.68547Z" }) }), _jsx("g", { transform: "translate(1.5168 0)", children: _jsx("path", { fill: "#EA4335", d: "M10.7235 4.74966C12.4343 4.7232 14.0878 5.36697 15.3268 6.54867L18.7529 3.12262C16.5835 1.0855 13.7042 -0.034466 10.7235 0.000808666C6.18629 0.000808666 2.03711 2.55822 0 6.61481L3.98604 9.70575C4.93404 6.86173 7.59287 4.74966 10.7235 4.74966Z" }) })] }));
9
+ /**
10
+ * Full-color Microsoft logo (four squares).
11
+ *
12
+ * Geometry and colors taken from Figma (node 2609:109007): 9×9 squares inset
13
+ * 2px from the 24×24 box with a 2px gutter between them.
14
+ */
15
+ export const MicrosoftIcon = ({ size = 24 }) => (_jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", children: [_jsx("rect", { x: "2", y: "2", width: "9", height: "9", fill: "#F25325" }), _jsx("rect", { x: "13", y: "2", width: "9", height: "9", fill: "#80BC06" }), _jsx("rect", { x: "2", y: "13", width: "9", height: "9", fill: "#05A6F0" }), _jsx("rect", { x: "13", y: "13", width: "9", height: "9", fill: "#FEBA08" })] }));
16
+ /**
17
+ * Monochrome Apple logo — uses currentColor so it follows the button text color (theme-aware).
18
+ *
19
+ * Vector data exported from Figma (node 2609:109006); the glyph spans the full
20
+ * 24px height and is inset 2px from the left edge, matching the design.
21
+ */
22
+ export const AppleIcon = ({ size = 24 }) => (_jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", children: _jsx("g", { transform: "translate(2 0)", children: _jsx("path", { fill: "currentColor", d: "M19.792 18.7033C19.429 19.5418 18.9994 20.3136 18.5016 21.0232C17.8231 21.9906 17.2676 22.6602 16.8395 23.0321C16.1758 23.6424 15.4647 23.955 14.7032 23.9728C14.1566 23.9728 13.4973 23.8172 12.73 23.5017C11.9601 23.1876 11.2525 23.0321 10.6056 23.0321C9.92709 23.0321 9.19941 23.1876 8.42106 23.5017C7.64153 23.8172 7.01355 23.9817 6.53342 23.9979C5.80322 24.0291 5.07539 23.7076 4.3489 23.0321C3.88521 22.6276 3.30523 21.9343 2.61043 20.9521C1.86498 19.9033 1.25211 18.687 0.771975 17.3004C0.25777 15.8026 0 14.3523 0 12.9482C0 11.3398 0.347544 9.95259 1.04367 8.79011C1.59076 7.85636 2.31859 7.11979 3.22953 6.57906C4.14046 6.03834 5.12473 5.76279 6.18469 5.74516C6.76467 5.74516 7.52524 5.92457 8.4704 6.27715C9.41288 6.63091 10.0181 6.81032 10.2834 6.81032C10.4817 6.81032 11.154 6.60054 12.2937 6.18234C13.3714 5.7945 14.281 5.63391 15.0262 5.69717C17.0454 5.86012 18.5624 6.6561 19.5712 8.09013C17.7654 9.18432 16.8721 10.7169 16.8898 12.6829C16.9061 14.2142 17.4617 15.4886 18.5535 16.5004C19.0483 16.97 19.6009 17.333 20.2156 17.5907C20.0823 17.9774 19.9416 18.3477 19.792 18.7033ZM15.161 0.480137C15.161 1.68041 14.7225 2.8011 13.8484 3.83841C12.7937 5.07155 11.5179 5.78413 10.1343 5.67168C10.1167 5.52769 10.1065 5.37614 10.1065 5.21688C10.1065 4.06462 10.6081 2.83147 11.4989 1.82321C11.9436 1.3127 12.5092 0.888228 13.1951 0.549615C13.8796 0.216055 14.5269 0.031589 15.1358 0C15.1536 0.160458 15.161 0.320926 15.161 0.480137Z" }) }) }));
@@ -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: canPreview ? 'button' : undefined, tabIndex: canPreview ? 0 : undefined, "aria-label": canPreview ? (previewButtonText ?? name) : undefined, onClick: canPreview ? onPreview : undefined, onKeyDown: canPreview
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
- onPreview?.();
79
+ cardAction();
77
80
  }
78
81
  }
79
82
  : undefined, sx: {
80
83
  transition: 'background-color 0.15s ease-in-out',
81
- cursor: canPreview ? 'pointer' : 'default',
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`) */
@@ -192,10 +192,10 @@ export const PromoBanner = ({ title = 'Real-time AI avatars for every conversati
192
192
  width: 'fit-content',
193
193
  '& button': { width: '100%' },
194
194
  },
195
- }, children: [promoCode && (_jsxs(Box, { position: "relative", display: "flex", children: [_jsx(MainButton, { text: promoCode, size: ButtonSize.md, hierarchy: ButtonHierarchy.SecondaryColor, showTrailingIcon: true, trailingIcon: copied ? 'Check' : 'Copy', trailingIconSlot: actionsMotion ? (_jsx(DissolveIcon, { icon: copied ? 'Check' : 'Copy', size: 20, color: "currentColor" })) : undefined, onClick: handleCopyPromoCode, style: {
195
+ }, children: [promoCode && (_jsxs(Box, { position: "relative", display: "flex", children: [_jsx(MainButton, { text: promoCode, size: ButtonSize.xxs, hierarchy: ButtonHierarchy.SecondaryColor, showTrailingIcon: true, trailingIcon: copied ? 'Check' : 'Copy', trailingIconSlot: actionsMotion ? (_jsx(DissolveIcon, { icon: copied ? 'Check' : 'Copy', size: 20, color: "currentColor" })) : undefined, onClick: handleCopyPromoCode, style: {
196
196
  border: '1px dashed var(--color-border-border-secondary-action)',
197
197
  height: '100%',
198
- }, testId: testId ? `${testId}-promo-code` : undefined }), promoCodeLabel && (_jsx(Box, { position: "absolute", top: "-13px", left: "-8px", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-xs)", padding: "var(--spacing-tokens-size-in-px-spacing-spacing-xxs) var(--spacing-tokens-size-in-px-spacing-spacing-sm)", whiteSpace: "nowrap", zIndex: 1, sx: {
198
+ }, testId: testId ? `${testId}-promo-code` : undefined }), promoCodeLabel && (_jsx(Box, { position: "absolute", top: "-13px", left: "-8px", display: "flex", alignItems: "center", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-xs)", paddingX: "var(--spacing-tokens-size-in-px-spacing-spacing-sm)", paddingTop: "calc(var(--spacing-tokens-size-in-px-spacing-spacing-xxs) + 1px)", paddingBottom: "var(--spacing-tokens-size-in-px-spacing-spacing-xxs)", whiteSpace: "nowrap", zIndex: 1, sx: {
199
199
  // On copy the pill morphs from the violet "promo code"
200
200
  // badge into the DS success badge, easing the colour so the
201
201
  // state change feels intentional rather than an instant swap.
@@ -215,5 +215,5 @@ export const PromoBanner = ({ title = 'Real-time AI avatars for every conversati
215
215
  // pin the label to the matching on-colour text token.
216
216
  color: copied
217
217
  ? 'var(--color-text-text-success-on-color)'
218
- : 'var(--color-text-text-dark)', children: copied ? 'COPIED!' : promoCodeLabel }) }))] })), resolvedCtaText && (_jsx(MainButton, { text: resolvedCtaText, size: ButtonSize.md, hierarchy: ButtonHierarchy.Primary, onClick: onCtaClick, loading: ctaLoading, disabled: ctaDisabled, testId: testId ? `${testId}-cta` : undefined }))] })), !isMobile && footerText && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-title)", textStyle: { width: '100%' }, children: footerText }))] }), renderAvatarMatrix(RIGHT_COLUMNS)] }));
218
+ : 'var(--color-text-text-dark)', children: copied ? 'COPIED!' : promoCodeLabel }) }))] })), resolvedCtaText && (_jsx(MainButton, { text: resolvedCtaText, size: ButtonSize.xxs, hierarchy: ButtonHierarchy.Primary, onClick: onCtaClick, loading: ctaLoading, disabled: ctaDisabled, testId: testId ? `${testId}-cta` : undefined }))] })), !isMobile && footerText && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-title)", textStyle: { width: '100%' }, children: footerText }))] }), renderAvatarMatrix(RIGHT_COLUMNS)] }));
219
219
  };
@@ -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 is revealed on hover.
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 is revealed on hover.
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
- } })), _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(Box, { className: "select-avatar-card__preview", sx: {
41
- opacity: 0,
42
- visibility: 'hidden',
43
- transition: 'opacity 0.15s ease-in-out',
44
- }, children: _jsx(MainButton, { text: previewButtonText, size: ButtonSize.xxs, hierarchy: ButtonHierarchy.SecondaryTransparent, showLeadingIcon: true, leadingIcon: "Play", 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: {
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. The Change action is always visible; hover the card to
8
- * reveal the Preview action.
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
- * to reveal its Preview action.
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: 'Reveal the Preview action on hover (Filled)',
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. The Change action is always visible; hover the card to
45
- * reveal the Preview action.
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
- * to reveal its Preview action.
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
- /** Reveal the Preview action on hover in the `Filled` state. Defaults to `true`. */
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;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beydesign/storybook",
3
3
  "private": false,
4
- "version": "0.5.0",
4
+ "version": "0.5.2",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",