@beydesign/storybook 0.1.17 → 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.
@@ -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
- export const TextInput = ({ label, value, onChange, placeholder, type = 'text', hintText, errorText, disabled, trailingText, style, hintBubbleText, hintBubbleProps, required, labelTrailingElement, labelContainerStyle, }) => {
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: [_jsx(Box, { display: 'flex', flexDirection: 'row', 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
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: type, value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, disabled: disabled, style: {
15
- border: 'none',
16
- outline: 'none',
17
- width: '100%',
18
- color: disabled
19
- ? 'var(--colors-light-text-text-disabled)'
20
- : 'var(--colors-light-text-text-title)',
21
- backgroundColor: 'transparent',
22
- }, onFocus: () => setFocused(true), onBlur: () => setFocused(false), required: required }) }), 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 }))] }) }));
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,3 @@
1
+ import React from 'react';
2
+ import { FeatureBannerProps } from './types';
3
+ export declare const FeatureBanner: React.FC<FeatureBannerProps>;
@@ -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,3 @@
1
+ import React from 'react';
2
+ import { PromoBannerProps } from './types';
3
+ export declare const PromoBanner: React.FC<PromoBannerProps>;
@@ -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';
@@ -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;
@@ -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.17",
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.12",
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",