@beydesign/storybook 0.1.16 → 0.1.19
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/Form/TextInput.js +46 -12
- package/dist/stories/Form/TextInput.stories.d.ts +13 -0
- package/dist/stories/Form/TextInput.stories.js +20 -1
- package/dist/stories/Form/types.d.ts +5 -0
- package/dist/stories/UI/FeatureBanner.d.ts +3 -0
- package/dist/stories/UI/FeatureBanner.js +34 -0
- package/dist/stories/UI/FeatureBanner.stories.d.ts +9 -0
- package/dist/stories/UI/FeatureBanner.stories.js +131 -0
- package/dist/stories/UI/PromoBanner.d.ts +3 -0
- package/dist/stories/UI/PromoBanner.js +31 -0
- package/dist/stories/UI/PromoBanner.stories.d.ts +9 -0
- package/dist/stories/UI/PromoBanner.stories.js +159 -0
- package/dist/stories/UI/index.d.ts +2 -0
- package/dist/stories/UI/index.js +2 -0
- package/dist/stories/UI/types.d.ts +81 -0
- package/dist/stories/UI/types.js +9 -0
- package/package.json +2 -2
|
@@ -1,23 +1,57 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box } from '@mui/material';
|
|
4
4
|
import { Typography, TypographySize, TypographyWeight } from '../Typography';
|
|
5
5
|
import { HintBubble, HintBubbleSize } from '../UI';
|
|
6
|
-
|
|
6
|
+
import { getIconComponent } from '../../utils';
|
|
7
|
+
export const TextInput = ({ label, value, onChange, placeholder, type = 'text', hintText, errorText, disabled, trailingText, style, hintBubbleText, hintBubbleProps, required, labelTrailingElement, labelContainerStyle, showPasswordToggle = true, }) => {
|
|
7
8
|
const [focused, setFocused] = useState(false);
|
|
9
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
10
|
+
const isPassword = type === 'password';
|
|
11
|
+
const enablePasswordToggle = isPassword && showPasswordToggle;
|
|
12
|
+
const inputType = enablePasswordToggle && showPassword ? 'text' : type;
|
|
13
|
+
// Reset visibility whenever the toggle is removed, so the field never
|
|
14
|
+
// reappears revealed after `type` switches away from password or
|
|
15
|
+
// `showPasswordToggle` is turned off.
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!enablePasswordToggle)
|
|
18
|
+
setShowPassword(false);
|
|
19
|
+
}, [enablePasswordToggle]);
|
|
20
|
+
const togglePasswordVisibility = () => {
|
|
21
|
+
if (!disabled)
|
|
22
|
+
setShowPassword((prev) => !prev);
|
|
23
|
+
};
|
|
24
|
+
const iconColor = disabled
|
|
25
|
+
? 'var(--colors-light-text-text-disabled)'
|
|
26
|
+
: 'var(--colors-light-foreground-fg-secondary)';
|
|
8
27
|
return (_jsx(Box, { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', sx: style, children: _jsxs(Box, { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', minWidth: '260px', children: [label && (_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', sx: labelContainerStyle, children: [_jsx(Typography, { size: TypographySize.HeadingXS, weight: TypographyWeight.SemiBold, required: required, children: label }), labelTrailingElement] })), hintBubbleText && (_jsx(HintBubble, { text: hintBubbleText, size: HintBubbleSize.sm, ...hintBubbleProps })), _jsxs(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', bgcolor: 'var(--colors-light-background-bg-component)', border: errorText
|
|
9
28
|
? 'none'
|
|
10
|
-
: '1px solid var(--colors-light-border-border-component)', overflow: 'hidden', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', children: [
|
|
29
|
+
: '1px solid var(--colors-light-border-border-component)', overflow: 'hidden', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', width: '100%', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs) 0 0 var(--spacing-tokens-size-in-px-spacing-spacing-xs)', border: `${errorText
|
|
11
30
|
? '1px solid var(--colors-light-border-border-error)'
|
|
12
31
|
: focused
|
|
13
32
|
? '1px solid var(--colors-light-border-border-component-active)'
|
|
14
|
-
: 'transparent'}`, children: _jsx("input", { type:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
: 'transparent'}`, children: [_jsx("input", { type: inputType, value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, disabled: disabled, style: {
|
|
34
|
+
border: 'none',
|
|
35
|
+
outline: 'none',
|
|
36
|
+
width: '100%',
|
|
37
|
+
color: disabled
|
|
38
|
+
? 'var(--colors-light-text-text-disabled)'
|
|
39
|
+
: 'var(--colors-light-text-text-title)',
|
|
40
|
+
backgroundColor: 'transparent',
|
|
41
|
+
}, onFocus: () => setFocused(true), onBlur: () => setFocused(false), required: required }), enablePasswordToggle && (_jsx(Box, { component: 'button', type: 'button', onClick: togglePasswordVisibility, disabled: disabled, "aria-label": showPassword ? 'Hide password' : 'Show password', "aria-pressed": showPassword, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, sx: {
|
|
42
|
+
padding: 0,
|
|
43
|
+
border: 'none',
|
|
44
|
+
borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)',
|
|
45
|
+
backgroundColor: 'transparent',
|
|
46
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
47
|
+
'&:focus-visible': {
|
|
48
|
+
outline: '2px solid var(--colors-light-border-border-component-active)',
|
|
49
|
+
outlineOffset: '2px',
|
|
50
|
+
},
|
|
51
|
+
}, children: getIconComponent(showPassword ? 'EyeSlash' : 'Eye', {
|
|
52
|
+
width: '20px',
|
|
53
|
+
height: '20px',
|
|
54
|
+
color: iconColor,
|
|
55
|
+
'aria-hidden': 'true',
|
|
56
|
+
}) }))] }), trailingText && (_jsx(Box, { display: 'flex', alignItems: 'center', height: '100%', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', borderLeft: '1px solid var(--colors-light-border-border-component)', children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: 'var(--colors-light-text-text-paragraph)', children: trailingText }) }))] }), errorText && (_jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: 'var(--colors-light-text-text-error)', children: errorText })), hintText && !errorText && (_jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: 'var(--colors-light-text-text-subtle)', children: hintText }))] }) }));
|
|
23
57
|
};
|
|
@@ -133,10 +133,23 @@ declare const meta: {
|
|
|
133
133
|
};
|
|
134
134
|
};
|
|
135
135
|
};
|
|
136
|
+
showPasswordToggle: {
|
|
137
|
+
control: "boolean";
|
|
138
|
+
description: string;
|
|
139
|
+
table: {
|
|
140
|
+
type: {
|
|
141
|
+
summary: string;
|
|
142
|
+
};
|
|
143
|
+
defaultValue: {
|
|
144
|
+
summary: string;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
};
|
|
136
148
|
};
|
|
137
149
|
};
|
|
138
150
|
export default meta;
|
|
139
151
|
type Story = StoryObj<typeof meta>;
|
|
140
152
|
export declare const Default: Story;
|
|
141
153
|
export declare const Password: Story;
|
|
154
|
+
export declare const PasswordWithoutToggle: Story;
|
|
142
155
|
export declare const Email: Story;
|
|
@@ -105,6 +105,14 @@ const meta = {
|
|
|
105
105
|
type: { summary: 'ReactNode' },
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
|
+
showPasswordToggle: {
|
|
109
|
+
control: 'boolean',
|
|
110
|
+
description: 'Whether to show the password visibility toggle (only applies when type is "password")',
|
|
111
|
+
table: {
|
|
112
|
+
type: { summary: 'boolean' },
|
|
113
|
+
defaultValue: { summary: 'true' },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
108
116
|
},
|
|
109
117
|
};
|
|
110
118
|
export default meta;
|
|
@@ -121,8 +129,19 @@ export const Password = {
|
|
|
121
129
|
label: 'Password',
|
|
122
130
|
value: 'password',
|
|
123
131
|
type: 'password',
|
|
132
|
+
placeholder: 'Enter your password',
|
|
133
|
+
onChange: () => { },
|
|
134
|
+
hintText: 'Use at least 8 characters',
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
export const PasswordWithoutToggle = {
|
|
138
|
+
args: {
|
|
139
|
+
label: 'Password',
|
|
140
|
+
value: 'password',
|
|
141
|
+
type: 'password',
|
|
142
|
+
showPasswordToggle: false,
|
|
143
|
+
placeholder: 'Enter your password',
|
|
124
144
|
onChange: () => { },
|
|
125
|
-
hintBubbleText: 'This is a hint bubble',
|
|
126
145
|
},
|
|
127
146
|
};
|
|
128
147
|
export const Email = {
|
|
@@ -75,6 +75,11 @@ export type TextInputProps = {
|
|
|
75
75
|
labelTrailingElement?: React.ReactNode;
|
|
76
76
|
/** Text input label container style */
|
|
77
77
|
labelContainerStyle?: SxProps<Theme>;
|
|
78
|
+
/**
|
|
79
|
+
* Whether to show the password visibility toggle (eye icon).
|
|
80
|
+
* Only applies when `type` is `password`. Defaults to `true`.
|
|
81
|
+
*/
|
|
82
|
+
showPasswordToggle?: boolean;
|
|
78
83
|
};
|
|
79
84
|
/**
|
|
80
85
|
* Toggle position variants
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
export const FeatureBanner = ({ title, description, actions = [], media = FeatureBannerMedia.None, mediaImage, onPlayClick, mediaCtaText, onMediaCtaClick, background, style: customStyle, testId, }) => {
|
|
9
|
+
const bannerBackground = background ||
|
|
10
|
+
(media === FeatureBannerMedia.Video ? PINK_GRADIENT : VIOLET_GRADIENT);
|
|
11
|
+
const renderAction = (action, index) => (_jsx(MainButton, { text: action.text, size: ButtonSize.md, hierarchy: action.primary
|
|
12
|
+
? ButtonHierarchy.Primary
|
|
13
|
+
: ButtonHierarchy.SecondaryColor, onClick: action.onClick, style: action.primary
|
|
14
|
+
? undefined
|
|
15
|
+
: {
|
|
16
|
+
border: '1px solid var(--colors-light-border-border-component)',
|
|
17
|
+
}, testId: testId ? `${testId}-action-${index}` : undefined }, `${action.text}-${index}`));
|
|
18
|
+
const renderMedia = () => {
|
|
19
|
+
if (media === FeatureBannerMedia.None || !mediaImage)
|
|
20
|
+
return null;
|
|
21
|
+
if (media === FeatureBannerMedia.Video) {
|
|
22
|
+
return (_jsxs(Box, { position: "relative", width: "296px", height: "150px", flexShrink: 0, overflow: "hidden", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-sm)", 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 }) }))] }));
|
|
23
|
+
}
|
|
24
|
+
// Image media spans the full banner height by escaping the vertical padding
|
|
25
|
+
return (_jsx(Box, { component: "img", src: mediaImage, alt: "", flexShrink: 0, sx: {
|
|
26
|
+
marginY: 'calc(-1 * var(--spacing-tokens-size-in-px-spacing-spacing-3xl))',
|
|
27
|
+
} }));
|
|
28
|
+
};
|
|
29
|
+
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: {
|
|
30
|
+
background: bannerBackground,
|
|
31
|
+
boxSizing: 'border-box',
|
|
32
|
+
...customStyle,
|
|
33
|
+
}, "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(--colors-light-text-text-title)", children: title })), description && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--colors-light-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()] }));
|
|
34
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
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 HomeLayout: Story;
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
export const HomeLayout = {
|
|
123
|
+
parameters: {
|
|
124
|
+
docs: {
|
|
125
|
+
description: {
|
|
126
|
+
story: 'Both banners side by side as they appear on the Home screen of the app.',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
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 }) })] })),
|
|
131
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
const DEFAULT_BACKGROUND = 'linear-gradient(180deg, rgba(194, 171, 252, 0.08) 0%, rgba(128, 96, 198, 0.2) 100%)';
|
|
6
|
+
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 = [
|
|
7
|
+
'Real-Time',
|
|
8
|
+
'Multilingual',
|
|
9
|
+
'API-first',
|
|
10
|
+
'Enterprise-ready',
|
|
11
|
+
'GDPR Compliant',
|
|
12
|
+
], promoCode, promoCodeLabel = 'YOUR PROMO CODE', onCopyPromoCode, ctaText, onCtaClick, footerText, leftImage, rightImage, showImages = true, background = DEFAULT_BACKGROUND, style: customStyle, testId, }) => {
|
|
13
|
+
const handleCopyPromoCode = () => {
|
|
14
|
+
if (!promoCode)
|
|
15
|
+
return;
|
|
16
|
+
navigator.clipboard?.writeText(promoCode);
|
|
17
|
+
onCopyPromoCode?.(promoCode);
|
|
18
|
+
};
|
|
19
|
+
const featuresText = features?.length
|
|
20
|
+
? features.map((feature) => `✓ ${feature}`).join(', ') + '.'
|
|
21
|
+
: '';
|
|
22
|
+
const renderSideImage = (image) => {
|
|
23
|
+
if (!showImages || !image)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsx(Box, { component: "img", src: image, alt: "", height: "100%", flexShrink: 0, sx: { objectFit: 'contain' } }));
|
|
26
|
+
};
|
|
27
|
+
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: { background, boxSizing: 'border-box', ...customStyle }, "data-testid": testId, children: [renderSideImage(leftImage), _jsxs(Box, { display: "flex", flexDirection: "column", alignItems: "center", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-2xl)", flex: 1, minWidth: 0, 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%", children: [title && (_jsx(Typography, { size: TypographySize.HeadingM, weight: TypographyWeight.Bold, color: "var(--colors-light-text-text-title)", textStyle: { textAlign: 'center' }, children: title })), (description || featuresText) && (_jsxs(Box, { display: "flex", flexDirection: "column", children: [description && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--colors-light-text-text-title)", children: description })), featuresText && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Bold, color: "var(--colors-light-text-text-title)", children: featuresText }))] }))] }), (promoCode || ctaText) && (_jsxs(Box, { display: "flex", justifyContent: "center", alignItems: "stretch", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-md)", 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: {
|
|
28
|
+
border: '1px dashed var(--colors-light-border-border-component)',
|
|
29
|
+
height: '100%',
|
|
30
|
+
}, testId: testId ? `${testId}-promo-code` : undefined }), promoCodeLabel && (_jsx(Box, { position: "absolute", top: "-13px", left: "-8px", bgcolor: "var(--primitives-desktop-primary-brand-violet-400)", 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, color: "var(--colors-light-text-text-title)", children: promoCodeLabel }) }))] })), ctaText && (_jsx(MainButton, { text: ctaText, size: ButtonSize.md, hierarchy: ButtonHierarchy.Primary, onClick: onCtaClick, testId: testId ? `${testId}-cta` : undefined }))] })), footerText && (_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: "var(--colors-light-text-text-title)", textStyle: { width: '100%' }, children: footerText }))] }), renderSideImage(rightImage)] }));
|
|
31
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { PromoBanner } from './PromoBanner';
|
|
3
|
+
declare const meta: Meta<typeof PromoBanner>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof PromoBanner>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const WithoutImages: Story;
|
|
8
|
+
export declare const WithoutPromoCode: Story;
|
|
9
|
+
export declare const TitleAndCtaOnly: Story;
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
const meta = {
|
|
5
|
+
title: 'Design System/Components/UI/PromoBanner',
|
|
6
|
+
component: PromoBanner,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'padded',
|
|
9
|
+
design: {
|
|
10
|
+
type: 'figspec',
|
|
11
|
+
url: 'https://www.figma.com/design/mIhjz2yJjcpLlIqw6oUivt/Beyond-Presence?node-id=7020-10224',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
argTypes: {
|
|
16
|
+
title: {
|
|
17
|
+
control: 'text',
|
|
18
|
+
description: 'Title of the banner',
|
|
19
|
+
table: {
|
|
20
|
+
type: { summary: 'string' },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
description: {
|
|
24
|
+
control: 'text',
|
|
25
|
+
description: 'Description text shown below the title',
|
|
26
|
+
table: {
|
|
27
|
+
type: { summary: 'string' },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
features: {
|
|
31
|
+
control: 'object',
|
|
32
|
+
description: 'Feature highlights rendered as a checkmark list',
|
|
33
|
+
table: {
|
|
34
|
+
type: { summary: 'string[]' },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
promoCode: {
|
|
38
|
+
control: 'text',
|
|
39
|
+
description: 'Promo code to display',
|
|
40
|
+
table: {
|
|
41
|
+
type: { summary: 'string' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
promoCodeLabel: {
|
|
45
|
+
control: 'text',
|
|
46
|
+
description: 'Label shown on top of the promo code',
|
|
47
|
+
table: {
|
|
48
|
+
type: { summary: 'string' },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
onCopyPromoCode: {
|
|
52
|
+
action: 'promo code copied',
|
|
53
|
+
description: 'Called after the promo code is copied to the clipboard',
|
|
54
|
+
table: {
|
|
55
|
+
type: { summary: 'function' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
ctaText: {
|
|
59
|
+
control: 'text',
|
|
60
|
+
description: 'Call-to-action button text',
|
|
61
|
+
table: {
|
|
62
|
+
type: { summary: 'string' },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
onCtaClick: {
|
|
66
|
+
action: 'cta clicked',
|
|
67
|
+
description: 'Called when the call-to-action button is clicked',
|
|
68
|
+
table: {
|
|
69
|
+
type: { summary: 'function' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
footerText: {
|
|
73
|
+
control: 'text',
|
|
74
|
+
description: 'Footer note shown below the actions',
|
|
75
|
+
table: {
|
|
76
|
+
type: { summary: 'string' },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
leftImage: {
|
|
80
|
+
control: 'text',
|
|
81
|
+
description: 'Image URL of the avatar collage shown on the left side',
|
|
82
|
+
table: {
|
|
83
|
+
type: { summary: 'string' },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
rightImage: {
|
|
87
|
+
control: 'text',
|
|
88
|
+
description: 'Image URL of the avatar collage shown on the right side',
|
|
89
|
+
table: {
|
|
90
|
+
type: { summary: 'string' },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
showImages: {
|
|
94
|
+
control: 'boolean',
|
|
95
|
+
description: 'Whether to show the avatar images',
|
|
96
|
+
table: {
|
|
97
|
+
type: { summary: 'boolean' },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
background: {
|
|
101
|
+
control: 'text',
|
|
102
|
+
description: 'Background of the banner',
|
|
103
|
+
table: {
|
|
104
|
+
type: { summary: 'string' },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
style: {
|
|
108
|
+
control: 'object',
|
|
109
|
+
description: 'Custom style for the banner',
|
|
110
|
+
table: {
|
|
111
|
+
type: { summary: 'object' },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
export default meta;
|
|
117
|
+
export const Default = {
|
|
118
|
+
args: {
|
|
119
|
+
title: 'Real-time AI avatars for every conversation.',
|
|
120
|
+
description: 'Trusted by developers, startups, educators, and enterprises to build engaging AI experiences at scale:',
|
|
121
|
+
features: [
|
|
122
|
+
'Real-Time',
|
|
123
|
+
'Multilingual',
|
|
124
|
+
'API-first',
|
|
125
|
+
'Enterprise-ready',
|
|
126
|
+
'GDPR Compliant',
|
|
127
|
+
],
|
|
128
|
+
promoCode: 'PKAJ2HGO',
|
|
129
|
+
promoCodeLabel: 'YOUR PROMO CODE',
|
|
130
|
+
ctaText: 'Enjoy 1 month free on Starter',
|
|
131
|
+
footerText: 'Use Promo code at checkout and get 90 End-to-End API minutes or 180 Audio-to-Video API minutes free.',
|
|
132
|
+
leftImage: bannerAvatarsLeft,
|
|
133
|
+
rightImage: bannerAvatarsRight,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
export const WithoutImages = {
|
|
137
|
+
args: {
|
|
138
|
+
...Default.args,
|
|
139
|
+
showImages: false,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
export const WithoutPromoCode = {
|
|
143
|
+
args: {
|
|
144
|
+
...Default.args,
|
|
145
|
+
promoCode: undefined,
|
|
146
|
+
footerText: undefined,
|
|
147
|
+
ctaText: 'Upgrade to Starter',
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
export const TitleAndCtaOnly = {
|
|
151
|
+
args: {
|
|
152
|
+
title: 'Real-time AI avatars for every conversation.',
|
|
153
|
+
description: undefined,
|
|
154
|
+
features: [],
|
|
155
|
+
ctaText: 'Get Started',
|
|
156
|
+
leftImage: bannerAvatarsLeft,
|
|
157
|
+
rightImage: bannerAvatarsRight,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export * from './Badge';
|
|
2
2
|
export * from './Avatar';
|
|
3
|
+
export * from './FeatureBanner';
|
|
3
4
|
export * from './HintBubble';
|
|
4
5
|
export * from './Logo';
|
|
5
6
|
export * from './Menu';
|
|
6
7
|
export * from './ProgressIndicator';
|
|
8
|
+
export * from './PromoBanner';
|
|
7
9
|
export * from './SkeletonLoader';
|
|
8
10
|
export * from './Topbar';
|
|
9
11
|
export * from './types';
|
package/dist/stories/UI/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export * from './Badge';
|
|
2
2
|
export * from './Avatar';
|
|
3
|
+
export * from './FeatureBanner';
|
|
3
4
|
export * from './HintBubble';
|
|
4
5
|
export * from './Logo';
|
|
5
6
|
export * from './Menu';
|
|
6
7
|
export * from './ProgressIndicator';
|
|
8
|
+
export * from './PromoBanner';
|
|
7
9
|
export * from './SkeletonLoader';
|
|
8
10
|
export * from './Topbar';
|
|
9
11
|
export * from './types';
|
|
@@ -236,6 +236,87 @@ export interface TopbarProps {
|
|
|
236
236
|
/** Class name */
|
|
237
237
|
className?: string;
|
|
238
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* PromoBanner component props
|
|
241
|
+
*/
|
|
242
|
+
export interface PromoBannerProps {
|
|
243
|
+
/** Title of the banner */
|
|
244
|
+
title?: string;
|
|
245
|
+
/** Description text shown below the title */
|
|
246
|
+
description?: string;
|
|
247
|
+
/** Feature highlights rendered as a checkmark list below the description */
|
|
248
|
+
features?: string[];
|
|
249
|
+
/** Promo code to display */
|
|
250
|
+
promoCode?: string;
|
|
251
|
+
/** Label shown on top of the promo code */
|
|
252
|
+
promoCodeLabel?: string;
|
|
253
|
+
/** Called after the promo code is copied to the clipboard */
|
|
254
|
+
onCopyPromoCode?: (promoCode: string) => void;
|
|
255
|
+
/** Call-to-action button text */
|
|
256
|
+
ctaText?: string;
|
|
257
|
+
/** Called when the call-to-action button is clicked */
|
|
258
|
+
onCtaClick?: () => void;
|
|
259
|
+
/** Footer note shown below the actions */
|
|
260
|
+
footerText?: string;
|
|
261
|
+
/** Image URL of the avatar collage shown on the left side */
|
|
262
|
+
leftImage?: string;
|
|
263
|
+
/** Image URL of the avatar collage shown on the right side */
|
|
264
|
+
rightImage?: string;
|
|
265
|
+
/** Whether to show the avatar images */
|
|
266
|
+
showImages?: boolean;
|
|
267
|
+
/** Background of the banner */
|
|
268
|
+
background?: string;
|
|
269
|
+
/** Custom style of the banner */
|
|
270
|
+
style?: SxProps<Theme>;
|
|
271
|
+
/** Test ID of the banner */
|
|
272
|
+
testId?: string;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* FeatureBanner media variants
|
|
276
|
+
*/
|
|
277
|
+
export declare enum FeatureBannerMedia {
|
|
278
|
+
Video = "Video",
|
|
279
|
+
Image = "Image",
|
|
280
|
+
None = "None"
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* FeatureBanner action button
|
|
284
|
+
*/
|
|
285
|
+
export interface FeatureBannerAction {
|
|
286
|
+
/** Button text */
|
|
287
|
+
text: string;
|
|
288
|
+
/** Called when the button is clicked */
|
|
289
|
+
onClick?: () => void;
|
|
290
|
+
/** Whether to render as the primary (brand) action */
|
|
291
|
+
primary?: boolean;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* FeatureBanner component props
|
|
295
|
+
*/
|
|
296
|
+
export interface FeatureBannerProps {
|
|
297
|
+
/** Title of the banner */
|
|
298
|
+
title?: string;
|
|
299
|
+
/** Description shown below the title */
|
|
300
|
+
description?: string;
|
|
301
|
+
/** Action buttons */
|
|
302
|
+
actions?: FeatureBannerAction[];
|
|
303
|
+
/** Media shown on the right side of the banner */
|
|
304
|
+
media?: FeatureBannerMedia;
|
|
305
|
+
/** Image URL for the media (video thumbnail or image column) */
|
|
306
|
+
mediaImage?: string;
|
|
307
|
+
/** Called when the play button on the video preview is clicked */
|
|
308
|
+
onPlayClick?: () => void;
|
|
309
|
+
/** CTA text overlaid on the video preview */
|
|
310
|
+
mediaCtaText?: string;
|
|
311
|
+
/** Called when the media CTA is clicked */
|
|
312
|
+
onMediaCtaClick?: () => void;
|
|
313
|
+
/** Background of the banner */
|
|
314
|
+
background?: string;
|
|
315
|
+
/** Custom style of the banner */
|
|
316
|
+
style?: SxProps<Theme>;
|
|
317
|
+
/** Test ID of the banner */
|
|
318
|
+
testId?: string;
|
|
319
|
+
}
|
|
239
320
|
export interface StepProps {
|
|
240
321
|
/** Current step of the stepper */
|
|
241
322
|
currentStep: number;
|
package/dist/stories/UI/types.js
CHANGED
|
@@ -69,3 +69,12 @@ export var ProgressIndicatorMode;
|
|
|
69
69
|
ProgressIndicatorMode["Value"] = "Value";
|
|
70
70
|
ProgressIndicatorMode["Percentage"] = "Percentage";
|
|
71
71
|
})(ProgressIndicatorMode || (ProgressIndicatorMode = {}));
|
|
72
|
+
/**
|
|
73
|
+
* FeatureBanner media variants
|
|
74
|
+
*/
|
|
75
|
+
export var FeatureBannerMedia;
|
|
76
|
+
(function (FeatureBannerMedia) {
|
|
77
|
+
FeatureBannerMedia["Video"] = "Video";
|
|
78
|
+
FeatureBannerMedia["Image"] = "Image";
|
|
79
|
+
FeatureBannerMedia["None"] = "None";
|
|
80
|
+
})(FeatureBannerMedia || (FeatureBannerMedia = {}));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beydesign/storybook",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.19",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@beydesign/tokens": "^0.0.
|
|
28
|
+
"@beydesign/tokens": "^0.0.16",
|
|
29
29
|
"@emotion/react": "^11.14.0",
|
|
30
30
|
"@emotion/styled": "^11.14.1",
|
|
31
31
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|