@transferwise/components 46.147.0 → 46.148.0
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/main.css +53 -0
- package/build/prompt/CriticalBanner/CriticalBanner.js +78 -68
- package/build/prompt/CriticalBanner/CriticalBanner.js.map +1 -1
- package/build/prompt/CriticalBanner/CriticalBanner.mjs +79 -69
- package/build/prompt/CriticalBanner/CriticalBanner.mjs.map +1 -1
- package/build/styles/main.css +53 -0
- package/build/styles/prompt/CriticalBanner/CriticalBanner.css +48 -0
- package/build/types/prompt/CriticalBanner/CriticalBanner.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/accordion/Accordion.story.tsx +25 -0
- package/src/avatarLayout/AvatarLayout.story.tsx +10 -0
- package/src/avatarView/AvatarView.story.tsx +8 -0
- package/src/body/Body.story.tsx +12 -0
- package/src/button/_stories/Button.story.tsx +7 -1
- package/src/calendar/Calendar.story.tsx +19 -7
- package/src/carousel/Carousel.story.tsx +35 -0
- package/src/checkbox/Checkbox.story.tsx +20 -0
- package/src/checkboxButton/CheckboxButton.story.tsx +16 -0
- package/src/chevron/Chevron.story.tsx +6 -0
- package/src/chips/Chips.story.tsx +23 -0
- package/src/circularButton/CircularButton.story.tsx +13 -0
- package/src/common/baseCard/BaseCard.story.tsx +12 -0
- package/src/common/bottomSheet/BottomSheet.story.tsx +21 -0
- package/src/common/circle/Circle.story.tsx +11 -0
- package/src/container/Container.story.tsx +12 -0
- package/src/dateInput/DateInput.story.tsx +20 -0
- package/src/dateLookup/DateLookup.story.tsx +23 -0
- package/src/decision/Decision.story.tsx +36 -0
- package/src/definitionList/DefinitionList.story.tsx +16 -0
- package/src/dimmer/Dimmer.story.tsx +24 -0
- package/src/display/Display.story.tsx +11 -0
- package/src/divider/Divider.story.tsx +6 -0
- package/src/drawer/Drawer.story.tsx +25 -0
- package/src/dropFade/DropFade.story.tsx +27 -0
- package/src/emphasis/Emphasis.story.tsx +10 -0
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.story.tsx +37 -0
- package/src/field/Field.story.tsx +16 -0
- package/src/flowNavigation/FlowNavigation.story.tsx +25 -0
- package/src/header/Header.story.tsx +17 -0
- package/src/iconButton/IconButton.story.tsx +14 -0
- package/src/image/Image.story.tsx +11 -0
- package/src/info/Info.story.tsx +10 -0
- package/src/inputWithDisplayFormat/InputWithDisplayFormat.story.tsx +23 -0
- package/src/inputs/InputGroup.story.tsx +37 -0
- package/src/inputs/SearchInput.story.tsx +22 -0
- package/src/inputs/SelectInput/_stories/SelectInput.story.tsx +42 -0
- package/src/inputs/TextArea.story.tsx +22 -0
- package/src/instructionsList/InstructionsList.story.tsx +19 -0
- package/src/label/Label.story.tsx +17 -0
- package/src/link/Link.story.tsx +11 -0
- package/src/list/List.story.tsx +19 -0
- package/src/listItem/_stories/ListItem.story.tsx +20 -0
- package/src/loader/Loader.story.tsx +6 -0
- package/src/logo/Logo.story.tsx +6 -0
- package/src/main.css +53 -0
- package/src/markdown/Markdown.story.tsx +17 -0
- package/src/modal/Modal.story.tsx +23 -0
- package/src/money/Money.story.tsx +7 -0
- package/src/moneyInput/MoneyInput.story.tsx +34 -0
- package/src/nudge/Nudge.story.tsx +17 -0
- package/src/overlayHeader/OverlayHeader.story.tsx +10 -0
- package/src/phoneNumberInput/PhoneNumberInput.story.tsx +23 -0
- package/src/popover/Popover.story.tsx +12 -0
- package/src/primitives/PrimitiveAnchor/stories/PrimitiveAnchor.story.tsx +11 -0
- package/src/primitives/PrimitiveButton/stories/PrimitiveButton.story.tsx +11 -0
- package/src/processIndicator/ProcessIndicator.story.tsx +10 -0
- package/src/progress/Progress.story.tsx +6 -0
- package/src/progressBar/ProgressBar.story.tsx +12 -0
- package/src/promoCard/PromoCard.story.tsx +15 -0
- package/src/promoCard/PromoCardGroup.story.tsx +28 -0
- package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +31 -0
- package/src/prompt/CriticalBanner/CriticalBanner.accessibility.docs.mdx +9 -0
- package/src/prompt/CriticalBanner/CriticalBanner.css +48 -0
- package/src/prompt/CriticalBanner/CriticalBanner.less +72 -0
- package/src/prompt/CriticalBanner/CriticalBanner.story.tsx +180 -169
- package/src/prompt/CriticalBanner/CriticalBanner.test.story.tsx +25 -6
- package/src/prompt/CriticalBanner/CriticalBanner.test.tsx +37 -0
- package/src/prompt/CriticalBanner/CriticalBanner.tsx +92 -83
- package/src/prompt/CriticalBanner/CriticalBanner.vars.less +1 -0
- package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +30 -0
- package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +14 -0
- package/src/radio/Radio.story.tsx +34 -0
- package/src/radioGroup/RadioGroup.story.tsx +26 -0
- package/src/section/Section.story.tsx +15 -0
- package/src/segmentedControl/SegmentedControl.story.tsx +27 -0
- package/src/sentimentSurface/SentimentSurface.story.tsx +11 -0
- package/src/slidingPanel/SlidingPanel.story.tsx +19 -0
- package/src/snackbar/Snackbar.story.tsx +24 -0
- package/src/statusIcon/StatusIcon.story.tsx +6 -0
- package/src/stepper/Stepper.story.tsx +30 -0
- package/src/sticky/Sticky.story.tsx +22 -1
- package/src/switch/Switch.story.tsx +17 -0
- package/src/table/Table.story.tsx +32 -0
- package/src/tabs/Tabs.story.tsx +31 -0
- package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.story.tsx +23 -0
- package/src/tile/Tile.story.tsx +13 -0
- package/src/title/Title.story.tsx +12 -0
- package/src/tooltip/Tooltip.story.tsx +8 -0
- package/src/typeahead/Typeahead.story.tsx +33 -0
- package/src/upload/Upload.story.tsx +24 -0
- package/src/uploadInput/UploadInput.story.tsx +31 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { useState, useEffect, useRef, type ReactNode } from 'react';
|
|
1
|
+
import { useState, useEffect, useMemo, useRef, type ReactNode } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { Meta, StoryObj, Decorator } from '@storybook/react-webpack5';
|
|
4
4
|
import { fn } from 'storybook/test';
|
|
5
5
|
import { Bank, Briefcase, Star, Travel } from '@transferwise/icons';
|
|
6
|
+
import {
|
|
7
|
+
createSandboxStory,
|
|
8
|
+
globalScope,
|
|
9
|
+
} from '../../../.storybook/components/sandbox/SandboxEditor';
|
|
6
10
|
import Button from '../../button';
|
|
7
11
|
import { CriticalBanner, type CriticalBannerProps } from './CriticalBanner';
|
|
8
|
-
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* **Design guidance**: <a href="https://docs.wise.design/components/critical-banner" target="_blank">wise.design/components/critical-banner</a>
|
|
@@ -120,6 +123,43 @@ const previewArgTypes = {
|
|
|
120
123
|
},
|
|
121
124
|
} as const;
|
|
122
125
|
|
|
126
|
+
const hiddenArgTypes = {
|
|
127
|
+
sentiment: { table: { disable: true } },
|
|
128
|
+
title: { table: { disable: true } },
|
|
129
|
+
description: { table: { disable: true } },
|
|
130
|
+
action: { table: { disable: true } },
|
|
131
|
+
actionSecondary: { table: { disable: true } },
|
|
132
|
+
media: { table: { disable: true } },
|
|
133
|
+
expanded: { table: { disable: true } },
|
|
134
|
+
onToggle: { table: { disable: true } },
|
|
135
|
+
id: { table: { disable: true } },
|
|
136
|
+
className: { table: { disable: true } },
|
|
137
|
+
'data-testid': { table: { disable: true } },
|
|
138
|
+
} as const;
|
|
139
|
+
|
|
140
|
+
const AnimatedEntryPreview = (args: CriticalBannerProps) => {
|
|
141
|
+
const { id = 'animated-entry' } = args;
|
|
142
|
+
const [bannerInstance, setBannerInstance] = useState(0);
|
|
143
|
+
const animatedBanner = useMemo(
|
|
144
|
+
() => ({
|
|
145
|
+
...args,
|
|
146
|
+
id: `${id}-${bannerInstance}`,
|
|
147
|
+
}),
|
|
148
|
+
[args, bannerInstance, id],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<>
|
|
153
|
+
<CriticalBanner key={animatedBanner.id} {...animatedBanner} />
|
|
154
|
+
<div className="p-t-3">
|
|
155
|
+
<Button block v2 onClick={() => setBannerInstance((current) => current + 1)}>
|
|
156
|
+
Replay
|
|
157
|
+
</Button>
|
|
158
|
+
</div>
|
|
159
|
+
</>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
123
163
|
/** Renders children inside an iframe that inherits the parent page's stylesheets. */
|
|
124
164
|
function IframeContainer({ width, children }: { width: number; children: ReactNode }) {
|
|
125
165
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
@@ -132,25 +172,11 @@ function IframeContainer({ width, children }: { width: number; children: ReactNo
|
|
|
132
172
|
const doc = iframe.contentDocument;
|
|
133
173
|
if (!doc) return;
|
|
134
174
|
|
|
135
|
-
doc.head.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
link.rel = 'stylesheet';
|
|
141
|
-
link.href = sheet.href;
|
|
142
|
-
doc.head.appendChild(link);
|
|
143
|
-
} else if (sheet.cssRules) {
|
|
144
|
-
const style = doc.createElement('style');
|
|
145
|
-
style.textContent = Array.from(sheet.cssRules)
|
|
146
|
-
.map((r) => r.cssText)
|
|
147
|
-
.join('\n');
|
|
148
|
-
doc.head.appendChild(style);
|
|
149
|
-
}
|
|
150
|
-
} catch {
|
|
151
|
-
// Skip cross-origin sheets
|
|
152
|
-
}
|
|
153
|
-
});
|
|
175
|
+
doc.head.replaceChildren(
|
|
176
|
+
...Array.from(document.head.querySelectorAll('link[rel="stylesheet"], style')).map((node) =>
|
|
177
|
+
node.cloneNode(true),
|
|
178
|
+
),
|
|
179
|
+
);
|
|
154
180
|
|
|
155
181
|
doc.body.style.margin = '0';
|
|
156
182
|
setMountNode(doc.body);
|
|
@@ -174,18 +200,20 @@ function IframeContainer({ width, children }: { width: number; children: ReactNo
|
|
|
174
200
|
);
|
|
175
201
|
}
|
|
176
202
|
|
|
177
|
-
const ContainerDecorator
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
203
|
+
const ContainerDecorator =
|
|
204
|
+
(gap = '1rem'): Decorator =>
|
|
205
|
+
(Story) => (
|
|
206
|
+
<div
|
|
207
|
+
style={{
|
|
208
|
+
width: '100%',
|
|
209
|
+
display: 'flex',
|
|
210
|
+
flexDirection: 'column',
|
|
211
|
+
gap,
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
<Story />
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
189
217
|
|
|
190
218
|
/**
|
|
191
219
|
* Interactive playground with all controls.
|
|
@@ -220,11 +248,31 @@ export const Playground: StoryObj<PreviewStoryArgs> = {
|
|
|
220
248
|
},
|
|
221
249
|
};
|
|
222
250
|
|
|
251
|
+
export const Sandbox = createSandboxStory({
|
|
252
|
+
code: `const App = () => {
|
|
253
|
+
const [expanded, setExpanded] = React.useState(true);
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<CriticalBanner
|
|
257
|
+
sentiment="negative"
|
|
258
|
+
title="Your account requires verification"
|
|
259
|
+
description="Please verify your identity to continue using all features."
|
|
260
|
+
action={{ label: 'Verify now', onClick: () => console.log('verify') }}
|
|
261
|
+
actionSecondary={{ label: 'Learn more', onClick: () => console.log('learn more') }}
|
|
262
|
+
media={{ avatar: { asset: <Bank /> } }}
|
|
263
|
+
expanded={expanded}
|
|
264
|
+
onToggle={() => setExpanded((prev) => !prev)}
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
};`,
|
|
268
|
+
scope: globalScope,
|
|
269
|
+
});
|
|
270
|
+
|
|
223
271
|
/**
|
|
224
272
|
* There are four sentiments, with negative being the default.
|
|
225
273
|
*/
|
|
226
274
|
export const Sentiments: Story = {
|
|
227
|
-
decorators: [ContainerDecorator],
|
|
275
|
+
decorators: [ContainerDecorator('2rem')],
|
|
228
276
|
parameters: {
|
|
229
277
|
docs: {
|
|
230
278
|
source: {
|
|
@@ -235,19 +283,7 @@ export const Sentiments: Story = {
|
|
|
235
283
|
},
|
|
236
284
|
},
|
|
237
285
|
},
|
|
238
|
-
argTypes:
|
|
239
|
-
sentiment: { table: { disable: true } },
|
|
240
|
-
title: { table: { disable: true } },
|
|
241
|
-
description: { table: { disable: true } },
|
|
242
|
-
action: { table: { disable: true } },
|
|
243
|
-
actionSecondary: { table: { disable: true } },
|
|
244
|
-
media: { table: { disable: true } },
|
|
245
|
-
expanded: { table: { disable: true } },
|
|
246
|
-
onToggle: { table: { disable: true } },
|
|
247
|
-
id: { table: { disable: true } },
|
|
248
|
-
className: { table: { disable: true } },
|
|
249
|
-
'data-testid': { table: { disable: true } },
|
|
250
|
-
},
|
|
286
|
+
argTypes: hiddenArgTypes,
|
|
251
287
|
render: function Render() {
|
|
252
288
|
return (
|
|
253
289
|
<>
|
|
@@ -270,7 +306,7 @@ export const Sentiments: Story = {
|
|
|
270
306
|
* Banners can have just a primary action, both primary and secondary, or no actions at all.
|
|
271
307
|
*/
|
|
272
308
|
export const Actions: Story = {
|
|
273
|
-
decorators: [ContainerDecorator],
|
|
309
|
+
decorators: [ContainerDecorator('2rem')],
|
|
274
310
|
parameters: {
|
|
275
311
|
docs: {
|
|
276
312
|
source: {
|
|
@@ -288,19 +324,7 @@ export const Actions: Story = {
|
|
|
288
324
|
},
|
|
289
325
|
},
|
|
290
326
|
},
|
|
291
|
-
argTypes:
|
|
292
|
-
sentiment: { table: { disable: true } },
|
|
293
|
-
title: { table: { disable: true } },
|
|
294
|
-
description: { table: { disable: true } },
|
|
295
|
-
action: { table: { disable: true } },
|
|
296
|
-
actionSecondary: { table: { disable: true } },
|
|
297
|
-
media: { table: { disable: true } },
|
|
298
|
-
expanded: { table: { disable: true } },
|
|
299
|
-
onToggle: { table: { disable: true } },
|
|
300
|
-
id: { table: { disable: true } },
|
|
301
|
-
className: { table: { disable: true } },
|
|
302
|
-
'data-testid': { table: { disable: true } },
|
|
303
|
-
},
|
|
327
|
+
argTypes: hiddenArgTypes,
|
|
304
328
|
render: function Render() {
|
|
305
329
|
return (
|
|
306
330
|
<>
|
|
@@ -327,7 +351,7 @@ export const Actions: Story = {
|
|
|
327
351
|
* Each sentiment has a default status icon. Override it with a custom image or an AvatarView.
|
|
328
352
|
*/
|
|
329
353
|
export const MediaTypes: Story = {
|
|
330
|
-
decorators: [ContainerDecorator],
|
|
354
|
+
decorators: [ContainerDecorator('2rem')],
|
|
331
355
|
parameters: {
|
|
332
356
|
docs: {
|
|
333
357
|
source: {
|
|
@@ -351,19 +375,7 @@ export const MediaTypes: Story = {
|
|
|
351
375
|
},
|
|
352
376
|
},
|
|
353
377
|
},
|
|
354
|
-
argTypes:
|
|
355
|
-
sentiment: { table: { disable: true } },
|
|
356
|
-
title: { table: { disable: true } },
|
|
357
|
-
description: { table: { disable: true } },
|
|
358
|
-
action: { table: { disable: true } },
|
|
359
|
-
actionSecondary: { table: { disable: true } },
|
|
360
|
-
media: { table: { disable: true } },
|
|
361
|
-
expanded: { table: { disable: true } },
|
|
362
|
-
onToggle: { table: { disable: true } },
|
|
363
|
-
id: { table: { disable: true } },
|
|
364
|
-
className: { table: { disable: true } },
|
|
365
|
-
'data-testid': { table: { disable: true } },
|
|
366
|
-
},
|
|
378
|
+
argTypes: hiddenArgTypes,
|
|
367
379
|
render: function Render() {
|
|
368
380
|
const [expandedStates, setExpandedStates] = useState<Record<string, boolean>>({
|
|
369
381
|
default: true,
|
|
@@ -445,6 +457,31 @@ export const MediaTypes: Story = {
|
|
|
445
457
|
* When the banner is shown on wider screens, it will always be fully expanded.
|
|
446
458
|
*/
|
|
447
459
|
export const Expanded: Story = {
|
|
460
|
+
argTypes: hiddenArgTypes,
|
|
461
|
+
render: function Render() {
|
|
462
|
+
const [expanded, setExpanded] = useState(true);
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<>
|
|
466
|
+
<div className="p-b-3">
|
|
467
|
+
<Button v2 onClick={() => setExpanded(!expanded)}>
|
|
468
|
+
{expanded ? 'Collapse externally' : 'Expand externally'}
|
|
469
|
+
</Button>
|
|
470
|
+
</div>
|
|
471
|
+
<IframeContainer width={350}>
|
|
472
|
+
<CriticalBanner
|
|
473
|
+
sentiment="negative"
|
|
474
|
+
title="Your account requires verification"
|
|
475
|
+
description="Please verify your identity to continue using all features. This process typically takes 2–3 minutes."
|
|
476
|
+
action={{ label: 'Verify now', onClick: fn() }}
|
|
477
|
+
actionSecondary={{ label: 'Learn more', onClick: fn() }}
|
|
478
|
+
expanded={expanded}
|
|
479
|
+
onToggle={() => setExpanded(!expanded)}
|
|
480
|
+
/>
|
|
481
|
+
</IframeContainer>
|
|
482
|
+
</>
|
|
483
|
+
);
|
|
484
|
+
},
|
|
448
485
|
parameters: {
|
|
449
486
|
docs: {
|
|
450
487
|
source: {
|
|
@@ -462,41 +499,6 @@ export const Expanded: Story = {
|
|
|
462
499
|
},
|
|
463
500
|
},
|
|
464
501
|
},
|
|
465
|
-
argTypes: {
|
|
466
|
-
sentiment: { table: { disable: true } },
|
|
467
|
-
title: { table: { disable: true } },
|
|
468
|
-
description: { table: { disable: true } },
|
|
469
|
-
action: { table: { disable: true } },
|
|
470
|
-
actionSecondary: { table: { disable: true } },
|
|
471
|
-
media: { table: { disable: true } },
|
|
472
|
-
expanded: { table: { disable: true } },
|
|
473
|
-
onToggle: { table: { disable: true } },
|
|
474
|
-
id: { table: { disable: true } },
|
|
475
|
-
className: { table: { disable: true } },
|
|
476
|
-
'data-testid': { table: { disable: true } },
|
|
477
|
-
},
|
|
478
|
-
render: function Render() {
|
|
479
|
-
const [expanded, setExpanded] = useState(true);
|
|
480
|
-
|
|
481
|
-
return (
|
|
482
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', width: '100%' }}>
|
|
483
|
-
<div>
|
|
484
|
-
<Button v2 onClick={() => setExpanded(!expanded)}>
|
|
485
|
-
{expanded ? 'Collapse externally' : 'Expand externally'}
|
|
486
|
-
</Button>
|
|
487
|
-
</div>
|
|
488
|
-
<CriticalBanner
|
|
489
|
-
sentiment="negative"
|
|
490
|
-
title="Your account requires verification"
|
|
491
|
-
description="Please verify your identity to continue using all features. This process typically takes 2–3 minutes."
|
|
492
|
-
action={{ label: 'Verify now', onClick: fn() }}
|
|
493
|
-
actionSecondary={{ label: 'Learn more', onClick: fn() }}
|
|
494
|
-
expanded={expanded}
|
|
495
|
-
onToggle={() => setExpanded(!expanded)}
|
|
496
|
-
/>
|
|
497
|
-
</div>
|
|
498
|
-
);
|
|
499
|
-
},
|
|
500
502
|
};
|
|
501
503
|
|
|
502
504
|
/**
|
|
@@ -504,27 +506,15 @@ export const Expanded: Story = {
|
|
|
504
506
|
* is clamped to 2 lines. With a title, only the title is shown when collapsed.
|
|
505
507
|
*/
|
|
506
508
|
export const CollapsedBehaviour: Story = {
|
|
507
|
-
decorators: [ContainerDecorator],
|
|
509
|
+
decorators: [ContainerDecorator()],
|
|
508
510
|
|
|
509
|
-
argTypes:
|
|
510
|
-
sentiment: { table: { disable: true } },
|
|
511
|
-
title: { table: { disable: true } },
|
|
512
|
-
description: { table: { disable: true } },
|
|
513
|
-
action: { table: { disable: true } },
|
|
514
|
-
actionSecondary: { table: { disable: true } },
|
|
515
|
-
media: { table: { disable: true } },
|
|
516
|
-
expanded: { table: { disable: true } },
|
|
517
|
-
onToggle: { table: { disable: true } },
|
|
518
|
-
id: { table: { disable: true } },
|
|
519
|
-
className: { table: { disable: true } },
|
|
520
|
-
'data-testid': { table: { disable: true } },
|
|
521
|
-
},
|
|
511
|
+
argTypes: hiddenArgTypes,
|
|
522
512
|
render: function Render() {
|
|
523
513
|
const [expanded1, setExpanded1] = useState(false);
|
|
524
514
|
const [expanded2, setExpanded2] = useState(false);
|
|
525
515
|
|
|
526
516
|
return (
|
|
527
|
-
|
|
517
|
+
<IframeContainer width={650}>
|
|
528
518
|
<CriticalBanner
|
|
529
519
|
sentiment="negative"
|
|
530
520
|
title="Collapsed with title"
|
|
@@ -534,22 +524,21 @@ export const CollapsedBehaviour: Story = {
|
|
|
534
524
|
onToggle={() => setExpanded1(!expanded1)}
|
|
535
525
|
/>
|
|
536
526
|
<CriticalBanner
|
|
527
|
+
className="m-t-3"
|
|
537
528
|
sentiment="warning"
|
|
538
529
|
description="When there is no title, the description stays visible but is clamped to a maximum of two lines. This ensures the user always sees some context even in the collapsed state, because there is no title to summarise the message."
|
|
539
530
|
action={{ label: 'Take action', onClick: fn() }}
|
|
540
531
|
expanded={expanded2}
|
|
541
532
|
onToggle={() => setExpanded2(!expanded2)}
|
|
542
533
|
/>
|
|
543
|
-
|
|
534
|
+
</IframeContainer>
|
|
544
535
|
);
|
|
545
536
|
},
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
canvas: { sourceState: 'hidden' },
|
|
550
|
-
},
|
|
537
|
+
parameters: {
|
|
538
|
+
docs: {
|
|
539
|
+
canvas: { sourceState: 'hidden' },
|
|
551
540
|
},
|
|
552
|
-
}
|
|
541
|
+
},
|
|
553
542
|
};
|
|
554
543
|
|
|
555
544
|
/**
|
|
@@ -563,27 +552,15 @@ export const Responsiveness: Story = {
|
|
|
563
552
|
canvas: { sourceState: 'hidden' },
|
|
564
553
|
},
|
|
565
554
|
},
|
|
566
|
-
argTypes:
|
|
567
|
-
sentiment: { table: { disable: true } },
|
|
568
|
-
title: { table: { disable: true } },
|
|
569
|
-
description: { table: { disable: true } },
|
|
570
|
-
action: { table: { disable: true } },
|
|
571
|
-
actionSecondary: { table: { disable: true } },
|
|
572
|
-
media: { table: { disable: true } },
|
|
573
|
-
expanded: { table: { disable: true } },
|
|
574
|
-
onToggle: { table: { disable: true } },
|
|
575
|
-
id: { table: { disable: true } },
|
|
576
|
-
className: { table: { disable: true } },
|
|
577
|
-
'data-testid': { table: { disable: true } },
|
|
578
|
-
},
|
|
555
|
+
argTypes: hiddenArgTypes,
|
|
579
556
|
render: function Render() {
|
|
580
557
|
return (
|
|
581
|
-
<div style={{ display: 'flex', gap: 16, alignItems: 'start', minWidth:
|
|
582
|
-
{[200, 350,
|
|
558
|
+
<div style={{ display: 'flex', gap: 16, alignItems: 'start', minWidth: 1700 }}>
|
|
559
|
+
{[200, 350, 800].map((width) => (
|
|
583
560
|
<IframeContainer key={width} width={width}>
|
|
584
561
|
<CriticalBanner
|
|
585
562
|
sentiment="warning"
|
|
586
|
-
title=
|
|
563
|
+
title={`At ${width}px the buttons are ${width === 200 ? 'stacked' : width === 350 ? 'full-width' : 'left-aligned'}`}
|
|
587
564
|
description="Please verify your identity to continue using all features."
|
|
588
565
|
action={{ label: 'Verify now', onClick: fn() }}
|
|
589
566
|
actionSecondary={{ label: 'Learn more', onClick: fn() }}
|
|
@@ -595,6 +572,50 @@ export const Responsiveness: Story = {
|
|
|
595
572
|
},
|
|
596
573
|
};
|
|
597
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Mobile viewport regression test for the overhang.
|
|
577
|
+
* The overhang is always applied below 600px.
|
|
578
|
+
*/
|
|
579
|
+
export const MobileOverhang: Story = {
|
|
580
|
+
argTypes: hiddenArgTypes,
|
|
581
|
+
render: function Render() {
|
|
582
|
+
return (
|
|
583
|
+
<div style={{ width: 350 }}>
|
|
584
|
+
<CriticalBanner
|
|
585
|
+
sentiment="negative"
|
|
586
|
+
title="Your account requires verification"
|
|
587
|
+
description="Please verify your identity to continue using all features."
|
|
588
|
+
action={{ label: 'Verify now', onClick: fn() }}
|
|
589
|
+
/>
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
592
|
+
},
|
|
593
|
+
parameters: {
|
|
594
|
+
docs: {
|
|
595
|
+
canvas: { sourceState: 'hidden' },
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Mobile-only entry animation: the banner reveals from above.
|
|
602
|
+
*/
|
|
603
|
+
export const AnimatedEntry: Story = {
|
|
604
|
+
args: {
|
|
605
|
+
title: 'Your account is restricted.',
|
|
606
|
+
description: 'Review your personal details to get your account active again.',
|
|
607
|
+
action: { label: 'Review details', onClick: fn() },
|
|
608
|
+
},
|
|
609
|
+
argTypes: hiddenArgTypes,
|
|
610
|
+
render: (args) => <AnimatedEntryPreview {...args} />,
|
|
611
|
+
parameters: {
|
|
612
|
+
layout: 'fullscreen',
|
|
613
|
+
docs: {
|
|
614
|
+
canvas: { sourceState: 'hidden' },
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
|
|
598
619
|
/**
|
|
599
620
|
* The text container is capped at 480px for optimal readability, even though
|
|
600
621
|
* the banner itself stretches to the full available width.
|
|
@@ -605,31 +626,21 @@ export const ParagraphWidth: Story = {
|
|
|
605
626
|
canvas: { sourceState: 'hidden' },
|
|
606
627
|
},
|
|
607
628
|
},
|
|
608
|
-
argTypes:
|
|
609
|
-
sentiment: { table: { disable: true } },
|
|
610
|
-
title: { table: { disable: true } },
|
|
611
|
-
description: { table: { disable: true } },
|
|
612
|
-
action: { table: { disable: true } },
|
|
613
|
-
actionSecondary: { table: { disable: true } },
|
|
614
|
-
media: { table: { disable: true } },
|
|
615
|
-
expanded: { table: { disable: true } },
|
|
616
|
-
onToggle: { table: { disable: true } },
|
|
617
|
-
id: { table: { disable: true } },
|
|
618
|
-
className: { table: { disable: true } },
|
|
619
|
-
'data-testid': { table: { disable: true } },
|
|
620
|
-
},
|
|
629
|
+
argTypes: hiddenArgTypes,
|
|
621
630
|
render: function Render() {
|
|
622
631
|
const [expanded, setExpanded] = useState(true);
|
|
623
632
|
|
|
624
633
|
return (
|
|
625
|
-
<
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
634
|
+
<IframeContainer width={1000}>
|
|
635
|
+
<CriticalBanner
|
|
636
|
+
sentiment="negative"
|
|
637
|
+
title="The text container is capped at 480px for optimal readability, even though the banner itself stretches to the full available width."
|
|
638
|
+
description="To restore access, you'll need to verify your identity. This involves confirming your personal details and uploading a valid government-issued ID. The process typically takes 2–3 minutes, and your account will be restored immediately after successful verification."
|
|
639
|
+
action={{ label: 'Verify now', onClick: fn() }}
|
|
640
|
+
expanded={expanded}
|
|
641
|
+
onToggle={() => setExpanded(!expanded)}
|
|
642
|
+
/>
|
|
643
|
+
</IframeContainer>
|
|
633
644
|
);
|
|
634
645
|
},
|
|
635
646
|
};
|
|
@@ -25,7 +25,7 @@ const ANIMATION_DURATION = 200; // 150ms animation + 50ms buffer
|
|
|
25
25
|
const longDescription =
|
|
26
26
|
'We have detected unusual activity on your account that does not match your typical usage patterns. To protect your funds and personal information, we have temporarily restricted access. Please verify your identity to restore full access to your account.';
|
|
27
27
|
|
|
28
|
-
function AllVariants() {
|
|
28
|
+
function AllVariants({ gap = '1rem' }: { gap?: string }) {
|
|
29
29
|
const [states, setStates] = useState({
|
|
30
30
|
negativeExpanded: true,
|
|
31
31
|
negativeCollapsed: false,
|
|
@@ -42,7 +42,7 @@ function AllVariants() {
|
|
|
42
42
|
setStates((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap
|
|
45
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap, width: '100%' }}>
|
|
46
46
|
<CriticalBanner
|
|
47
47
|
sentiment="negative"
|
|
48
48
|
title="Negative — expanded"
|
|
@@ -133,10 +133,29 @@ export const Variants: Story = {
|
|
|
133
133
|
|
|
134
134
|
/** Mobile viewport regression test with all variants. */
|
|
135
135
|
export const MobileVariants: Story = {
|
|
136
|
-
render: () => <AllVariants />,
|
|
136
|
+
render: () => <AllVariants gap="2rem" />,
|
|
137
137
|
...withVariantConfig(['mobile']),
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
+
/** Mobile entry animation visual regression test. */
|
|
141
|
+
export const AnimatedEntryMobile: Story = {
|
|
142
|
+
render: function Render() {
|
|
143
|
+
return (
|
|
144
|
+
<CriticalBanner
|
|
145
|
+
sentiment="negative"
|
|
146
|
+
title="Your account is restricted."
|
|
147
|
+
description="Review your personal details to get your account active again."
|
|
148
|
+
action={{ label: 'Review details', onClick: fn() }}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
...withVariantConfig(['mobile'], {
|
|
153
|
+
parameters: {
|
|
154
|
+
chromatic: { pauseAnimationAtEnd: true },
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
|
|
140
159
|
/**
|
|
141
160
|
* Tests keyboard navigation and interaction:
|
|
142
161
|
* - Tab focuses the first chevron toggle button
|
|
@@ -150,7 +169,7 @@ export const KeyboardInteraction: Story = {
|
|
|
150
169
|
const [expanded2, setExpanded2] = useState(false);
|
|
151
170
|
|
|
152
171
|
return (
|
|
153
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '
|
|
172
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem', width: '100%' }}>
|
|
154
173
|
<CriticalBanner
|
|
155
174
|
sentiment="negative"
|
|
156
175
|
title="First banner"
|
|
@@ -289,7 +308,7 @@ export const RTL: Story = {
|
|
|
289
308
|
|
|
290
309
|
/** 400% zoom regression test with all variants. */
|
|
291
310
|
export const Zoom400: Story = {
|
|
292
|
-
render: () => <AllVariants />,
|
|
311
|
+
render: () => <AllVariants gap="2rem" />,
|
|
293
312
|
...withVariantConfig(['400%']),
|
|
294
313
|
};
|
|
295
314
|
|
|
@@ -353,7 +372,7 @@ export const MultiLineTitleAnimation: Story = {
|
|
|
353
372
|
export const UncontrolledState: Story = {
|
|
354
373
|
render: function Render() {
|
|
355
374
|
return (
|
|
356
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '
|
|
375
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem', width: '100%' }}>
|
|
357
376
|
<CriticalBanner
|
|
358
377
|
sentiment="negative"
|
|
359
378
|
title="Uncontrolled banner (no onToggle)"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* eslint-disable testing-library/no-container */
|
|
2
|
+
import { mockMatchMedia, mockResizeObserver, render, screen } from '../../test-utils';
|
|
3
|
+
import { resetLiveRegionAnnouncementQueue } from '../../common/liveRegion/LiveRegion';
|
|
4
|
+
import { CriticalBanner, CriticalBannerProps } from './CriticalBanner';
|
|
5
|
+
|
|
6
|
+
mockMatchMedia();
|
|
7
|
+
mockResizeObserver();
|
|
8
|
+
|
|
9
|
+
describe('CriticalBanner', () => {
|
|
10
|
+
const defaultProps: CriticalBannerProps = {
|
|
11
|
+
description: 'Please verify your identity to continue using all features.',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
resetLiveRegionAnnouncementQueue();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('applies mobile overhang classes by default', () => {
|
|
19
|
+
const { container } = render(
|
|
20
|
+
<CriticalBanner {...defaultProps} data-testid="critical-banner" />,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(screen.getByTestId('critical-banner')).toBeInTheDocument();
|
|
24
|
+
expect(container.querySelector('.wds-critical-banner-overhang')).toBeInTheDocument();
|
|
25
|
+
expect(container.querySelector('.wds-critical-banner-overhang-query')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('wraps the banner in the css entry animation structure', () => {
|
|
29
|
+
const { container } = render(
|
|
30
|
+
<CriticalBanner {...defaultProps} data-testid="critical-banner" />,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(screen.getByTestId('critical-banner')).toBeInTheDocument();
|
|
34
|
+
expect(container.querySelector('.wds-critical-banner__entry-mask')).toBeInTheDocument();
|
|
35
|
+
expect(container.querySelector('.wds-critical-banner__entry-track')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
});
|