@beydesign/storybook 0.7.3 → 0.7.4

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.
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useState, useEffect, useCallback, useRef } from 'react';
3
3
  import { Box } from '@mui/material';
4
4
  import { keyframes } from '@emotion/react';
5
5
  import { ArrowsClockwise, Warning } from '@phosphor-icons/react';
6
6
  import { ASPECT_RATIO_BY_VIEW, WIDTH_BY_VIEW } from './constants';
7
7
  import { AvatarSilhouette } from './AvatarSilhouette';
8
8
  import { Typography, TypographySize, TypographyWeight } from '../Typography';
9
- import { Menu, Badge, BadgeColor, BadgeSize } from '../UI';
9
+ import { Menu, Badge, BadgeColor, BadgeSize, SkeletonLoader } from '../UI';
10
10
  import { MainButton, ButtonHierarchy, ButtonSize, ButtonDisplayMode, } from '../Buttons';
11
11
  /** Empty-state label per view variant, shown over the silhouette when there is no image. */
12
12
  const EMPTY_TEXT_BY_VIEW = {
@@ -34,10 +34,24 @@ const spin = keyframes `
34
34
  `;
35
35
  /** A continuously rotating refresh icon, matching the AgentCard processing spinner. */
36
36
  const Spinner = () => (_jsx(Box, { display: 'flex', sx: { animation: `${spin} 1.5s linear infinite` }, children: _jsx(ArrowsClockwise, { size: 24, color: 'var(--color-text-text-white)' }) }));
37
- 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, }) => {
37
+ export const EntityCard = ({ view = 'avatar', name, image, videoUrl, description = '', showDescription = false, date, aspectRatio, processing = false, error = false, onPreview, onCardClick, selected = false, previewButtonText, previewIcon = 'Play', previewButtonClassName, stockAgent = false, menuItems = [], showMenu = false, width, testId, }) => {
38
38
  const [hovered, setHovered] = useState(false);
39
39
  const [menuOpen, setMenuOpen] = useState(false);
40
40
  const [imageError, setImageError] = useState(false);
41
+ const [videoLoading, setVideoLoading] = useState(!!videoUrl);
42
+ const [videoError, setVideoError] = useState(false);
43
+ const videoRef = useRef(null);
44
+ useEffect(() => {
45
+ setVideoLoading(!!videoUrl);
46
+ setVideoError(false);
47
+ }, [videoUrl]);
48
+ const handleVideoLoadedData = useCallback(() => {
49
+ setVideoLoading(false);
50
+ }, []);
51
+ const handleVideoError = useCallback(() => {
52
+ setVideoLoading(false);
53
+ setVideoError(true);
54
+ }, []);
41
55
  const isVideo = view === 'video';
42
56
  const isAgent = view === 'agent';
43
57
  const cardWidth = width ?? WIDTH_BY_VIEW[view];
@@ -53,6 +67,7 @@ export const EntityCard = ({ view = 'avatar', name, image, description = '', sho
53
67
  const active = hovered || menuOpen;
54
68
  const showBadge = isAgent && stockAgent && !error;
55
69
  const showImage = !!image && !imageError && !error;
70
+ const showVideo = !showImage && !!videoUrl && !videoError && !error;
56
71
  const emptyText = EMPTY_TEXT_BY_VIEW[view];
57
72
  const resolvedTestId = testId ?? `${entityKey}-${isVideo ? 'video' : 'entity'}-card`;
58
73
  const handlePreviewButtonClick = (event) => {
@@ -79,7 +94,12 @@ export const EntityCard = ({ view = 'avatar', name, image, description = '', sho
79
94
  opacity: 1,
80
95
  pointerEvents: 'auto',
81
96
  },
82
- }, "data-testid": resolvedTestId, children: [_jsxs(Box, { position: 'relative', width: '100%', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', overflow: 'hidden', sx: { aspectRatio: imageAspectRatio }, children: [error ? (_jsx(Box, { width: '100%', height: '100%', bgcolor: 'var(--color-background-bg-badge-accent-red)', display: 'flex', justifyContent: 'center', alignItems: 'center', role: 'img', "aria-label": ERROR_TEXT, title: ERROR_TEXT, children: _jsx(Warning, { size: 50, color: 'var(--color-foreground-fg-error)' }) })) : showImage ? (_jsx("img", { src: image, alt: name, width: '100%', height: '100%', style: { objectFit: 'cover', height: '100%', width: '100%' }, onError: () => setImageError(true) })) : (_jsxs(Box, { position: 'relative', width: '100%', height: '100%', overflow: 'hidden', bgcolor: view === 'agent'
97
+ }, "data-testid": resolvedTestId, children: [_jsxs(Box, { position: 'relative', width: '100%', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', overflow: 'hidden', sx: { aspectRatio: imageAspectRatio }, children: [error ? (_jsx(Box, { width: '100%', height: '100%', bgcolor: 'var(--color-background-bg-badge-accent-red)', display: 'flex', justifyContent: 'center', alignItems: 'center', role: 'img', "aria-label": ERROR_TEXT, title: ERROR_TEXT, children: _jsx(Warning, { size: 50, color: 'var(--color-foreground-fg-error)' }) })) : showImage ? (_jsx("img", { src: image, alt: name, width: '100%', height: '100%', style: { objectFit: 'cover', height: '100%', width: '100%' }, onError: () => setImageError(true) })) : showVideo ? (_jsxs(Box, { width: '100%', height: '100%', position: 'relative', children: [_jsx("video", { ref: videoRef, src: videoUrl, muted: true, playsInline: true, preload: 'metadata', onLoadedData: handleVideoLoadedData, onError: handleVideoError, style: {
98
+ objectFit: 'cover',
99
+ width: '100%',
100
+ height: '100%',
101
+ display: videoLoading ? 'none' : 'block',
102
+ } }), videoLoading && (_jsx(Box, { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', children: _jsx(SkeletonLoader, {}) }))] })) : (_jsxs(Box, { position: 'relative', width: '100%', height: '100%', overflow: 'hidden', bgcolor: view === 'agent'
83
103
  ? 'var(--color-background-bg-component-disabled)'
84
104
  : 'var(--color-background-bg-button-disabled)', children: [_jsx(Box, { position: 'absolute', bottom: 0, left: '50%', sx: {
85
105
  transform: 'translateX(-50%)',
@@ -16,3 +16,6 @@ export declare const VideoNoImage: Story;
16
16
  export declare const ErrorState: Story;
17
17
  export declare const VideoError: Story;
18
18
  export declare const Processing: Story;
19
+ export declare const VideoPreview: Story;
20
+ export declare const VideoPreviewLoading: Story;
21
+ export declare const VideoPreviewFailure: Story;
@@ -30,6 +30,11 @@ const meta = {
30
30
  description: 'Image url',
31
31
  table: { type: { summary: 'string' } },
32
32
  },
33
+ videoUrl: {
34
+ control: 'text',
35
+ description: 'Video url — displays a paused video frame when no image is supplied',
36
+ table: { type: { summary: 'string' } },
37
+ },
33
38
  description: {
34
39
  control: 'text',
35
40
  description: 'Optional description shown below the image',
@@ -351,3 +356,59 @@ export const Processing = {
351
356
  },
352
357
  },
353
358
  };
359
+ const sampleVideoUrl = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4';
360
+ export const VideoPreview = {
361
+ args: {
362
+ view: 'video',
363
+ name: 'Product launch announcement',
364
+ videoUrl: sampleVideoUrl,
365
+ date: 'Jul 6, 2026',
366
+ onPreview: () => { },
367
+ previewButtonText: 'Preview',
368
+ showMenu: true,
369
+ menuItems: videoMenuItems,
370
+ },
371
+ parameters: {
372
+ docs: {
373
+ description: {
374
+ story: 'Video view using `videoUrl` instead of a static image — the card displays a paused video frame. The video is muted, inline, and cropped with `object-fit: cover`.',
375
+ },
376
+ },
377
+ },
378
+ };
379
+ export const VideoPreviewLoading = {
380
+ args: {
381
+ view: 'video',
382
+ name: 'Product launch announcement',
383
+ videoUrl: 'https://httpbin.org/delay/30',
384
+ date: 'Jul 6, 2026',
385
+ onPreview: () => { },
386
+ previewButtonText: 'Preview',
387
+ },
388
+ parameters: {
389
+ docs: {
390
+ description: {
391
+ story: 'While the video frame loads, a shimmer skeleton fills the media area.',
392
+ },
393
+ },
394
+ },
395
+ };
396
+ export const VideoPreviewFailure = {
397
+ args: {
398
+ view: 'video',
399
+ name: 'Product launch announcement',
400
+ videoUrl: 'https://invalid.test/missing.mp4',
401
+ date: 'Jul 6, 2026',
402
+ onPreview: () => { },
403
+ previewButtonText: 'Preview',
404
+ showMenu: true,
405
+ menuItems: videoMenuItems,
406
+ },
407
+ parameters: {
408
+ docs: {
409
+ description: {
410
+ story: 'When the video fails to load, the card falls back to the standard empty-state placeholder (silhouette + "Video is not available").',
411
+ },
412
+ },
413
+ },
414
+ };
@@ -17,6 +17,8 @@ export interface EntityCardProps {
17
17
  name: string;
18
18
  /** Image url */
19
19
  image?: string;
20
+ /** Display a paused video frame when no image is supplied. */
21
+ videoUrl?: string;
20
22
  /** Optional description shown below the image */
21
23
  description?: string;
22
24
  /** Whether to show the description */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beydesign/storybook",
3
3
  "private": false,
4
- "version": "0.7.3",
4
+ "version": "0.7.4",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",