@transferwise/components 0.0.0-experimental-4d90243 → 0.0.0-experimental-a308934
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/build/field/Field.js +5 -4
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +5 -4
- package/build/field/Field.mjs.map +1 -1
- package/build/i18n/en.json +2 -0
- package/build/i18n/en.json.js +2 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +2 -0
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/index.mjs +1 -0
- package/build/index.mjs.map +1 -1
- package/build/listItem/Prompt/ListItemPrompt.js +5 -4
- package/build/listItem/Prompt/ListItemPrompt.js.map +1 -1
- package/build/listItem/Prompt/ListItemPrompt.mjs +5 -4
- package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -1
- package/build/main.css +85 -29
- package/build/moneyInput/MoneyInput.js +12 -28
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs +14 -30
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/moneyInput/currencyFormatting.js +2 -8
- package/build/moneyInput/currencyFormatting.js.map +1 -1
- package/build/moneyInput/currencyFormatting.mjs +4 -5
- package/build/moneyInput/currencyFormatting.mjs.map +1 -1
- package/build/prompt/ActionPrompt/ActionPrompt.js +8 -40
- package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
- package/build/prompt/ActionPrompt/ActionPrompt.mjs +8 -40
- package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
- package/build/prompt/CriticalBanner/CriticalBanner.js +145 -0
- package/build/prompt/CriticalBanner/CriticalBanner.js.map +1 -0
- package/build/prompt/CriticalBanner/CriticalBanner.mjs +143 -0
- package/build/prompt/CriticalBanner/CriticalBanner.mjs.map +1 -0
- package/build/prompt/CriticalBanner/helpers.js +29 -0
- package/build/prompt/CriticalBanner/helpers.js.map +1 -0
- package/build/prompt/CriticalBanner/helpers.mjs +26 -0
- package/build/prompt/CriticalBanner/helpers.mjs.map +1 -0
- package/build/prompt/PrimitivePrompt/PrimitivePrompt.js +2 -0
- package/build/prompt/PrimitivePrompt/PrimitivePrompt.js.map +1 -1
- package/build/prompt/PrimitivePrompt/PrimitivePrompt.mjs +2 -0
- package/build/prompt/PrimitivePrompt/PrimitivePrompt.mjs.map +1 -1
- package/build/prompt/common/Expander/Expander.js +47 -0
- package/build/prompt/common/Expander/Expander.js.map +1 -0
- package/build/prompt/common/Expander/Expander.messages.js +17 -0
- package/build/prompt/common/Expander/Expander.messages.js.map +1 -0
- package/build/prompt/common/Expander/Expander.messages.mjs +13 -0
- package/build/prompt/common/Expander/Expander.messages.mjs.map +1 -0
- package/build/prompt/common/Expander/Expander.mjs +44 -0
- package/build/prompt/common/Expander/Expander.mjs.map +1 -0
- package/build/prompt/helpers/promptMedia.js +52 -0
- package/build/prompt/helpers/promptMedia.js.map +1 -0
- package/build/prompt/helpers/promptMedia.mjs +50 -0
- package/build/prompt/helpers/promptMedia.mjs.map +1 -0
- package/build/styles/main.css +85 -29
- package/build/styles/prompt/CriticalBanner/CriticalBanner.css +39 -0
- package/build/styles/prompt/common/Expander/Expander.css +8 -0
- package/build/typeahead/Typeahead.js +5 -4
- package/build/typeahead/Typeahead.js.map +1 -1
- package/build/typeahead/Typeahead.mjs +5 -4
- package/build/typeahead/Typeahead.mjs.map +1 -1
- package/build/types/index.d.ts +2 -2
- package/build/types/index.d.ts.map +1 -1
- package/build/types/moneyInput/MoneyInput.d.ts +0 -6
- package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/moneyInput/currencyFormatting.d.ts +3 -4
- package/build/types/moneyInput/currencyFormatting.d.ts.map +1 -1
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +2 -11
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
- package/build/types/prompt/CriticalBanner/CriticalBanner.d.ts +43 -0
- package/build/types/prompt/CriticalBanner/CriticalBanner.d.ts.map +1 -0
- package/build/types/prompt/CriticalBanner/helpers.d.ts +18 -0
- package/build/types/prompt/CriticalBanner/helpers.d.ts.map +1 -0
- package/build/types/prompt/CriticalBanner/index.d.ts +3 -0
- package/build/types/prompt/CriticalBanner/index.d.ts.map +1 -0
- package/build/types/prompt/PrimitivePrompt/PrimitivePrompt.d.ts +8 -3
- package/build/types/prompt/PrimitivePrompt/PrimitivePrompt.d.ts.map +1 -1
- package/build/types/prompt/common/Expander/Expander.d.ts +20 -0
- package/build/types/prompt/common/Expander/Expander.d.ts.map +1 -0
- package/build/types/prompt/common/Expander/Expander.messages.d.ts +14 -0
- package/build/types/prompt/common/Expander/Expander.messages.d.ts.map +1 -0
- package/build/types/prompt/helpers/promptMedia.d.ts +22 -0
- package/build/types/prompt/helpers/promptMedia.d.ts.map +1 -0
- package/build/types/prompt/index.d.ts +2 -0
- package/build/types/prompt/index.d.ts.map +1 -1
- package/build/types/test-utils/index.d.ts +4 -0
- package/build/types/test-utils/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/i18n/en.json +2 -0
- package/src/index.ts +2 -2
- package/src/main.css +85 -29
- package/src/main.less +3 -1
- package/src/moneyInput/MoneyInput.story.tsx +1 -10
- package/src/moneyInput/MoneyInput.test.story.tsx +1 -141
- package/src/moneyInput/MoneyInput.test.tsx +0 -45
- package/src/moneyInput/MoneyInput.tsx +3 -27
- package/src/moneyInput/currencyFormatting.ts +5 -11
- package/src/prompt/ActionPrompt/ActionPrompt.tsx +9 -62
- package/src/prompt/CriticalBanner/CriticalBanner.accessibility.docs.mdx +131 -0
- package/src/prompt/CriticalBanner/CriticalBanner.css +39 -0
- package/src/prompt/CriticalBanner/CriticalBanner.less +45 -0
- package/src/prompt/CriticalBanner/CriticalBanner.story.tsx +476 -0
- package/src/prompt/CriticalBanner/CriticalBanner.test.story.tsx +67 -0
- package/src/prompt/CriticalBanner/CriticalBanner.tsx +188 -0
- package/src/prompt/CriticalBanner/helpers.ts +39 -0
- package/src/prompt/CriticalBanner/index.ts +2 -0
- package/src/prompt/PrimitivePrompt/PrimitivePrompt.tsx +9 -2
- package/src/prompt/common/Expander/Expander.css +8 -0
- package/src/prompt/common/Expander/Expander.less +9 -0
- package/src/prompt/common/Expander/Expander.messages.ts +14 -0
- package/src/prompt/common/Expander/Expander.test.tsx +167 -0
- package/src/prompt/common/Expander/Expander.tsx +83 -0
- package/src/prompt/helpers/promptMedia.tsx +79 -0
- package/src/prompt/index.ts +4 -0
- package/src/sentimentSurface/SentimentSurface.story.tsx +43 -17
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { ReactElement, useState } from 'react';
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
|
+
import { fn } from 'storybook/test';
|
|
4
|
+
import { Bank, Star, Travel, Briefcase } from '@transferwise/icons';
|
|
5
|
+
import { lorem10, lorem20 } from '../../test-utils';
|
|
6
|
+
import Button from '../../button';
|
|
7
|
+
import Title from '../../title';
|
|
8
|
+
import { CriticalBanner, type CriticalBannerProps } from './CriticalBanner';
|
|
9
|
+
|
|
10
|
+
const withComponentGrid =
|
|
11
|
+
({ maxWidth = 'auto', gap = '1rem' } = {}) =>
|
|
12
|
+
(Story: () => ReactElement) => (
|
|
13
|
+
<div
|
|
14
|
+
style={{
|
|
15
|
+
width: '100%',
|
|
16
|
+
display: 'flex',
|
|
17
|
+
flexDirection: 'column',
|
|
18
|
+
justifyContent: 'flex-start',
|
|
19
|
+
alignItems: 'flex-start',
|
|
20
|
+
alignContent: 'flex-start',
|
|
21
|
+
gap,
|
|
22
|
+
maxWidth,
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Story />
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const meta: Meta<typeof CriticalBanner> = {
|
|
30
|
+
title: 'Prompts/CriticalBanner',
|
|
31
|
+
component: CriticalBanner,
|
|
32
|
+
tags: ['new'],
|
|
33
|
+
decorators: [withComponentGrid()],
|
|
34
|
+
args: {
|
|
35
|
+
title: 'Critical Action Required',
|
|
36
|
+
description: 'Please complete the following action to continue.',
|
|
37
|
+
action: { label: 'Take Action', onClick: fn() },
|
|
38
|
+
expanded: true,
|
|
39
|
+
onToggle: fn(),
|
|
40
|
+
},
|
|
41
|
+
argTypes: {
|
|
42
|
+
sentiment: {
|
|
43
|
+
control: 'select',
|
|
44
|
+
options: ['success', 'negative', 'neutral', 'warning'],
|
|
45
|
+
},
|
|
46
|
+
title: {
|
|
47
|
+
control: 'text',
|
|
48
|
+
table: {
|
|
49
|
+
type: { summary: 'ReactNode' },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
description: {
|
|
53
|
+
control: 'text',
|
|
54
|
+
table: {
|
|
55
|
+
type: { summary: 'ReactNode' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
expanded: {
|
|
59
|
+
control: 'boolean',
|
|
60
|
+
},
|
|
61
|
+
media: {
|
|
62
|
+
control: false,
|
|
63
|
+
table: {
|
|
64
|
+
type: {
|
|
65
|
+
summary: 'CriticalBannerMedia',
|
|
66
|
+
detail:
|
|
67
|
+
'{ imgSrc?: string; avatar?: { imgSrc?: string; profileName?: string; profileType?: string; asset?: ReactNode; badge?: { flagCode: string } }; aria-label?: string }',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
action: {
|
|
72
|
+
control: false,
|
|
73
|
+
table: {
|
|
74
|
+
type: {
|
|
75
|
+
summary: 'CriticalBannerAction',
|
|
76
|
+
detail: '{ label: ReactNode; onClick?: () => void; href?: string; target?: string }',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
actionSecondary: {
|
|
81
|
+
control: false,
|
|
82
|
+
table: {
|
|
83
|
+
type: {
|
|
84
|
+
summary: 'CriticalBannerActionSecondary',
|
|
85
|
+
detail: '{ label: ReactNode; onClick?: () => void; href?: string; target?: string }',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default meta;
|
|
93
|
+
type Story = StoryObj<typeof CriticalBanner>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convenience controls for previewing rich markup,
|
|
97
|
+
* not otherwise possible via Storybook
|
|
98
|
+
*/
|
|
99
|
+
type PreviewStoryArgs = CriticalBannerProps & {
|
|
100
|
+
previewMedia: CriticalBannerProps['media'];
|
|
101
|
+
previewSecondaryAction: boolean;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const previewArgGroup = {
|
|
105
|
+
category: 'Storybook Preview options',
|
|
106
|
+
type: {
|
|
107
|
+
summary: undefined,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const MEDIA_OPTIONS: Record<string, CriticalBannerProps['media']> = {
|
|
112
|
+
undefined,
|
|
113
|
+
'Custom image (no badge)': { imgSrc: '../../wise-card.svg' },
|
|
114
|
+
'Avatar: Profile image': { avatar: { imgSrc: '../../avatar-rectangle-fox.webp' } },
|
|
115
|
+
'Avatar: Business (GB badge)': {
|
|
116
|
+
avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } },
|
|
117
|
+
},
|
|
118
|
+
'Avatar: Personal (EU badge)': {
|
|
119
|
+
avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } },
|
|
120
|
+
},
|
|
121
|
+
'Avatar: Icon (Bank)': { avatar: { asset: <Bank /> } },
|
|
122
|
+
'Avatar: Icon (Star)': { avatar: { asset: <Star /> } },
|
|
123
|
+
'Avatar: Icon (Travel)': { avatar: { asset: <Travel /> } },
|
|
124
|
+
'Avatar: Icon (Briefcase)': { avatar: { asset: <Briefcase /> } },
|
|
125
|
+
'Avatar: Initials': { avatar: { profileName: 'John Doe' } },
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const previewArgTypes = {
|
|
129
|
+
previewMedia: {
|
|
130
|
+
name: 'Preview with `media`',
|
|
131
|
+
control: 'select',
|
|
132
|
+
options: [
|
|
133
|
+
'undefined',
|
|
134
|
+
'Custom image (no badge)',
|
|
135
|
+
'Avatar: Profile image',
|
|
136
|
+
'Avatar: Business (GB badge)',
|
|
137
|
+
'Avatar: Personal (EU badge)',
|
|
138
|
+
'Avatar: Icon (Bank)',
|
|
139
|
+
'Avatar: Icon (Star)',
|
|
140
|
+
'Avatar: Icon (Travel)',
|
|
141
|
+
'Avatar: Icon (Briefcase)',
|
|
142
|
+
'Avatar: Initials',
|
|
143
|
+
],
|
|
144
|
+
mapping: MEDIA_OPTIONS,
|
|
145
|
+
table: previewArgGroup,
|
|
146
|
+
},
|
|
147
|
+
previewSecondaryAction: {
|
|
148
|
+
name: 'Preview with `actionSecondary`',
|
|
149
|
+
control: 'boolean',
|
|
150
|
+
table: previewArgGroup,
|
|
151
|
+
},
|
|
152
|
+
} as const;
|
|
153
|
+
|
|
154
|
+
const getPropsForPreview = (
|
|
155
|
+
args: PreviewStoryArgs,
|
|
156
|
+
): [CriticalBannerProps, Partial<CriticalBannerProps>] => {
|
|
157
|
+
const { previewMedia, previewSecondaryAction, ...props } = args;
|
|
158
|
+
|
|
159
|
+
return [
|
|
160
|
+
props,
|
|
161
|
+
{
|
|
162
|
+
media: previewMedia,
|
|
163
|
+
actionSecondary: previewSecondaryAction ? { label: 'Cancel', onClick: fn() } : undefined,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The Playground story demonstrates the CriticalBanner with interactive controls.
|
|
170
|
+
* The story manages the `expanded` state internally to enable the toggle behavior.
|
|
171
|
+
*/
|
|
172
|
+
export const Playground: StoryObj<PreviewStoryArgs> = {
|
|
173
|
+
argTypes: {
|
|
174
|
+
sentiment: {
|
|
175
|
+
control: 'radio',
|
|
176
|
+
options: ['success', 'negative', 'neutral', 'warning'],
|
|
177
|
+
},
|
|
178
|
+
...previewArgTypes,
|
|
179
|
+
},
|
|
180
|
+
args: {
|
|
181
|
+
previewMedia: { avatar: undefined },
|
|
182
|
+
previewSecondaryAction: true,
|
|
183
|
+
sentiment: 'negative',
|
|
184
|
+
},
|
|
185
|
+
render: function PlaygroundRender(args: PreviewStoryArgs) {
|
|
186
|
+
const [expanded, setExpanded] = useState(args.expanded);
|
|
187
|
+
const [props, previewProps] = getPropsForPreview(args);
|
|
188
|
+
return (
|
|
189
|
+
<CriticalBanner
|
|
190
|
+
{...props}
|
|
191
|
+
{...previewProps}
|
|
192
|
+
expanded={expanded}
|
|
193
|
+
onToggle={() => setExpanded(!expanded)}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* CriticalBanner supports multiple sentiments to communicate different types of critical messages:
|
|
201
|
+
* - `negative` (default): Critical errors or issues
|
|
202
|
+
* - `warning`: Warning messages requiring attention
|
|
203
|
+
* - `neutral`: General important notices
|
|
204
|
+
* - `success`: Success confirmations
|
|
205
|
+
*/
|
|
206
|
+
export const Sentiments: Story = {
|
|
207
|
+
render: () => {
|
|
208
|
+
const [expandedStates, setExpandedStates] = useState<Record<string, boolean>>({
|
|
209
|
+
success: true,
|
|
210
|
+
warning: true,
|
|
211
|
+
negative: true,
|
|
212
|
+
neutral: true,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const handleToggle = (sentiment: string) => {
|
|
216
|
+
setExpandedStates((prev) => ({ ...prev, [sentiment]: !prev[sentiment] }));
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<>
|
|
221
|
+
{(['negative', 'warning', 'neutral', 'success'] as const).map((sentiment) => (
|
|
222
|
+
<CriticalBanner
|
|
223
|
+
key={sentiment}
|
|
224
|
+
sentiment={sentiment}
|
|
225
|
+
title={`${sentiment.charAt(0).toUpperCase() + sentiment.slice(1)} Banner`}
|
|
226
|
+
description={lorem10}
|
|
227
|
+
action={{ label: 'Primary', onClick: fn() }}
|
|
228
|
+
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
229
|
+
expanded={expandedStates[sentiment]}
|
|
230
|
+
onToggle={() => handleToggle(sentiment)}
|
|
231
|
+
/>
|
|
232
|
+
))}
|
|
233
|
+
</>
|
|
234
|
+
);
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* CriticalBanner can have just a primary action, both primary and secondary actions, or no actions at all.
|
|
240
|
+
*/
|
|
241
|
+
export const Actions: Story = {
|
|
242
|
+
render: () => {
|
|
243
|
+
const [expandedStates, setExpandedStates] = useState({
|
|
244
|
+
noActions: true,
|
|
245
|
+
primaryOnly: true,
|
|
246
|
+
both: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<>
|
|
251
|
+
<CriticalBanner
|
|
252
|
+
sentiment="negative"
|
|
253
|
+
title="No actions"
|
|
254
|
+
description="This banner has no action buttons."
|
|
255
|
+
expanded={expandedStates.noActions}
|
|
256
|
+
onToggle={() => setExpandedStates((prev) => ({ ...prev, noActions: !prev.noActions }))}
|
|
257
|
+
/>
|
|
258
|
+
<CriticalBanner
|
|
259
|
+
sentiment="warning"
|
|
260
|
+
title="Primary action only"
|
|
261
|
+
description="This banner has only one action button."
|
|
262
|
+
action={{ label: 'Take Action', onClick: fn() }}
|
|
263
|
+
expanded={expandedStates.primaryOnly}
|
|
264
|
+
onToggle={() =>
|
|
265
|
+
setExpandedStates((prev) => ({ ...prev, primaryOnly: !prev.primaryOnly }))
|
|
266
|
+
}
|
|
267
|
+
/>
|
|
268
|
+
<CriticalBanner
|
|
269
|
+
sentiment="negative"
|
|
270
|
+
title="Primary and secondary actions"
|
|
271
|
+
description="This banner has both action buttons."
|
|
272
|
+
action={{ label: 'Confirm', onClick: fn() }}
|
|
273
|
+
actionSecondary={{ label: 'Cancel', onClick: fn() }}
|
|
274
|
+
expanded={expandedStates.both}
|
|
275
|
+
onToggle={() => setExpandedStates((prev) => ({ ...prev, both: !prev.both }))}
|
|
276
|
+
/>
|
|
277
|
+
</>
|
|
278
|
+
);
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* CriticalBanner supports various media types to enhance visual communication.
|
|
284
|
+
*
|
|
285
|
+
* **Default**: Each sentiment has a default status icon that appears when no media is provided.
|
|
286
|
+
*
|
|
287
|
+
* **Icon Overrides**: Replace default icons with custom images, avatars with images, avatars with initials,
|
|
288
|
+
* avatars with custom icons, or avatars with profile types and badges.
|
|
289
|
+
*/
|
|
290
|
+
export const MediaTypes: Story = {
|
|
291
|
+
render: () => {
|
|
292
|
+
const [expandedStates, setExpandedStates] = useState<Record<string, boolean>>({
|
|
293
|
+
default: true,
|
|
294
|
+
customImage: true,
|
|
295
|
+
avatarImage: true,
|
|
296
|
+
avatarInitials: true,
|
|
297
|
+
avatarIcon: true,
|
|
298
|
+
businessBadge: true,
|
|
299
|
+
personalBadge: true,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const handleToggle = (key: string) => {
|
|
303
|
+
setExpandedStates((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<>
|
|
308
|
+
<Title type="title-body">Default</Title>
|
|
309
|
+
<CriticalBanner
|
|
310
|
+
sentiment="negative"
|
|
311
|
+
title="Default status icon"
|
|
312
|
+
description="When no media is provided, the sentiment's default status icon is displayed."
|
|
313
|
+
action={{ label: 'Continue', onClick: fn() }}
|
|
314
|
+
expanded={expandedStates.default}
|
|
315
|
+
onToggle={() => handleToggle('default')}
|
|
316
|
+
/>
|
|
317
|
+
|
|
318
|
+
<Title type="title-body">Icon Overrides</Title>
|
|
319
|
+
<CriticalBanner
|
|
320
|
+
sentiment="negative"
|
|
321
|
+
title="Custom image"
|
|
322
|
+
description={lorem10}
|
|
323
|
+
media={{ imgSrc: '../../wise-card.svg' }}
|
|
324
|
+
action={{ label: 'View card', onClick: fn() }}
|
|
325
|
+
expanded={expandedStates.customImage}
|
|
326
|
+
onToggle={() => handleToggle('customImage')}
|
|
327
|
+
/>
|
|
328
|
+
<CriticalBanner
|
|
329
|
+
sentiment="negative"
|
|
330
|
+
title="Avatar with image"
|
|
331
|
+
description={lorem10}
|
|
332
|
+
media={{ avatar: { imgSrc: '../../avatar-rectangle-fox.webp' } }}
|
|
333
|
+
action={{ label: 'View profile', onClick: fn() }}
|
|
334
|
+
expanded={expandedStates.avatarImage}
|
|
335
|
+
onToggle={() => handleToggle('avatarImage')}
|
|
336
|
+
/>
|
|
337
|
+
<CriticalBanner
|
|
338
|
+
sentiment="negative"
|
|
339
|
+
title="Avatar with initials"
|
|
340
|
+
description={lorem10}
|
|
341
|
+
media={{ avatar: { profileName: 'John Doe' } }}
|
|
342
|
+
action={{ label: 'Send invite', onClick: fn() }}
|
|
343
|
+
expanded={expandedStates.avatarInitials}
|
|
344
|
+
onToggle={() => handleToggle('avatarInitials')}
|
|
345
|
+
/>
|
|
346
|
+
<CriticalBanner
|
|
347
|
+
sentiment="warning"
|
|
348
|
+
title="Avatar with custom icon"
|
|
349
|
+
description={lorem10}
|
|
350
|
+
media={{ avatar: { asset: <Bank /> } }}
|
|
351
|
+
action={{ label: 'Connect bank', onClick: fn() }}
|
|
352
|
+
expanded={expandedStates.avatarIcon}
|
|
353
|
+
onToggle={() => handleToggle('avatarIcon')}
|
|
354
|
+
/>
|
|
355
|
+
<CriticalBanner
|
|
356
|
+
sentiment="neutral"
|
|
357
|
+
title="Business profile with badge"
|
|
358
|
+
description={lorem10}
|
|
359
|
+
media={{ avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } } }}
|
|
360
|
+
action={{ label: 'Open account', onClick: fn() }}
|
|
361
|
+
expanded={expandedStates.businessBadge}
|
|
362
|
+
onToggle={() => handleToggle('businessBadge')}
|
|
363
|
+
/>
|
|
364
|
+
<CriticalBanner
|
|
365
|
+
sentiment="negative"
|
|
366
|
+
title="Personal profile with badge"
|
|
367
|
+
description={lorem10}
|
|
368
|
+
media={{ avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } } }}
|
|
369
|
+
action={{ label: 'Verify', onClick: fn() }}
|
|
370
|
+
expanded={expandedStates.personalBadge}
|
|
371
|
+
onToggle={() => handleToggle('personalBadge')}
|
|
372
|
+
/>
|
|
373
|
+
</>
|
|
374
|
+
);
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* The CriticalBanner is fully controlled - the parent component manages the `expanded` state.
|
|
380
|
+
* Clicking the chevron calls `onToggle`, allowing the parent to update the state.
|
|
381
|
+
*/
|
|
382
|
+
export const ExpandCollapse: Story = {
|
|
383
|
+
render: () => {
|
|
384
|
+
const [expanded, setExpanded] = useState(true);
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<>
|
|
388
|
+
<CriticalBanner
|
|
389
|
+
sentiment="negative"
|
|
390
|
+
title="Your account requires verification"
|
|
391
|
+
description="Please verify your identity to continue using all features. This process typically takes 2-3 minutes."
|
|
392
|
+
action={{ label: 'Verify now', onClick: fn() }}
|
|
393
|
+
actionSecondary={{ label: 'Learn more', onClick: fn() }}
|
|
394
|
+
expanded={expanded}
|
|
395
|
+
onToggle={() => setExpanded(!expanded)}
|
|
396
|
+
/>
|
|
397
|
+
<div style={{ marginTop: '1rem' }}>
|
|
398
|
+
<Button v2 onClick={() => setExpanded(!expanded)}>
|
|
399
|
+
{expanded ? 'Collapse externally' : 'Expand externally'}
|
|
400
|
+
</Button>
|
|
401
|
+
</div>
|
|
402
|
+
</>
|
|
403
|
+
);
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* While the component itself will stretch to the full available width, the text container will be
|
|
409
|
+
* capped at `480px` to ensure optimal readability.
|
|
410
|
+
*/
|
|
411
|
+
export const ParagraphWidth: Story = {
|
|
412
|
+
parameters: {
|
|
413
|
+
docs: {
|
|
414
|
+
canvas: {
|
|
415
|
+
sourceState: 'hidden',
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
render: () => {
|
|
420
|
+
const [expanded, setExpanded] = useState(true);
|
|
421
|
+
return (
|
|
422
|
+
<CriticalBanner
|
|
423
|
+
title={lorem10}
|
|
424
|
+
description={lorem20}
|
|
425
|
+
sentiment="negative"
|
|
426
|
+
action={{ label: 'View details', onClick: fn() }}
|
|
427
|
+
expanded={expanded}
|
|
428
|
+
onToggle={() => setExpanded(!expanded)}
|
|
429
|
+
/>
|
|
430
|
+
);
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Demonstrates the accessibility features of CriticalBanner.
|
|
436
|
+
* The component uses aria-live="assertive" to immediately announce changes to screen readers.
|
|
437
|
+
*
|
|
438
|
+
* To test with a screen reader:
|
|
439
|
+
* 1. Enable your screen reader (VoiceOver on Mac: Cmd+F5, NVDA on Windows)
|
|
440
|
+
* 2. Click "Show banner" - the banner content should be announced immediately
|
|
441
|
+
* 3. Click the chevron to expand/collapse - changes should be announced
|
|
442
|
+
* 4. Click "Hide banner" and then "Show banner" again - it should announce again
|
|
443
|
+
*
|
|
444
|
+
* Announcement behavior:
|
|
445
|
+
* - Collapsed with title: Announces title only
|
|
446
|
+
* - Collapsed without title: Announces description only
|
|
447
|
+
* - Expanded: Announces title, description, and action button labels
|
|
448
|
+
*/
|
|
449
|
+
export const LiveRegionAnnouncements: Story = {
|
|
450
|
+
render: () => {
|
|
451
|
+
const [showBanner, setShowBanner] = useState(false);
|
|
452
|
+
const [expanded, setExpanded] = useState(false);
|
|
453
|
+
|
|
454
|
+
return (
|
|
455
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
456
|
+
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
|
457
|
+
<Button v2 onClick={() => setShowBanner(!showBanner)}>
|
|
458
|
+
{showBanner ? 'Hide banner' : 'Show banner'}
|
|
459
|
+
</Button>
|
|
460
|
+
</div>
|
|
461
|
+
|
|
462
|
+
{showBanner && (
|
|
463
|
+
<CriticalBanner
|
|
464
|
+
sentiment="negative"
|
|
465
|
+
title="Your account requires verification"
|
|
466
|
+
description="Please verify your identity to continue using all features"
|
|
467
|
+
action={{ label: 'Verify now', onClick: fn() }}
|
|
468
|
+
actionSecondary={{ label: 'Learn more', onClick: fn() }}
|
|
469
|
+
expanded={expanded}
|
|
470
|
+
onToggle={() => setExpanded(!expanded)}
|
|
471
|
+
/>
|
|
472
|
+
)}
|
|
473
|
+
</div>
|
|
474
|
+
);
|
|
475
|
+
},
|
|
476
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { fn } from 'storybook/test';
|
|
3
|
+
import { CriticalBanner } from './CriticalBanner';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof CriticalBanner> = {
|
|
6
|
+
title: 'Prompts/CriticalBanner/Tests',
|
|
7
|
+
component: CriticalBanner,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof CriticalBanner>;
|
|
12
|
+
|
|
13
|
+
const longDescription =
|
|
14
|
+
'DescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescriptionDescription';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Shows all four collapsed/expanded × with-title/without-title combinations.
|
|
18
|
+
* Used for visual regression testing of the collapsed state behaviour:
|
|
19
|
+
* - With title: description is hidden when collapsed
|
|
20
|
+
* - Without title: description is visible (up to 2 lines) when collapsed
|
|
21
|
+
*/
|
|
22
|
+
export const CollapsedVariants: Story = {
|
|
23
|
+
render: () => (
|
|
24
|
+
<div
|
|
25
|
+
style={{
|
|
26
|
+
display: 'grid',
|
|
27
|
+
gridTemplateColumns: '1fr 1fr',
|
|
28
|
+
gap: '1rem',
|
|
29
|
+
width: '100%',
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{/* Row 1: Expanded */}
|
|
33
|
+
<CriticalBanner
|
|
34
|
+
sentiment="negative"
|
|
35
|
+
title="Title (optional)"
|
|
36
|
+
description="Description"
|
|
37
|
+
action={{ label: 'Primary', onClick: fn() }}
|
|
38
|
+
expanded
|
|
39
|
+
onToggle={fn()}
|
|
40
|
+
/>
|
|
41
|
+
<CriticalBanner
|
|
42
|
+
sentiment="negative"
|
|
43
|
+
description={longDescription}
|
|
44
|
+
action={{ label: 'Primary', onClick: fn() }}
|
|
45
|
+
expanded
|
|
46
|
+
onToggle={fn()}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
{/* Row 2: Collapsed */}
|
|
50
|
+
<CriticalBanner
|
|
51
|
+
sentiment="negative"
|
|
52
|
+
title="Title (optional)"
|
|
53
|
+
description="Description"
|
|
54
|
+
action={{ label: 'Primary', onClick: fn() }}
|
|
55
|
+
expanded={false}
|
|
56
|
+
onToggle={fn()}
|
|
57
|
+
/>
|
|
58
|
+
<CriticalBanner
|
|
59
|
+
sentiment="negative"
|
|
60
|
+
description={longDescription}
|
|
61
|
+
action={{ label: 'Primary', onClick: fn() }}
|
|
62
|
+
expanded={false}
|
|
63
|
+
onToggle={fn()}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
),
|
|
67
|
+
};
|