@beydesign/storybook 0.2.16 → 0.2.17
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/UI/ButtonCard.js +6 -1
- package/dist/stories/UI/ConversationalAvatarsBanner.d.ts +3 -0
- package/dist/stories/UI/ConversationalAvatarsBanner.js +52 -0
- package/dist/stories/UI/ConversationalAvatarsBanner.stories.d.ts +7 -0
- package/dist/stories/UI/ConversationalAvatarsBanner.stories.js +76 -0
- package/dist/stories/UI/ConversationalVoiceAgentsBanner.d.ts +3 -0
- package/dist/stories/UI/ConversationalVoiceAgentsBanner.js +42 -0
- package/dist/stories/UI/ConversationalVoiceAgentsBanner.stories.d.ts +7 -0
- package/dist/stories/UI/ConversationalVoiceAgentsBanner.stories.js +104 -0
- package/dist/stories/UI/PromoBanner.js +51 -13
- package/dist/stories/UI/PromoBanner.stories.js +24 -23
- package/dist/stories/UI/bannerAvatars.d.ts +12 -0
- package/dist/stories/UI/bannerAvatars.js +20 -0
- package/dist/stories/UI/index.d.ts +2 -1
- package/dist/stories/UI/index.js +2 -1
- package/dist/stories/UI/types.d.ts +40 -26
- package/dist/stories/UI/types.js +0 -9
- package/package.json +1 -1
- package/dist/stories/UI/FeatureBanner.d.ts +0 -3
- package/dist/stories/UI/FeatureBanner.js +0 -50
- package/dist/stories/UI/FeatureBanner.stories.d.ts +0 -10
- package/dist/stories/UI/FeatureBanner.stories.js +0 -163
|
@@ -23,7 +23,12 @@ export const ButtonCard = ({ title, description, icon = 'UserFocus', iconColor =
|
|
|
23
23
|
boxShadow: `var(--global-shadows-shadow-xs-offset-x) var(--global-shadows-shadow-xs-offset-y) var(--global-shadows-shadow-xs-blur) var(--global-shadows-shadow-xs-spread) var(--global-shadows-shadow-xs-color)`,
|
|
24
24
|
},
|
|
25
25
|
}),
|
|
26
|
-
}, children: [_jsxs(Box, { display: 'flex', flex: '1 0 0', minWidth: 0, alignItems: 'flex-start', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', children: [_jsx(Box, { display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, width: '34px', height: '34px', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', bgcolor: iconBackgroundColor,
|
|
26
|
+
}, children: [_jsxs(Box, { display: 'flex', flex: '1 0 0', minWidth: 0, alignItems: 'flex-start', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', children: [_jsx(Box, { display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, width: '34px', height: '34px', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', bgcolor: iconBackgroundColor,
|
|
27
|
+
// Without border-box the 34px tile grows to 50px (34 + 8px padding
|
|
28
|
+
// each side); Figma sizes the tile at a fixed 34×34. The 24px icon
|
|
29
|
+
// must not shrink to fit the 18px content box — like Figma it stays
|
|
30
|
+
// 24px and overflows the nominal padding, centered in the tile.
|
|
31
|
+
boxSizing: 'border-box', sx: { '& svg': { flexShrink: 0 } }, children: getIconComponent(icon, { size: 24, color: iconColor }) }), _jsxs(Box, { display: 'flex', flex: '1 0 0', minWidth: 0, flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', alignItems: 'flex-start', sx: { wordBreak: 'break-word' }, children: [_jsx(Typography, { size: TypographySize.HeadingS, weight: TypographyWeight.Bold, color: 'var(--color-text-text-title)', textStyle: { textAlign: 'left' }, children: title }), description && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: 'var(--color-text-text-paragraph)', textStyle: { textAlign: 'left' }, children: description }))] })] }), showActionButton && (_jsx(Box, { position: 'absolute', top: '6px', right: '8px', children: _jsx(MainButton, { text: actionButtonText, hierarchy: ButtonHierarchy.TertiaryColor, size: ButtonSize.xxs, onClick: (e) => {
|
|
27
32
|
e.stopPropagation();
|
|
28
33
|
onActionClick?.();
|
|
29
34
|
}, testId: `${cardKey}-action-button` }) }))] }));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from '@mui/material';
|
|
3
|
+
import { Typography, TypographySize, TypographyWeight } from '../Typography';
|
|
4
|
+
import { MainButton, ButtonHierarchy, ButtonSize } from '../Buttons';
|
|
5
|
+
import { BANNER_AVATARS, AVATAR_TILE_BACKGROUND, AVATAR_TILE_WIDTH, } from './bannerAvatars';
|
|
6
|
+
const DEFAULT_BACKGROUND = 'linear-gradient(180deg, rgba(194, 171, 252, 0.08) 0%, rgba(128, 96, 198, 0.2) 100%)';
|
|
7
|
+
// The avatar column stays on the right at every breakpoint (unlike FeatureBanner
|
|
8
|
+
// which stacks its media), so the banner only reduces its padding/gap on mobile.
|
|
9
|
+
const MOBILE = '@media (max-width: 640px)';
|
|
10
|
+
// Height the card holds so the avatar collage shows the hero tile plus a sliver
|
|
11
|
+
// of the tiles above/below.
|
|
12
|
+
const BANNER_MIN_HEIGHT = 198;
|
|
13
|
+
// Figma tile size — 130×150; the 198px card centers the middle (hero) tile and
|
|
14
|
+
// clips the tiles above/below to slivers.
|
|
15
|
+
const TILE_HEIGHT = 150;
|
|
16
|
+
// On mobile the tighter card can't fit a full tile, so the collage bleeds this
|
|
17
|
+
// many extra pixels past the right edge — the tile's right side (and rounded
|
|
18
|
+
// corner) is clipped, matching the Figma mobile layout.
|
|
19
|
+
const MOBILE_TILE_CUTOFF = 15;
|
|
20
|
+
// Avatars shown in the collage column (reused from the PromoBanner set). The
|
|
21
|
+
// middle tile is the fully-visible hero; the rest are clipped by the height.
|
|
22
|
+
const IMAGE_COLUMN = [
|
|
23
|
+
BANNER_AVATARS.fjolla,
|
|
24
|
+
BANNER_AVATARS.micheal,
|
|
25
|
+
BANNER_AVATARS.yuruo,
|
|
26
|
+
];
|
|
27
|
+
export const ConversationalAvatarsBanner = ({ title, description, actions = [], background = DEFAULT_BACKGROUND, style: customStyle, testId, }) => {
|
|
28
|
+
const renderAction = (action, index) => (_jsx(MainButton, { text: action.text, size: ButtonSize.md, hierarchy: action.primary
|
|
29
|
+
? ButtonHierarchy.Primary
|
|
30
|
+
: ButtonHierarchy.SecondaryColor, onClick: action.onClick, testId: testId ? `${testId}-action-${index}` : undefined }, `${action.text}-${index}`));
|
|
31
|
+
return (_jsxs(Box, { display: "flex", alignItems: "center", gap: "40px", width: "100%", minHeight: `${BANNER_MIN_HEIGHT}px`, overflow: "hidden", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-lg)", padding: "var(--spacing-tokens-size-in-px-spacing-spacing-3xl) var(--spacing-tokens-size-in-px-spacing-spacing-2xl)", sx: {
|
|
32
|
+
background,
|
|
33
|
+
boxSizing: 'border-box',
|
|
34
|
+
[MOBILE]: {
|
|
35
|
+
gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)',
|
|
36
|
+
padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-xl) var(--spacing-tokens-size-in-px-spacing-spacing-lg)',
|
|
37
|
+
},
|
|
38
|
+
...customStyle,
|
|
39
|
+
}, "data-testid": testId, children: [_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-xl)", flex: 1, minWidth: 0, children: [_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: [title && (_jsx(Typography, { size: TypographySize.HeadingM, weight: TypographyWeight.Bold, color: "var(--color-text-text-title)", children: title })), description && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-label)", children: description }))] }), actions.length > 0 && (_jsx(Box, { display: "flex", alignItems: "center", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: actions.map(renderAction) }))] }), _jsx(Box, { flexShrink: 0, alignSelf: "stretch", position: "relative", overflow: "hidden", width: `${AVATAR_TILE_WIDTH}px`, sx: {
|
|
40
|
+
marginY: 'calc(-1 * var(--spacing-tokens-size-in-px-spacing-spacing-3xl))',
|
|
41
|
+
[MOBILE]: {
|
|
42
|
+
marginY: 'calc(-1 * var(--spacing-tokens-size-in-px-spacing-spacing-xl))',
|
|
43
|
+
// Push the strip past the right edge so the tile is clipped there.
|
|
44
|
+
marginRight: `calc(-1 * var(--spacing-tokens-size-in-px-spacing-spacing-lg) - ${MOBILE_TILE_CUTOFF}px)`,
|
|
45
|
+
},
|
|
46
|
+
}, children: _jsx(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", position: "absolute", left: 0, right: 0, top: "50%", sx: { transform: 'translateY(-50%)' }, children: IMAGE_COLUMN.map((image, index) => (_jsx(Box, { component: "img", src: image, alt: "", width: "100%", height: `${TILE_HEIGHT}px`, flexShrink: 0, sx: {
|
|
47
|
+
objectFit: 'cover',
|
|
48
|
+
borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)',
|
|
49
|
+
background: AVATAR_TILE_BACKGROUND,
|
|
50
|
+
display: 'block',
|
|
51
|
+
} }, index))) }) })] }));
|
|
52
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ConversationalAvatarsBanner } from './ConversationalAvatarsBanner';
|
|
3
|
+
declare const meta: Meta<typeof ConversationalAvatarsBanner>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof ConversationalAvatarsBanner>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const Mobile: Story;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ConversationalAvatarsBanner } from './ConversationalAvatarsBanner';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Design System/Components/UI/ConversationalAvatarsBanner',
|
|
4
|
+
component: ConversationalAvatarsBanner,
|
|
5
|
+
parameters: {
|
|
6
|
+
layout: 'padded',
|
|
7
|
+
design: {
|
|
8
|
+
type: 'figspec',
|
|
9
|
+
url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=7081-24780',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
argTypes: {
|
|
14
|
+
title: {
|
|
15
|
+
control: 'text',
|
|
16
|
+
description: 'Title of the banner',
|
|
17
|
+
table: { type: { summary: 'string' } },
|
|
18
|
+
},
|
|
19
|
+
description: {
|
|
20
|
+
control: 'text',
|
|
21
|
+
description: 'Description shown below the title',
|
|
22
|
+
table: { type: { summary: 'string' } },
|
|
23
|
+
},
|
|
24
|
+
actions: {
|
|
25
|
+
control: 'object',
|
|
26
|
+
description: 'Action buttons — set primary: true for the brand (violet) button',
|
|
27
|
+
table: { type: { summary: 'BannerAction[]' } },
|
|
28
|
+
},
|
|
29
|
+
background: {
|
|
30
|
+
control: 'text',
|
|
31
|
+
description: 'Background of the banner',
|
|
32
|
+
table: { type: { summary: 'string' } },
|
|
33
|
+
},
|
|
34
|
+
style: {
|
|
35
|
+
control: 'object',
|
|
36
|
+
description: 'Custom style for the banner',
|
|
37
|
+
table: { type: { summary: 'object' } },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export default meta;
|
|
42
|
+
export const Default = {
|
|
43
|
+
args: {
|
|
44
|
+
title: 'Introducing Conversational Avatars',
|
|
45
|
+
description: 'Bring your ideas to life with intelligent conversational avatars.',
|
|
46
|
+
actions: [{ text: 'Create New Agent' }],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const FIGMA_MOBILE_VIEWPORT = {
|
|
50
|
+
viewport: {
|
|
51
|
+
viewports: {
|
|
52
|
+
figmaMobile: {
|
|
53
|
+
name: 'Figma Mobile (375px)',
|
|
54
|
+
styles: { width: '375px', height: '812px' },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
defaultViewport: 'figmaMobile',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
export const Mobile = {
|
|
61
|
+
args: {
|
|
62
|
+
...Default.args,
|
|
63
|
+
},
|
|
64
|
+
parameters: {
|
|
65
|
+
layout: 'padded',
|
|
66
|
+
...FIGMA_MOBILE_VIEWPORT,
|
|
67
|
+
docs: {
|
|
68
|
+
description: {
|
|
69
|
+
story: 'The avatar banner on the mobile Home screen — unlike the feature ' +
|
|
70
|
+
'banner it keeps the avatar collage as a narrow strip on the right ' +
|
|
71
|
+
'instead of stacking. Best viewed in the Canvas with the preset ' +
|
|
72
|
+
'mobile viewport.',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from '@mui/material';
|
|
3
|
+
import { Typography, TypographySize, TypographyWeight } from '../Typography';
|
|
4
|
+
import { MainButton, ButtonHierarchy, ButtonSize, ButtonDisplayMode, } from '../Buttons';
|
|
5
|
+
import { BANNER_VIDEO_PREVIEW } from './bannerAvatars';
|
|
6
|
+
const PINK_GRADIENT = 'linear-gradient(90deg, rgba(235, 205, 240, 0.05) 0%, rgba(202, 59, 224, 0.15) 100%)';
|
|
7
|
+
// Below this width the banner stacks vertically to match the Figma mobile
|
|
8
|
+
// layout (text + actions on top, video preview full-width below).
|
|
9
|
+
const MOBILE = '@media (max-width: 640px)';
|
|
10
|
+
// The video preview keeps the Figma 296×150 tile on desktop.
|
|
11
|
+
const PREVIEW_WIDTH = 296;
|
|
12
|
+
const PREVIEW_HEIGHT = 150;
|
|
13
|
+
export const ConversationalVoiceAgentsBanner = ({ title, description, actions = [], previewImage = BANNER_VIDEO_PREVIEW, onPlayClick, previewCtaText, onPreviewCtaClick, background = PINK_GRADIENT, style: customStyle, testId, }) => {
|
|
14
|
+
const renderAction = (action, index) => (_jsx(MainButton, { text: action.text, size: ButtonSize.md, hierarchy: action.primary
|
|
15
|
+
? ButtonHierarchy.Primary
|
|
16
|
+
: ButtonHierarchy.SecondaryColor, onClick: action.onClick, testId: testId ? `${testId}-action-${index}` : undefined }, `${action.text}-${index}`));
|
|
17
|
+
return (_jsxs(Box, { display: "flex", alignItems: "center", gap: "40px", width: "100%", overflow: "hidden", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-lg)", padding: "var(--spacing-tokens-size-in-px-spacing-spacing-3xl) var(--spacing-tokens-size-in-px-spacing-spacing-2xl)", sx: {
|
|
18
|
+
background,
|
|
19
|
+
boxSizing: 'border-box',
|
|
20
|
+
[MOBILE]: {
|
|
21
|
+
flexDirection: 'column',
|
|
22
|
+
alignItems: 'stretch',
|
|
23
|
+
gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-3xl)',
|
|
24
|
+
padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-xl) var(--spacing-tokens-size-in-px-spacing-spacing-lg)',
|
|
25
|
+
},
|
|
26
|
+
...customStyle,
|
|
27
|
+
}, "data-testid": testId, children: [_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-xl)", flex: 1, minWidth: 0, children: [_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: [title && (_jsx(Typography, { size: TypographySize.HeadingM, weight: TypographyWeight.Bold, color: "var(--color-text-text-title)", children: title })), description && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-label)", children: description }))] }), actions.length > 0 && (_jsx(Box, { display: "flex", alignItems: "center", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", sx: {
|
|
28
|
+
// On mobile the two actions split the row into equal-width
|
|
29
|
+
// buttons, matching the Figma mobile layout.
|
|
30
|
+
[MOBILE]: {
|
|
31
|
+
width: '100%',
|
|
32
|
+
'& > *': { flex: 1, minWidth: 0 },
|
|
33
|
+
'& button': { width: '100%' },
|
|
34
|
+
},
|
|
35
|
+
}, children: actions.map(renderAction) }))] }), _jsxs(Box, { position: "relative", flexShrink: 0, overflow: "hidden", width: `${PREVIEW_WIDTH}px`, height: `${PREVIEW_HEIGHT}px`, borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-sm)", sx: {
|
|
36
|
+
[MOBILE]: {
|
|
37
|
+
width: '100%',
|
|
38
|
+
height: 'auto',
|
|
39
|
+
aspectRatio: `${PREVIEW_WIDTH} / ${PREVIEW_HEIGHT}`,
|
|
40
|
+
},
|
|
41
|
+
}, children: [_jsx(Box, { component: "img", src: previewImage, alt: "", width: "100%", height: "100%", sx: { objectFit: 'cover', display: 'block' } }), _jsx(Box, { position: "absolute", left: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", bottom: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: _jsx(MainButton, { size: ButtonSize.md, hierarchy: ButtonHierarchy.SecondaryTransparent, displayIconMode: ButtonDisplayMode.Only, displayIcon: "Play", onClick: onPlayClick, style: { border: 'none' }, testId: testId ? `${testId}-play` : undefined }) }), previewCtaText && (_jsx(Box, { position: "absolute", right: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", bottom: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: _jsx(MainButton, { text: previewCtaText, size: ButtonSize.md, hierarchy: ButtonHierarchy.SecondaryTransparent, onClick: onPreviewCtaClick, style: { border: 'none' }, testId: testId ? `${testId}-preview-cta` : undefined }) }))] })] }));
|
|
42
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ConversationalVoiceAgentsBanner } from './ConversationalVoiceAgentsBanner';
|
|
3
|
+
declare const meta: Meta<typeof ConversationalVoiceAgentsBanner>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof ConversationalVoiceAgentsBanner>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const Mobile: Story;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ConversationalVoiceAgentsBanner } from './ConversationalVoiceAgentsBanner';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Design System/Components/UI/ConversationalVoiceAgentsBanner',
|
|
4
|
+
component: ConversationalVoiceAgentsBanner,
|
|
5
|
+
parameters: {
|
|
6
|
+
layout: 'padded',
|
|
7
|
+
design: {
|
|
8
|
+
type: 'figspec',
|
|
9
|
+
url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=6938-20061',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
argTypes: {
|
|
14
|
+
title: {
|
|
15
|
+
control: 'text',
|
|
16
|
+
description: 'Title of the banner',
|
|
17
|
+
table: { type: { summary: 'string' } },
|
|
18
|
+
},
|
|
19
|
+
description: {
|
|
20
|
+
control: 'text',
|
|
21
|
+
description: 'Description shown below the title',
|
|
22
|
+
table: { type: { summary: 'string' } },
|
|
23
|
+
},
|
|
24
|
+
actions: {
|
|
25
|
+
control: 'object',
|
|
26
|
+
description: 'Action buttons — set primary: true for the brand (violet) button',
|
|
27
|
+
table: { type: { summary: 'BannerAction[]' } },
|
|
28
|
+
},
|
|
29
|
+
previewImage: {
|
|
30
|
+
control: 'text',
|
|
31
|
+
description: 'Thumbnail/poster image URL for the video preview',
|
|
32
|
+
table: { type: { summary: 'string' } },
|
|
33
|
+
},
|
|
34
|
+
onPlayClick: {
|
|
35
|
+
action: 'play clicked',
|
|
36
|
+
description: 'Called when the play button on the video preview is clicked',
|
|
37
|
+
table: { type: { summary: 'function' } },
|
|
38
|
+
},
|
|
39
|
+
previewCtaText: {
|
|
40
|
+
control: 'text',
|
|
41
|
+
description: 'CTA text overlaid on the video preview',
|
|
42
|
+
table: { type: { summary: 'string' } },
|
|
43
|
+
},
|
|
44
|
+
onPreviewCtaClick: {
|
|
45
|
+
action: 'preview cta clicked',
|
|
46
|
+
description: 'Called when the preview CTA is clicked',
|
|
47
|
+
table: { type: { summary: 'function' } },
|
|
48
|
+
},
|
|
49
|
+
background: {
|
|
50
|
+
control: 'text',
|
|
51
|
+
description: 'Background of the banner',
|
|
52
|
+
table: { type: { summary: 'string' } },
|
|
53
|
+
},
|
|
54
|
+
style: {
|
|
55
|
+
control: 'object',
|
|
56
|
+
description: 'Custom style for the banner',
|
|
57
|
+
table: { type: { summary: 'object' } },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
export default meta;
|
|
62
|
+
export const Default = {
|
|
63
|
+
args: {
|
|
64
|
+
title: 'Add a Human Face to your Conversational Voice Agents',
|
|
65
|
+
description: 'Bring your ideas to life with intelligent conversational avatars.',
|
|
66
|
+
actions: [
|
|
67
|
+
{ text: 'Create API Key' },
|
|
68
|
+
{ text: 'Go to Docs', primary: true },
|
|
69
|
+
],
|
|
70
|
+
previewCtaText: 'How to Get Started',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
const FIGMA_MOBILE_VIEWPORT = {
|
|
74
|
+
viewport: {
|
|
75
|
+
viewports: {
|
|
76
|
+
figmaMobile: {
|
|
77
|
+
name: 'Figma Mobile (375px)',
|
|
78
|
+
styles: { width: '375px', height: '812px' },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
defaultViewport: 'figmaMobile',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
export const Mobile = {
|
|
85
|
+
args: {
|
|
86
|
+
...Default.args,
|
|
87
|
+
},
|
|
88
|
+
parameters: {
|
|
89
|
+
layout: 'padded',
|
|
90
|
+
...FIGMA_MOBILE_VIEWPORT,
|
|
91
|
+
design: {
|
|
92
|
+
type: 'figspec',
|
|
93
|
+
url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=7081-24780',
|
|
94
|
+
},
|
|
95
|
+
docs: {
|
|
96
|
+
description: {
|
|
97
|
+
story: 'The conversational agents banner on the mobile Home screen — text ' +
|
|
98
|
+
'and actions stack on top, the video preview spans full-width below. ' +
|
|
99
|
+
'Below 640px the component reflows automatically; best viewed in the ' +
|
|
100
|
+
'Canvas with the preset mobile viewport.',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
@@ -1,31 +1,65 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box } from '@mui/material';
|
|
2
|
+
import { Box, useMediaQuery } from '@mui/material';
|
|
3
3
|
import { Typography, TypographySize, TypographyWeight } from '../Typography';
|
|
4
4
|
import { MainButton, ButtonHierarchy, ButtonSize } from '../Buttons';
|
|
5
|
+
import { BANNER_AVATARS as AVATARS, AVATAR_TILE_BACKGROUND, } from './bannerAvatars';
|
|
5
6
|
const DEFAULT_BACKGROUND = 'linear-gradient(180deg, rgba(194, 171, 252, 0.08) 0%, rgba(128, 96, 198, 0.2) 100%)';
|
|
6
|
-
// Below this width the banner
|
|
7
|
-
//
|
|
8
|
-
|
|
7
|
+
// Below this width the banner switches to the Figma mobile variant: text
|
|
8
|
+
// centered, features/footer hidden, promo code + CTA stacked, and the mobile*
|
|
9
|
+
// content overrides applied.
|
|
10
|
+
const MOBILE_QUERY = '(max-width: 640px)';
|
|
11
|
+
const MOBILE = `@media ${MOBILE_QUERY}`;
|
|
12
|
+
// The side collages only fit alongside the 458px content column at the Figma
|
|
13
|
+
// frame width (~1122px). Below that they would crush the content and overflow
|
|
14
|
+
// the buttons, so hide them and let the content center on its own.
|
|
15
|
+
const HIDE_COLLAGE = '@media (max-width: 1121px)';
|
|
16
|
+
// Width of the Figma text/content column. Capping the middle content keeps the
|
|
17
|
+
// side collages adjacent to the text on wide (e.g. Full HD) viewports instead
|
|
18
|
+
// of stretching apart; the row stays centered so extra space becomes gutters.
|
|
19
|
+
const CONTENT_MAX_WIDTH = '458px';
|
|
20
|
+
// The middle tile of each column is the fully-visible "hero"; the first and
|
|
21
|
+
// last tiles are clipped to slivers above/below by the banner height. Columns
|
|
22
|
+
// are centered in the banner, then `offset` shifts them vertically (negative =
|
|
23
|
+
// up) so the two columns of each side interlock, matching the Figma layout.
|
|
24
|
+
const LEFT_COLUMNS = [
|
|
25
|
+
{ offset: 0, images: [AVATARS.jerome, AVATARS.micheal, AVATARS.jerome] },
|
|
26
|
+
{ offset: -40, images: [AVATARS.laura, AVATARS.nelly, AVATARS.fjolla] },
|
|
27
|
+
];
|
|
28
|
+
const RIGHT_COLUMNS = [
|
|
29
|
+
{ offset: 0, images: [AVATARS.jerome, AVATARS.fjolla, AVATARS.yuruo] },
|
|
30
|
+
{ offset: -20, images: [AVATARS.micheal, AVATARS.laura, AVATARS.nelly] },
|
|
31
|
+
];
|
|
9
32
|
export const PromoBanner = ({ title = 'Real-time AI avatars for every conversation.', description = 'Trusted by developers, startups, educators, and enterprises to build engaging AI experiences at scale:', features = [
|
|
10
33
|
'Real-Time',
|
|
11
34
|
'Multilingual',
|
|
12
35
|
'API-first',
|
|
13
36
|
'Enterprise-ready',
|
|
14
37
|
'GDPR Compliant',
|
|
15
|
-
], promoCode, promoCodeLabel = 'YOUR PROMO CODE', onCopyPromoCode, ctaText, onCtaClick, footerText,
|
|
38
|
+
], promoCode, promoCodeLabel = 'YOUR PROMO CODE', onCopyPromoCode, ctaText, onCtaClick, footerText, mobileTitle, mobileDescription, mobileCtaText, showImages = true, background = DEFAULT_BACKGROUND, style: customStyle, testId, }) => {
|
|
39
|
+
// Below 640px the banner renders the Figma mobile variant, swapping to any
|
|
40
|
+
// provided mobile* copy and falling back to the desktop content otherwise.
|
|
41
|
+
const isMobile = useMediaQuery(MOBILE_QUERY);
|
|
42
|
+
const resolvedTitle = isMobile && mobileTitle ? mobileTitle : title;
|
|
43
|
+
const resolvedDescription = isMobile && mobileDescription ? mobileDescription : description;
|
|
44
|
+
const resolvedCtaText = isMobile && mobileCtaText ? mobileCtaText : ctaText;
|
|
16
45
|
const handleCopyPromoCode = () => {
|
|
17
46
|
if (!promoCode)
|
|
18
47
|
return;
|
|
19
48
|
navigator.clipboard?.writeText(promoCode);
|
|
20
49
|
onCopyPromoCode?.(promoCode);
|
|
21
50
|
};
|
|
22
|
-
|
|
51
|
+
// Features are part of the desktop-only layout (hidden on the mobile variant).
|
|
52
|
+
const featuresText = !isMobile && features?.length
|
|
23
53
|
? features.map((feature) => `✓ ${feature}`).join(', ') + '.'
|
|
24
54
|
: '';
|
|
25
|
-
const
|
|
26
|
-
if (!showImages
|
|
55
|
+
const renderAvatarMatrix = (columns) => {
|
|
56
|
+
if (!showImages)
|
|
27
57
|
return null;
|
|
28
|
-
return (_jsx(Box, {
|
|
58
|
+
return (_jsx(Box, { display: "flex", alignItems: "center", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-lg)", height: "100%", flexShrink: 0, overflow: "hidden", sx: { [HIDE_COLLAGE]: { display: 'none' } }, children: columns.map((column, columnIndex) => (_jsx(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", flexShrink: 0, sx: { transform: `translateY(${column.offset}px)` }, children: column.images.map((image, imageIndex) => (_jsx(Box, { component: "img", src: image, alt: "", width: "130px", height: "150px", flexShrink: 0, sx: {
|
|
59
|
+
objectFit: 'cover',
|
|
60
|
+
borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)',
|
|
61
|
+
background: AVATAR_TILE_BACKGROUND,
|
|
62
|
+
} }, imageIndex))) }, columnIndex))) }));
|
|
29
63
|
};
|
|
30
64
|
return (_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "center", gap: "40px", height: "220px", width: "100%", overflow: "hidden", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-lg)", paddingX: "var(--spacing-tokens-size-in-px-spacing-spacing-2xl)", sx: {
|
|
31
65
|
background,
|
|
@@ -36,17 +70,21 @@ export const PromoBanner = ({ title = 'Real-time AI avatars for every conversati
|
|
|
36
70
|
paddingX: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)',
|
|
37
71
|
},
|
|
38
72
|
...customStyle,
|
|
39
|
-
}, "data-testid": testId, children: [
|
|
73
|
+
}, "data-testid": testId, children: [renderAvatarMatrix(LEFT_COLUMNS), _jsxs(Box, { display: "flex", flexDirection: "column", alignItems: "center", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-2xl)", flex: 1, minWidth: 0, maxWidth: CONTENT_MAX_WIDTH, paddingY: "var(--spacing-tokens-size-in-px-spacing-spacing-3xl)", children: [_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", width: "100%",
|
|
74
|
+
// Text is centered on the mobile variant (inherited by the children).
|
|
75
|
+
sx: { [MOBILE]: { textAlign: 'center' } }, children: [resolvedTitle && (_jsx(Typography, { size: TypographySize.HeadingM, weight: TypographyWeight.Bold, color: "var(--color-text-text-title)", textStyle: { textAlign: 'center' }, children: resolvedTitle })), (resolvedDescription || featuresText) && (_jsxs(Box, { display: "flex", flexDirection: "column", children: [resolvedDescription && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-title)", children: resolvedDescription })), featuresText && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Bold, color: "var(--color-text-text-title)", children: featuresText }))] }))] }), (promoCode || resolvedCtaText) && (_jsxs(Box, { display: "flex", justifyContent: "center", alignItems: "stretch", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", sx: {
|
|
76
|
+
// On mobile the promo code + CTA stack and share the wider CTA's
|
|
77
|
+
// width (centered), matching Figma — not stretched full-width.
|
|
40
78
|
[MOBILE]: {
|
|
41
79
|
flexDirection: 'column',
|
|
42
|
-
width: '
|
|
80
|
+
width: 'fit-content',
|
|
43
81
|
'& button': { width: '100%' },
|
|
44
82
|
},
|
|
45
83
|
}, children: [promoCode && (_jsxs(Box, { position: "relative", display: "flex", children: [_jsx(MainButton, { text: promoCode, size: ButtonSize.md, hierarchy: ButtonHierarchy.SecondaryColor, showTrailingIcon: true, trailingIcon: "Copy", onClick: handleCopyPromoCode, style: {
|
|
46
|
-
border: '1px dashed var(--color-border-border-
|
|
84
|
+
border: '1px dashed var(--color-border-border-secondary-action)',
|
|
47
85
|
height: '100%',
|
|
48
86
|
}, testId: testId ? `${testId}-promo-code` : undefined }), promoCodeLabel && (_jsx(Box, { position: "absolute", top: "-13px", left: "-8px", bgcolor: "var(--color-background-bg-icon-badge)", 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, children: _jsx(Typography, { size: TypographySize.Text2XS, weight: TypographyWeight.SemiBold,
|
|
49
87
|
// Invariant violet label (bg-icon-badge) -> pin to the
|
|
50
88
|
// invariant dark text so it stays legible in both themes.
|
|
51
|
-
color: "var(--color-text-text-dark)", children: promoCodeLabel }) }))] })),
|
|
89
|
+
color: "var(--color-text-text-dark)", children: promoCodeLabel }) }))] })), resolvedCtaText && (_jsx(MainButton, { text: resolvedCtaText, size: ButtonSize.md, hierarchy: ButtonHierarchy.Primary, onClick: onCtaClick, 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)] }));
|
|
52
90
|
};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { PromoBanner } from './PromoBanner';
|
|
2
|
-
import bannerAvatarsLeft from '../assets/banner/banner-avatars-left.png';
|
|
3
|
-
import bannerAvatarsRight from '../assets/banner/banner-avatars-right.png';
|
|
4
2
|
const meta = {
|
|
5
3
|
title: 'Design System/Components/UI/PromoBanner',
|
|
6
4
|
component: PromoBanner,
|
|
@@ -76,23 +74,30 @@ const meta = {
|
|
|
76
74
|
type: { summary: 'string' },
|
|
77
75
|
},
|
|
78
76
|
},
|
|
79
|
-
|
|
77
|
+
mobileTitle: {
|
|
80
78
|
control: 'text',
|
|
81
|
-
description: '
|
|
79
|
+
description: 'Title override used on the mobile variant (< 640px)',
|
|
82
80
|
table: {
|
|
83
81
|
type: { summary: 'string' },
|
|
84
82
|
},
|
|
85
83
|
},
|
|
86
|
-
|
|
84
|
+
mobileDescription: {
|
|
87
85
|
control: 'text',
|
|
88
|
-
description: '
|
|
86
|
+
description: 'Description override used on the mobile variant (< 640px)',
|
|
87
|
+
table: {
|
|
88
|
+
type: { summary: 'string' },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
mobileCtaText: {
|
|
92
|
+
control: 'text',
|
|
93
|
+
description: 'CTA text override used on the mobile variant (< 640px)',
|
|
89
94
|
table: {
|
|
90
95
|
type: { summary: 'string' },
|
|
91
96
|
},
|
|
92
97
|
},
|
|
93
98
|
showImages: {
|
|
94
99
|
control: 'boolean',
|
|
95
|
-
description: 'Whether to show the avatar
|
|
100
|
+
description: 'Whether to show the avatar collage on both sides',
|
|
96
101
|
table: {
|
|
97
102
|
type: { summary: 'boolean' },
|
|
98
103
|
},
|
|
@@ -129,8 +134,10 @@ export const Default = {
|
|
|
129
134
|
promoCodeLabel: 'YOUR PROMO CODE',
|
|
130
135
|
ctaText: 'Enjoy 1 month free on Starter',
|
|
131
136
|
footerText: 'Use Promo code at checkout and get 90 End-to-End API minutes or 180 Audio-to-Video API minutes free.',
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
// Below 640px the banner renders the Figma mobile variant with this copy.
|
|
138
|
+
mobileTitle: 'Welcome! Get your free Starter plan for 1 month',
|
|
139
|
+
mobileDescription: "For a limited time, enjoy 1 month free on our starter plan (worth $50). That's 9k credits for 90 End-to-End API minutes or 180 Audio-to-Video API minutes. Just apply the code below at checkout.",
|
|
140
|
+
mobileCtaText: 'Activate My Free Starter Plan',
|
|
134
141
|
},
|
|
135
142
|
};
|
|
136
143
|
export const WithoutImages = {
|
|
@@ -153,8 +160,6 @@ export const TitleAndCtaOnly = {
|
|
|
153
160
|
description: undefined,
|
|
154
161
|
features: [],
|
|
155
162
|
ctaText: 'Get Started',
|
|
156
|
-
leftImage: bannerAvatarsLeft,
|
|
157
|
-
rightImage: bannerAvatarsRight,
|
|
158
163
|
},
|
|
159
164
|
};
|
|
160
165
|
const FIGMA_MOBILE_VIEWPORT = {
|
|
@@ -169,15 +174,10 @@ const FIGMA_MOBILE_VIEWPORT = {
|
|
|
169
174
|
},
|
|
170
175
|
};
|
|
171
176
|
export const Mobile = {
|
|
177
|
+
// Same props as Default — below 640px the component swaps to the mobile*
|
|
178
|
+
// content overrides and the Figma mobile layout automatically.
|
|
172
179
|
args: {
|
|
173
|
-
|
|
174
|
-
description: "For a limited time, enjoy 1 month free on our starter plan (worth $50). That's 9k credits for 90 End-to-End API minutes or 180 Audio-to-Video API minutes. Just apply the code below at checkout.",
|
|
175
|
-
features: [],
|
|
176
|
-
promoCode: 'PKAJ2HGO',
|
|
177
|
-
promoCodeLabel: 'YOUR PROMO CODE',
|
|
178
|
-
ctaText: 'Activate My Free Starter Plan',
|
|
179
|
-
footerText: undefined,
|
|
180
|
-
showImages: false,
|
|
180
|
+
...Default.args,
|
|
181
181
|
},
|
|
182
182
|
parameters: {
|
|
183
183
|
layout: 'padded',
|
|
@@ -188,10 +188,11 @@ export const Mobile = {
|
|
|
188
188
|
},
|
|
189
189
|
docs: {
|
|
190
190
|
description: {
|
|
191
|
-
story: 'The
|
|
192
|
-
'
|
|
193
|
-
'
|
|
194
|
-
'
|
|
191
|
+
story: 'The same PromoBanner as Default, viewed below 640px. The component ' +
|
|
192
|
+
'renders the Figma mobile variant: side collages and features/footer ' +
|
|
193
|
+
'hidden, text centered, promo code and CTA stacked full-width, and the ' +
|
|
194
|
+
'mobileTitle / mobileDescription / mobileCtaText copy applied. Best ' +
|
|
195
|
+
'viewed in the Canvas with the preset mobile viewport.',
|
|
195
196
|
},
|
|
196
197
|
},
|
|
197
198
|
},
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const BANNER_AVATARS: {
|
|
2
|
+
nelly: string;
|
|
3
|
+
micheal: string;
|
|
4
|
+
fjolla: string;
|
|
5
|
+
laura: string;
|
|
6
|
+
yuruo: string;
|
|
7
|
+
jerome: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const AVATAR_TILE_BACKGROUND = "linear-gradient(180deg, #e9e9e9 0%, #beb1df 100%)";
|
|
10
|
+
export declare const BANNER_VIDEO_PREVIEW = "https://storage.googleapis.com/bey-dev-public/yuruo-preview.png";
|
|
11
|
+
export declare const AVATAR_TILE_WIDTH = 130;
|
|
12
|
+
export declare const AVATAR_TILE_HEIGHT = 150;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Shared avatar imagery used by the banner collages (PromoBanner side columns
|
|
2
|
+
// and ConversationalAvatarsBanner collage column). Keeping them here lets both components reuse
|
|
3
|
+
// the same set of avatars.
|
|
4
|
+
const AVATAR_BASE = 'https://storage.googleapis.com/bey-dev-public';
|
|
5
|
+
export const BANNER_AVATARS = {
|
|
6
|
+
nelly: `${AVATAR_BASE}/nelly.jpg`,
|
|
7
|
+
micheal: `${AVATAR_BASE}/micheal.jpg`,
|
|
8
|
+
fjolla: `${AVATAR_BASE}/fjolla.png`,
|
|
9
|
+
laura: `${AVATAR_BASE}/laura.png`,
|
|
10
|
+
yuruo: `${AVATAR_BASE}/yuruo.png`,
|
|
11
|
+
jerome: `${AVATAR_BASE}/jerome.png`,
|
|
12
|
+
};
|
|
13
|
+
// Placeholder gradient shown behind avatars with transparent areas so every
|
|
14
|
+
// tile has a consistent backdrop (matches the Figma image tile fill).
|
|
15
|
+
export const AVATAR_TILE_BACKGROUND = 'linear-gradient(180deg, #e9e9e9 0%, #beb1df 100%)';
|
|
16
|
+
// Poster/thumbnail shown in the ConversationalVoiceAgentsBanner video preview.
|
|
17
|
+
export const BANNER_VIDEO_PREVIEW = `${AVATAR_BASE}/yuruo-preview.png`;
|
|
18
|
+
// Size of a single avatar tile in the collages.
|
|
19
|
+
export const AVATAR_TILE_WIDTH = 130;
|
|
20
|
+
export const AVATAR_TILE_HEIGHT = 150;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from './Badge';
|
|
2
2
|
export * from './ButtonCard';
|
|
3
3
|
export * from './Avatar';
|
|
4
|
+
export * from './ConversationalAvatarsBanner';
|
|
4
5
|
export * from './CheckIcon';
|
|
5
|
-
export * from './
|
|
6
|
+
export * from './ConversationalVoiceAgentsBanner';
|
|
6
7
|
export * from './HintBubble';
|
|
7
8
|
export * from './Logo';
|
|
8
9
|
export * from './Menu';
|
package/dist/stories/UI/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from './Badge';
|
|
2
2
|
export * from './ButtonCard';
|
|
3
3
|
export * from './Avatar';
|
|
4
|
+
export * from './ConversationalAvatarsBanner';
|
|
4
5
|
export * from './CheckIcon';
|
|
5
|
-
export * from './
|
|
6
|
+
export * from './ConversationalVoiceAgentsBanner';
|
|
6
7
|
export * from './HintBubble';
|
|
7
8
|
export * from './Logo';
|
|
8
9
|
export * from './Menu';
|
|
@@ -354,13 +354,15 @@ export interface PromoBannerProps {
|
|
|
354
354
|
ctaText?: string;
|
|
355
355
|
/** Called when the call-to-action button is clicked */
|
|
356
356
|
onCtaClick?: () => void;
|
|
357
|
-
/** Footer note shown below the actions */
|
|
357
|
+
/** Footer note shown below the actions (desktop only) */
|
|
358
358
|
footerText?: string;
|
|
359
|
-
/**
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
|
|
363
|
-
/**
|
|
359
|
+
/** Title override used on the mobile variant (falls back to `title`) */
|
|
360
|
+
mobileTitle?: string;
|
|
361
|
+
/** Description override used on the mobile variant (falls back to `description`) */
|
|
362
|
+
mobileDescription?: string;
|
|
363
|
+
/** CTA text override used on the mobile variant (falls back to `ctaText`) */
|
|
364
|
+
mobileCtaText?: string;
|
|
365
|
+
/** Whether to show the avatar collage on both sides of the banner */
|
|
364
366
|
showImages?: boolean;
|
|
365
367
|
/** Background of the banner */
|
|
366
368
|
background?: string;
|
|
@@ -370,17 +372,10 @@ export interface PromoBannerProps {
|
|
|
370
372
|
testId?: string;
|
|
371
373
|
}
|
|
372
374
|
/**
|
|
373
|
-
*
|
|
375
|
+
* Shared banner action button, used by ConversationalAvatarsBanner and
|
|
376
|
+
* ConversationalVoiceAgentsBanner.
|
|
374
377
|
*/
|
|
375
|
-
export
|
|
376
|
-
Video = "Video",
|
|
377
|
-
Image = "Image",
|
|
378
|
-
None = "None"
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* FeatureBanner action button
|
|
382
|
-
*/
|
|
383
|
-
export interface FeatureBannerAction {
|
|
378
|
+
export interface BannerAction {
|
|
384
379
|
/** Button text */
|
|
385
380
|
text: string;
|
|
386
381
|
/** Called when the button is clicked */
|
|
@@ -389,25 +384,44 @@ export interface FeatureBannerAction {
|
|
|
389
384
|
primary?: boolean;
|
|
390
385
|
}
|
|
391
386
|
/**
|
|
392
|
-
*
|
|
387
|
+
* ConversationalAvatarsBanner component props — a feature card whose media is a collage column
|
|
388
|
+
* of avatars (reused from the PromoBanner set) that stays on the right at all
|
|
389
|
+
* breakpoints.
|
|
390
|
+
*/
|
|
391
|
+
export interface ConversationalAvatarsBannerProps {
|
|
392
|
+
/** Title of the banner */
|
|
393
|
+
title?: string;
|
|
394
|
+
/** Description shown below the title */
|
|
395
|
+
description?: string;
|
|
396
|
+
/** Action buttons */
|
|
397
|
+
actions?: BannerAction[];
|
|
398
|
+
/** Background of the banner */
|
|
399
|
+
background?: string;
|
|
400
|
+
/** Custom style of the banner */
|
|
401
|
+
style?: SxProps<Theme>;
|
|
402
|
+
/** Test ID of the banner */
|
|
403
|
+
testId?: string;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* ConversationalVoiceAgentsBanner component props — a feature card whose media is a
|
|
407
|
+
* video preview (thumbnail with a play button and an optional CTA pill). Text
|
|
408
|
+
* and actions sit on the left on desktop and stack above the preview on mobile.
|
|
393
409
|
*/
|
|
394
|
-
export interface
|
|
410
|
+
export interface ConversationalVoiceAgentsBannerProps {
|
|
395
411
|
/** Title of the banner */
|
|
396
412
|
title?: string;
|
|
397
413
|
/** Description shown below the title */
|
|
398
414
|
description?: string;
|
|
399
415
|
/** Action buttons */
|
|
400
|
-
actions?:
|
|
401
|
-
/**
|
|
402
|
-
|
|
403
|
-
/** Image URL for the media (video thumbnail or image column) */
|
|
404
|
-
mediaImage?: string;
|
|
416
|
+
actions?: BannerAction[];
|
|
417
|
+
/** Thumbnail/poster image URL for the video preview */
|
|
418
|
+
previewImage?: string;
|
|
405
419
|
/** Called when the play button on the video preview is clicked */
|
|
406
420
|
onPlayClick?: () => void;
|
|
407
421
|
/** CTA text overlaid on the video preview */
|
|
408
|
-
|
|
409
|
-
/** Called when the
|
|
410
|
-
|
|
422
|
+
previewCtaText?: string;
|
|
423
|
+
/** Called when the preview CTA is clicked */
|
|
424
|
+
onPreviewCtaClick?: () => void;
|
|
411
425
|
/** Background of the banner */
|
|
412
426
|
background?: string;
|
|
413
427
|
/** Custom style of the banner */
|
package/dist/stories/UI/types.js
CHANGED
|
@@ -110,15 +110,6 @@ export var ProgressIndicatorMode;
|
|
|
110
110
|
/** Renders only the bar, with no accompanying value text. */
|
|
111
111
|
ProgressIndicatorMode["Bar"] = "Bar";
|
|
112
112
|
})(ProgressIndicatorMode || (ProgressIndicatorMode = {}));
|
|
113
|
-
/**
|
|
114
|
-
* FeatureBanner media variants
|
|
115
|
-
*/
|
|
116
|
-
export var FeatureBannerMedia;
|
|
117
|
-
(function (FeatureBannerMedia) {
|
|
118
|
-
FeatureBannerMedia["Video"] = "Video";
|
|
119
|
-
FeatureBannerMedia["Image"] = "Image";
|
|
120
|
-
FeatureBannerMedia["None"] = "None";
|
|
121
|
-
})(FeatureBannerMedia || (FeatureBannerMedia = {}));
|
|
122
113
|
/**
|
|
123
114
|
* TabMenu size variants
|
|
124
115
|
*/
|
package/package.json
CHANGED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box } from '@mui/material';
|
|
3
|
-
import { FeatureBannerMedia, } from './types';
|
|
4
|
-
import { Typography, TypographySize, TypographyWeight } from '../Typography';
|
|
5
|
-
import { MainButton, ButtonHierarchy, ButtonSize, ButtonDisplayMode, } from '../Buttons';
|
|
6
|
-
const VIOLET_GRADIENT = 'linear-gradient(180deg, rgba(194, 171, 252, 0.08) 0%, rgba(128, 96, 198, 0.2) 100%)';
|
|
7
|
-
const PINK_GRADIENT = 'linear-gradient(90deg, rgba(235, 205, 240, 0.05) 0%, rgba(202, 59, 224, 0.15) 100%)';
|
|
8
|
-
// Below this width the banner stacks vertically to match the Figma mobile
|
|
9
|
-
// layout (text + actions on top, media full-width below).
|
|
10
|
-
const MOBILE = '@media (max-width: 640px)';
|
|
11
|
-
export const FeatureBanner = ({ title, description, actions = [], media = FeatureBannerMedia.None, mediaImage, onPlayClick, mediaCtaText, onMediaCtaClick, background, style: customStyle, testId, }) => {
|
|
12
|
-
const bannerBackground = background ||
|
|
13
|
-
(media === FeatureBannerMedia.Video ? PINK_GRADIENT : VIOLET_GRADIENT);
|
|
14
|
-
const renderAction = (action, index) => (_jsx(MainButton, { text: action.text, size: ButtonSize.md, hierarchy: action.primary
|
|
15
|
-
? ButtonHierarchy.Primary
|
|
16
|
-
: ButtonHierarchy.SecondaryColor, onClick: action.onClick, style: action.primary
|
|
17
|
-
? undefined
|
|
18
|
-
: {
|
|
19
|
-
border: '1px solid var(--color-border-border-component)',
|
|
20
|
-
}, testId: testId ? `${testId}-action-${index}` : undefined }, `${action.text}-${index}`));
|
|
21
|
-
const renderMedia = () => {
|
|
22
|
-
if (media === FeatureBannerMedia.None || !mediaImage)
|
|
23
|
-
return null;
|
|
24
|
-
if (media === FeatureBannerMedia.Video) {
|
|
25
|
-
return (_jsxs(Box, { position: "relative", width: "296px", height: "150px", flexShrink: 0, overflow: "hidden", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-sm)", sx: {
|
|
26
|
-
[MOBILE]: {
|
|
27
|
-
width: '100%',
|
|
28
|
-
height: 'auto',
|
|
29
|
-
aspectRatio: '296 / 150',
|
|
30
|
-
},
|
|
31
|
-
}, children: [_jsx(Box, { component: "img", src: mediaImage, alt: "", width: "100%", height: "100%", sx: { objectFit: 'cover', display: 'block' } }), _jsx(Box, { position: "absolute", left: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", bottom: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: _jsx(MainButton, { size: ButtonSize.md, hierarchy: ButtonHierarchy.SecondaryTransparent, displayIconMode: ButtonDisplayMode.Only, displayIcon: "Play", onClick: onPlayClick, style: { border: 'none' }, testId: testId ? `${testId}-play` : undefined }) }), mediaCtaText && (_jsx(Box, { position: "absolute", right: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", bottom: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: _jsx(MainButton, { text: mediaCtaText, size: ButtonSize.md, hierarchy: ButtonHierarchy.SecondaryTransparent, onClick: onMediaCtaClick, style: { border: 'none' }, testId: testId ? `${testId}-media-cta` : undefined }) }))] }));
|
|
32
|
-
}
|
|
33
|
-
// Image media spans the full banner height by escaping the vertical padding
|
|
34
|
-
return (_jsx(Box, { component: "img", src: mediaImage, alt: "", flexShrink: 0, sx: {
|
|
35
|
-
marginY: 'calc(-1 * var(--spacing-tokens-size-in-px-spacing-spacing-3xl))',
|
|
36
|
-
[MOBILE]: { marginY: 0, maxWidth: '100%' },
|
|
37
|
-
} }));
|
|
38
|
-
};
|
|
39
|
-
return (_jsxs(Box, { display: "flex", alignItems: "center", gap: "40px", width: "100%", overflow: "hidden", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-lg)", padding: "var(--spacing-tokens-size-in-px-spacing-spacing-3xl) var(--spacing-tokens-size-in-px-spacing-spacing-2xl)", sx: {
|
|
40
|
-
background: bannerBackground,
|
|
41
|
-
boxSizing: 'border-box',
|
|
42
|
-
[MOBILE]: {
|
|
43
|
-
flexDirection: 'column',
|
|
44
|
-
alignItems: 'stretch',
|
|
45
|
-
gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-3xl)',
|
|
46
|
-
padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-xl) var(--spacing-tokens-size-in-px-spacing-spacing-lg)',
|
|
47
|
-
},
|
|
48
|
-
...customStyle,
|
|
49
|
-
}, "data-testid": testId, children: [_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-xl)", flex: 1, minWidth: 0, children: [_jsxs(Box, { display: "flex", flexDirection: "column", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: [title && (_jsx(Typography, { size: TypographySize.HeadingM, weight: TypographyWeight.Bold, color: "var(--color-text-text-title)", children: title })), description && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--color-text-text-label)", children: description }))] }), actions.length > 0 && (_jsx(Box, { display: "flex", alignItems: "center", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", children: actions.map(renderAction) }))] }), renderMedia()] }));
|
|
50
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { FeatureBanner } from './FeatureBanner';
|
|
3
|
-
declare const meta: Meta<typeof FeatureBanner>;
|
|
4
|
-
export default meta;
|
|
5
|
-
type Story = StoryObj<typeof FeatureBanner>;
|
|
6
|
-
export declare const Video: Story;
|
|
7
|
-
export declare const Image: Story;
|
|
8
|
-
export declare const TextOnly: Story;
|
|
9
|
-
export declare const Mobile: Story;
|
|
10
|
-
export declare const HomeLayout: Story;
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { FeatureBanner } from './FeatureBanner';
|
|
3
|
-
import { FeatureBannerMedia } from './types';
|
|
4
|
-
import videoPreview from '../assets/banner/video-preview.png';
|
|
5
|
-
import avatarsColumn from '../assets/banner/banner-avatars-column.png';
|
|
6
|
-
const meta = {
|
|
7
|
-
title: 'Design System/Components/UI/FeatureBanner',
|
|
8
|
-
component: FeatureBanner,
|
|
9
|
-
parameters: {
|
|
10
|
-
layout: 'padded',
|
|
11
|
-
design: {
|
|
12
|
-
type: 'figspec',
|
|
13
|
-
url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=7011-27024',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
tags: ['autodocs'],
|
|
17
|
-
argTypes: {
|
|
18
|
-
title: {
|
|
19
|
-
control: 'text',
|
|
20
|
-
description: 'Title of the banner',
|
|
21
|
-
table: {
|
|
22
|
-
type: { summary: 'string' },
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
description: {
|
|
26
|
-
control: 'text',
|
|
27
|
-
description: 'Description shown below the title',
|
|
28
|
-
table: {
|
|
29
|
-
type: { summary: 'string' },
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
actions: {
|
|
33
|
-
control: 'object',
|
|
34
|
-
description: 'Action buttons — set primary: true for the brand (violet) button',
|
|
35
|
-
table: {
|
|
36
|
-
type: { summary: 'FeatureBannerAction[]' },
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
media: {
|
|
40
|
-
control: 'select',
|
|
41
|
-
description: 'Media shown on the right side of the banner',
|
|
42
|
-
options: Object.values(FeatureBannerMedia),
|
|
43
|
-
table: {
|
|
44
|
-
type: { summary: 'string' },
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
mediaImage: {
|
|
48
|
-
control: 'text',
|
|
49
|
-
description: 'Image URL for the media (video thumbnail or image column)',
|
|
50
|
-
table: {
|
|
51
|
-
type: { summary: 'string' },
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
onPlayClick: {
|
|
55
|
-
action: 'play clicked',
|
|
56
|
-
description: 'Called when the play button on the video preview is clicked',
|
|
57
|
-
table: {
|
|
58
|
-
type: { summary: 'function' },
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
mediaCtaText: {
|
|
62
|
-
control: 'text',
|
|
63
|
-
description: 'CTA text overlaid on the video preview',
|
|
64
|
-
table: {
|
|
65
|
-
type: { summary: 'string' },
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
onMediaCtaClick: {
|
|
69
|
-
action: 'media cta clicked',
|
|
70
|
-
description: 'Called when the media CTA is clicked',
|
|
71
|
-
table: {
|
|
72
|
-
type: { summary: 'function' },
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
background: {
|
|
76
|
-
control: 'text',
|
|
77
|
-
description: 'Background of the banner',
|
|
78
|
-
table: {
|
|
79
|
-
type: { summary: 'string' },
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
style: {
|
|
83
|
-
control: 'object',
|
|
84
|
-
description: 'Custom style for the banner',
|
|
85
|
-
table: {
|
|
86
|
-
type: { summary: 'object' },
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
export default meta;
|
|
92
|
-
export const Video = {
|
|
93
|
-
args: {
|
|
94
|
-
title: 'Add a Human Face to your Conversational Voice Agents',
|
|
95
|
-
description: 'Bring your ideas to life with intelligent conversational avatars.',
|
|
96
|
-
actions: [
|
|
97
|
-
{ text: 'Create API Key' },
|
|
98
|
-
{ text: 'Go to Docs', primary: true },
|
|
99
|
-
],
|
|
100
|
-
media: FeatureBannerMedia.Video,
|
|
101
|
-
mediaImage: videoPreview,
|
|
102
|
-
mediaCtaText: 'How to Get Started',
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
export const Image = {
|
|
106
|
-
args: {
|
|
107
|
-
title: 'Introducing Conversational Avatars',
|
|
108
|
-
description: 'Bring your ideas to life with intelligent conversational avatars.',
|
|
109
|
-
actions: [{ text: 'Create New Agent' }],
|
|
110
|
-
media: FeatureBannerMedia.Image,
|
|
111
|
-
mediaImage: avatarsColumn,
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
export const TextOnly = {
|
|
115
|
-
args: {
|
|
116
|
-
title: 'Introducing Conversational Avatars',
|
|
117
|
-
description: 'Bring your ideas to life with intelligent conversational avatars.',
|
|
118
|
-
actions: [{ text: 'Create New Agent' }],
|
|
119
|
-
media: FeatureBannerMedia.None,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
const FIGMA_MOBILE_VIEWPORT = {
|
|
123
|
-
viewport: {
|
|
124
|
-
viewports: {
|
|
125
|
-
figmaMobile: {
|
|
126
|
-
name: 'Figma Mobile (375px)',
|
|
127
|
-
styles: { width: '375px', height: '812px' },
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
defaultViewport: 'figmaMobile',
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
export const Mobile = {
|
|
134
|
-
args: {
|
|
135
|
-
...Video.args,
|
|
136
|
-
},
|
|
137
|
-
parameters: {
|
|
138
|
-
layout: 'padded',
|
|
139
|
-
...FIGMA_MOBILE_VIEWPORT,
|
|
140
|
-
design: {
|
|
141
|
-
type: 'figspec',
|
|
142
|
-
url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=7081-24780',
|
|
143
|
-
},
|
|
144
|
-
docs: {
|
|
145
|
-
description: {
|
|
146
|
-
story: 'The feature banner on the mobile Home screen — text and actions ' +
|
|
147
|
-
'stack on top, media spans full-width below. Below 640px the component ' +
|
|
148
|
-
'reflows automatically; best viewed in the Canvas with the preset ' +
|
|
149
|
-
'mobile viewport.',
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
export const HomeLayout = {
|
|
155
|
-
parameters: {
|
|
156
|
-
docs: {
|
|
157
|
-
description: {
|
|
158
|
-
story: 'Both banners side by side as they appear on the Home screen of the app.',
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
render: () => (_jsxs("div", { style: { display: 'flex', gap: '12px', alignItems: 'stretch' }, children: [_jsx("div", { style: { flex: 3, minWidth: 0, display: 'flex' }, children: _jsx(FeatureBanner, { ...Video.args }) }), _jsx("div", { style: { flex: 2, minWidth: 0, display: 'flex' }, children: _jsx(FeatureBanner, { ...Image.args }) })] })),
|
|
163
|
-
};
|