@transferwise/components 0.0.0-experimental-85c7589 → 0.0.0-experimental-f144cf1
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/package.json
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Meta, Source } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Prompts/Snackbar/Accessibility" />
|
|
4
|
+
|
|
5
|
+
# Accessibility
|
|
6
|
+
|
|
7
|
+
Snackbar is a polite live region. It uses `role="status"` and `aria-live="polite"`, so its message
|
|
8
|
+
is announced when the screen reader is idle without interrupting the current task. It does not move
|
|
9
|
+
keyboard focus when it appears.
|
|
10
|
+
|
|
11
|
+
Visual sentiment and announcement urgency are separate. A negative Snackbar remains polite; use a
|
|
12
|
+
different component when an update is urgent enough to interrupt the user.
|
|
13
|
+
|
|
14
|
+
<Source
|
|
15
|
+
dark
|
|
16
|
+
code={`showSnackbar({
|
|
17
|
+
text: 'Transfer failed',
|
|
18
|
+
sentiment: 'negative',
|
|
19
|
+
});`}
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
## Actions and dismissal
|
|
23
|
+
|
|
24
|
+
The optional action uses its visible label as its accessible name. Keep the label concise and make
|
|
25
|
+
the outcome clear. Timed Snackbars pause while the pointer is hovering over them or while keyboard
|
|
26
|
+
focus is inside.
|
|
27
|
+
|
|
28
|
+
The polite announcement tells screen-reader users that pressing `Escape` dismisses the Snackbar
|
|
29
|
+
without moving focus. On desktop, the close button also has an accessible name and dismisses through
|
|
30
|
+
the normal exit motion. On mobile, the Snackbar can be dismissed with a downward swipe. A swipe
|
|
31
|
+
below 50% of its rendered height, an upward or horizontal gesture, or a cancelled gesture returns it
|
|
32
|
+
to its resting position.
|
|
33
|
+
|
|
34
|
+
<Source
|
|
35
|
+
dark
|
|
36
|
+
code={`showSnackbar({
|
|
37
|
+
text: 'Transfer deleted',
|
|
38
|
+
action: {
|
|
39
|
+
label: 'Undo',
|
|
40
|
+
onClick: handleUndo,
|
|
41
|
+
},
|
|
42
|
+
duration: 'extended',
|
|
43
|
+
onDismiss: handleDismiss,
|
|
44
|
+
});`}
|
|
45
|
+
/>
|
|
46
|
+
|
|
47
|
+
## Media
|
|
48
|
+
|
|
49
|
+
Default sentiment media already includes the appropriate accessible treatment. If custom media
|
|
50
|
+
conveys meaning, add its accessibility attributes directly to the element you pass. Decorative
|
|
51
|
+
custom media should be hidden from assistive technology.
|
|
52
|
+
|
|
53
|
+
<Source
|
|
54
|
+
dark
|
|
55
|
+
code={`showSnackbar({
|
|
56
|
+
text: 'Added to your wallet',
|
|
57
|
+
media: <Card title="Card" />,
|
|
58
|
+
});`}
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<Source
|
|
62
|
+
dark
|
|
63
|
+
code={`showSnackbar({
|
|
64
|
+
text: 'Offer applied',
|
|
65
|
+
media: <GiftBox aria-hidden />,
|
|
66
|
+
});`}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
## Motion and zoom
|
|
70
|
+
|
|
71
|
+
Snackbar removes movement and scaling when `prefers-reduced-motion: reduce` is enabled. Its content
|
|
72
|
+
reflows at high zoom and narrow viewport widths without horizontal scrolling. Keep messages short
|
|
73
|
+
and plain so they remain easy to read when text wraps.
|
|
@@ -1,89 +1,474 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
import { fn
|
|
2
|
+
import { fn } from 'storybook/test';
|
|
3
|
+
import { Card } from '@transferwise/icons';
|
|
3
4
|
|
|
4
|
-
import { wait } from '../../test-utils/wait';
|
|
5
5
|
import {
|
|
6
6
|
createSandboxStory,
|
|
7
7
|
globalScope,
|
|
8
8
|
} from '../../../.storybook/components/sandbox/SandboxEditor';
|
|
9
9
|
import Button from '../../Button';
|
|
10
10
|
import { Snackbar } from '../Snackbar';
|
|
11
|
+
import type { SnackbarProps } from '../Snackbar.types';
|
|
11
12
|
import { SnackbarConsumer } from '../SnackbarContext';
|
|
12
13
|
import SnackbarProvider from '../SnackbarProvider';
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
const SENTIMENTS = ['neutral', 'negative', 'warning', 'success', 'proposition'] as const;
|
|
16
|
+
const DURATIONS = ['default', 'extended', 'indefinite'] as const;
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
16
19
|
title: 'Prompts/Snackbar',
|
|
17
|
-
|
|
20
|
+
component: Snackbar,
|
|
21
|
+
parameters: {
|
|
22
|
+
docs: {
|
|
23
|
+
toc: true,
|
|
24
|
+
description: {
|
|
25
|
+
component:
|
|
26
|
+
'Snackbar is a brief, temporary notification that confirms the outcome of an action or process that has just happened. Keep its message short and plain: it supports one optional action, but no inline links or rich-text formatting. `Snackbar` can be rendered directly; use `SnackbarProvider` with `useSnackbar()` for app-level transient feedback.',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
args: {
|
|
31
|
+
duration: 'default',
|
|
32
|
+
loading: false,
|
|
33
|
+
sentiment: 'neutral',
|
|
34
|
+
text: 'Changes saved',
|
|
35
|
+
},
|
|
36
|
+
argTypes: {
|
|
37
|
+
text: {
|
|
38
|
+
control: 'text',
|
|
39
|
+
description: 'Short, plain-text Snackbar message.',
|
|
40
|
+
table: {
|
|
41
|
+
type: { summary: 'ReactNode' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
sentiment: {
|
|
45
|
+
control: 'radio',
|
|
46
|
+
options: SENTIMENTS,
|
|
47
|
+
description: 'Controls the colour scheme and sentiment-derived default media.',
|
|
48
|
+
table: {
|
|
49
|
+
defaultValue: { summary: 'neutral' },
|
|
50
|
+
type: { summary: 'Sentiment' },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
media: {
|
|
54
|
+
control: false,
|
|
55
|
+
description:
|
|
56
|
+
'Overrides the sentiment-derived media. Custom media is displayed in the 24×24px media slot and should provide its own accessibility attributes when meaningful.',
|
|
57
|
+
table: {
|
|
58
|
+
type: { summary: 'ReactNode' },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
loading: {
|
|
62
|
+
control: 'boolean',
|
|
63
|
+
description:
|
|
64
|
+
'Replaces the media with a ProcessIndicator, uses neutral styling, and prevents automatic dismissal.',
|
|
65
|
+
table: {
|
|
66
|
+
defaultValue: { summary: 'false' },
|
|
67
|
+
type: { summary: 'boolean' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
action: {
|
|
71
|
+
control: false,
|
|
72
|
+
description:
|
|
73
|
+
'One optional small Button. Supports a callback action or navigation through href and target.',
|
|
74
|
+
table: {
|
|
75
|
+
type: {
|
|
76
|
+
summary: 'SnackbarAction',
|
|
77
|
+
detail:
|
|
78
|
+
"Pick<ButtonProps, 'onClick' | 'href' | 'target'> & { label: ButtonProps['children'] }",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
duration: {
|
|
83
|
+
control: 'inline-radio',
|
|
84
|
+
options: DURATIONS,
|
|
85
|
+
description: 'Controls automatic dismissal using the design-defined duration presets.',
|
|
86
|
+
table: {
|
|
87
|
+
defaultValue: { summary: 'default' },
|
|
88
|
+
type: { summary: "'default' | 'extended' | 'indefinite'" },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
onDismiss: {
|
|
92
|
+
control: false,
|
|
93
|
+
description: 'Called after the Snackbar completes its exit transition.',
|
|
94
|
+
table: {
|
|
95
|
+
type: { summary: '() => void' },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
timeout: {
|
|
99
|
+
table: { disable: true },
|
|
100
|
+
},
|
|
101
|
+
theme: {
|
|
102
|
+
table: { disable: true },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
} satisfies Meta<SnackbarProps>;
|
|
106
|
+
|
|
18
107
|
export default meta;
|
|
108
|
+
type Story = StoryObj<typeof meta>;
|
|
109
|
+
|
|
110
|
+
type PreviewStoryArgs = SnackbarProps & {
|
|
111
|
+
previewAction: boolean;
|
|
112
|
+
previewMedia: 'default' | 'custom';
|
|
113
|
+
previewOnDismiss: boolean;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
type SnackbarExample = {
|
|
117
|
+
label: string;
|
|
118
|
+
props: SnackbarProps;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const previewArgGroup = {
|
|
122
|
+
category: 'Storybook Preview options',
|
|
123
|
+
type: { summary: undefined },
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const previewArgTypes = {
|
|
127
|
+
previewAction: {
|
|
128
|
+
name: 'Preview with `action`',
|
|
129
|
+
control: 'boolean',
|
|
130
|
+
table: previewArgGroup,
|
|
131
|
+
},
|
|
132
|
+
previewMedia: {
|
|
133
|
+
name: 'Preview with `media`',
|
|
134
|
+
control: 'radio',
|
|
135
|
+
options: ['default', 'custom'],
|
|
136
|
+
table: previewArgGroup,
|
|
137
|
+
},
|
|
138
|
+
previewOnDismiss: {
|
|
139
|
+
name: 'Preview with `onDismiss`',
|
|
140
|
+
control: 'boolean',
|
|
141
|
+
table: previewArgGroup,
|
|
142
|
+
},
|
|
143
|
+
} as const;
|
|
144
|
+
|
|
145
|
+
const SnackbarExamples = ({ examples }: { examples: SnackbarExample[] }) => (
|
|
146
|
+
<SnackbarProvider>
|
|
147
|
+
<SnackbarConsumer>
|
|
148
|
+
{({ createSnackbar }) => (
|
|
149
|
+
<div
|
|
150
|
+
style={{
|
|
151
|
+
display: 'flex',
|
|
152
|
+
flexWrap: 'wrap',
|
|
153
|
+
gap: 'var(--size-8)',
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{examples.map(({ label, props }) => (
|
|
157
|
+
<Button key={label} v2 size="md" onClick={() => createSnackbar(props)}>
|
|
158
|
+
{label}
|
|
159
|
+
</Button>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
163
|
+
</SnackbarConsumer>
|
|
164
|
+
</SnackbarProvider>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Use the controls to preview the public Snackbar properties. Rich values such as `action`,
|
|
169
|
+
* `media`, and `onDismiss` use dedicated preview controls because Storybook cannot edit React
|
|
170
|
+
* elements or callback functions directly.
|
|
171
|
+
*/
|
|
172
|
+
export const Playground: StoryObj<PreviewStoryArgs> = {
|
|
173
|
+
args: {
|
|
174
|
+
duration: 'default',
|
|
175
|
+
previewAction: true,
|
|
176
|
+
previewMedia: 'default',
|
|
177
|
+
previewOnDismiss: true,
|
|
178
|
+
loading: false,
|
|
179
|
+
},
|
|
180
|
+
argTypes: previewArgTypes,
|
|
181
|
+
render: ({ previewAction, previewMedia, previewOnDismiss, ...props }) => (
|
|
182
|
+
<SnackbarExamples
|
|
183
|
+
examples={[
|
|
184
|
+
{
|
|
185
|
+
label: 'Show Snackbar',
|
|
186
|
+
props: {
|
|
187
|
+
...props,
|
|
188
|
+
action: previewAction ? { label: 'View', onClick: fn() } : undefined,
|
|
189
|
+
media: previewMedia === 'custom' ? <Card title="Card" /> : undefined,
|
|
190
|
+
onDismiss: previewOnDismiss ? fn() : undefined,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
]}
|
|
194
|
+
/>
|
|
195
|
+
),
|
|
196
|
+
};
|
|
19
197
|
|
|
20
198
|
export const Sandbox = createSandboxStory({
|
|
21
|
-
code:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
199
|
+
code: `const SnackbarTrigger = () => {
|
|
200
|
+
const showSnackbar = useSnackbar();
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<Button
|
|
204
|
+
v2
|
|
205
|
+
size="md"
|
|
206
|
+
onClick={() =>
|
|
207
|
+
showSnackbar({
|
|
208
|
+
text: 'Transfer completed successfully',
|
|
209
|
+
sentiment: 'success',
|
|
210
|
+
action: {
|
|
211
|
+
label: 'View',
|
|
212
|
+
onClick: () => console.log('View clicked'),
|
|
213
|
+
},
|
|
214
|
+
duration: 'extended',
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
>
|
|
218
|
+
Show Snackbar
|
|
219
|
+
</Button>
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const App = () => (
|
|
224
|
+
<SnackbarProvider>
|
|
225
|
+
<SnackbarTrigger />
|
|
226
|
+
</SnackbarProvider>
|
|
227
|
+
);`,
|
|
40
228
|
scope: globalScope,
|
|
41
229
|
});
|
|
42
230
|
|
|
43
|
-
|
|
231
|
+
/**
|
|
232
|
+
* `Snackbar` remains available for direct rendering. For app-level feedback, wrap the relevant
|
|
233
|
+
* app boundary in `SnackbarProvider`, then call the function returned by `useSnackbar()` after an
|
|
234
|
+
* action or process completes. The Provider owns mounting, timers, replacement, and
|
|
235
|
+
* exit-before-enter behaviour.
|
|
236
|
+
*
|
|
237
|
+
* Keep the message short and plain. Snackbar copy does not support inline links or bold text.
|
|
238
|
+
*/
|
|
239
|
+
export const Basic: Story = {
|
|
240
|
+
args: {
|
|
241
|
+
duration: 'indefinite',
|
|
242
|
+
sentiment: 'success',
|
|
243
|
+
text: 'Changes saved',
|
|
244
|
+
},
|
|
245
|
+
parameters: {
|
|
246
|
+
docs: {
|
|
247
|
+
source: {
|
|
248
|
+
code: `// Direct rendering
|
|
249
|
+
<Snackbar text="Changes saved" sentiment="success" />;
|
|
250
|
+
|
|
251
|
+
// Programmatic app-level feedback
|
|
252
|
+
const showSnackbar = useSnackbar();
|
|
253
|
+
|
|
254
|
+
showSnackbar({
|
|
255
|
+
text: 'Changes saved',
|
|
256
|
+
sentiment: 'success',
|
|
257
|
+
});`,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
render: (args) => <SnackbarExamples examples={[{ label: 'Show Snackbar', props: args }]} />,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Use `sentiment` to match the meaning of the feedback. Sentiment controls appearance and default
|
|
266
|
+
* media; it does not change the polite screen-reader announcement.
|
|
267
|
+
*
|
|
268
|
+
* - `neutral`: general confirmation or information
|
|
269
|
+
* - `negative`: failed or unsuccessful outcome
|
|
270
|
+
* - `warning`: outcome that needs attention
|
|
271
|
+
* - `success`: successful completion
|
|
272
|
+
* - `proposition`: a relevant product proposition or benefit
|
|
273
|
+
*/
|
|
274
|
+
export const Sentiments: Story = {
|
|
275
|
+
parameters: {
|
|
276
|
+
docs: {
|
|
277
|
+
source: {
|
|
278
|
+
code: `showSnackbar({ text: 'Details updated', sentiment: 'neutral' });
|
|
279
|
+
showSnackbar({ text: 'Transfer failed', sentiment: 'negative' });
|
|
280
|
+
showSnackbar({ text: 'Check the recipient details', sentiment: 'warning' });
|
|
281
|
+
showSnackbar({ text: 'Transfer completed', sentiment: 'success' });
|
|
282
|
+
showSnackbar({ text: 'You can earn a referral reward', sentiment: 'proposition' });`,
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
render: () => (
|
|
287
|
+
<SnackbarExamples
|
|
288
|
+
examples={[
|
|
289
|
+
{ label: 'Neutral', props: { text: 'Details updated', sentiment: 'neutral' } },
|
|
290
|
+
{ label: 'Negative', props: { text: 'Transfer failed', sentiment: 'negative' } },
|
|
291
|
+
{
|
|
292
|
+
label: 'Warning',
|
|
293
|
+
props: { text: 'Check the recipient details', sentiment: 'warning' },
|
|
294
|
+
},
|
|
295
|
+
{ label: 'Success', props: { text: 'Transfer completed', sentiment: 'success' } },
|
|
296
|
+
{
|
|
297
|
+
label: 'Proposition',
|
|
298
|
+
props: { text: 'You can earn a referral reward', sentiment: 'proposition' },
|
|
299
|
+
},
|
|
300
|
+
]}
|
|
301
|
+
/>
|
|
302
|
+
),
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Snackbar supports one optional small action. Use `onClick` for an in-place action or `href` for
|
|
307
|
+
* navigation. An action dismisses the Snackbar, so use `duration="extended"` when people need
|
|
308
|
+
* enough time to read and act.
|
|
309
|
+
*/
|
|
310
|
+
export const Actions: Story = {
|
|
311
|
+
parameters: {
|
|
312
|
+
docs: {
|
|
313
|
+
source: {
|
|
314
|
+
code: `showSnackbar({
|
|
315
|
+
text: 'Transfer deleted',
|
|
316
|
+
action: {
|
|
317
|
+
label: 'Undo',
|
|
318
|
+
onClick: handleUndo,
|
|
319
|
+
},
|
|
320
|
+
duration: 'extended',
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
showSnackbar({
|
|
324
|
+
text: 'Your transfer is ready',
|
|
325
|
+
action: {
|
|
326
|
+
label: 'View transfer',
|
|
327
|
+
href: '/transfers/123',
|
|
328
|
+
},
|
|
329
|
+
duration: 'extended',
|
|
330
|
+
});`,
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
render: () => (
|
|
335
|
+
<SnackbarExamples
|
|
336
|
+
examples={[
|
|
337
|
+
{
|
|
338
|
+
label: 'Callback action',
|
|
339
|
+
props: {
|
|
340
|
+
text: 'Transfer deleted',
|
|
341
|
+
action: { label: 'Undo', onClick: fn() },
|
|
342
|
+
duration: 'extended',
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
label: 'Navigation action',
|
|
347
|
+
props: {
|
|
348
|
+
text: 'Your transfer is ready',
|
|
349
|
+
action: { label: 'View transfer', href: '/transfers/123' },
|
|
350
|
+
duration: 'extended',
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
]}
|
|
354
|
+
/>
|
|
355
|
+
),
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Default media is derived from `sentiment`. Use `media` only when a custom icon communicates the
|
|
360
|
+
* message more clearly. `loading` has the highest priority: it replaces default or custom media
|
|
361
|
+
* with a ProcessIndicator, switches to neutral styling, and remains visible indefinitely.
|
|
362
|
+
*/
|
|
363
|
+
export const MediaAndLoading: Story = {
|
|
44
364
|
parameters: {
|
|
45
365
|
docs: {
|
|
46
366
|
source: {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
367
|
+
code: `// Sentiment-derived default media
|
|
368
|
+
showSnackbar({ text: 'Transfer completed', sentiment: 'success' });
|
|
369
|
+
|
|
370
|
+
// Custom media
|
|
371
|
+
showSnackbar({
|
|
372
|
+
text: 'Added to your wallet',
|
|
373
|
+
sentiment: 'success',
|
|
374
|
+
media: <Card title="Card" />,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Loading takes precedence over media and duration
|
|
378
|
+
showSnackbar({
|
|
379
|
+
text: 'Uploading document',
|
|
380
|
+
loading: true,
|
|
381
|
+
});`,
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
render: () => (
|
|
386
|
+
<SnackbarExamples
|
|
387
|
+
examples={[
|
|
388
|
+
{
|
|
389
|
+
label: 'Default media',
|
|
390
|
+
props: { text: 'Transfer completed', sentiment: 'success', duration: 'indefinite' },
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
label: 'Custom media',
|
|
394
|
+
props: {
|
|
395
|
+
text: 'Added to your wallet',
|
|
396
|
+
sentiment: 'success',
|
|
397
|
+
media: <Card title="Card" />,
|
|
398
|
+
duration: 'indefinite',
|
|
399
|
+
},
|
|
51
400
|
},
|
|
401
|
+
{
|
|
402
|
+
label: 'Loading',
|
|
403
|
+
props: { text: 'Uploading document', loading: true },
|
|
404
|
+
},
|
|
405
|
+
]}
|
|
406
|
+
/>
|
|
407
|
+
),
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Duration is semantic rather than accepting arbitrary milliseconds:
|
|
412
|
+
*
|
|
413
|
+
* - `default`: 4 seconds for short confirmation without an action
|
|
414
|
+
* - `extended`: 6 seconds for additional reading or an action
|
|
415
|
+
* - `indefinite`: no automatic dismissal
|
|
416
|
+
*
|
|
417
|
+
* Timed Snackbars pause while hovered or while keyboard focus is inside, then resume when the
|
|
418
|
+
* interaction ends. Loading Snackbars are always indefinite.
|
|
419
|
+
*
|
|
420
|
+
* The deprecated millisecond `timeout` remains supported during migration but is not available in
|
|
421
|
+
* Storybook controls. Migrate values from 0–5000ms to `default`, values above 5000ms to `extended`,
|
|
422
|
+
* and intentionally persistent usage to `indefinite`. Semantic `duration` takes precedence when
|
|
423
|
+
* both are provided.
|
|
424
|
+
*/
|
|
425
|
+
export const Durations: Story = {
|
|
426
|
+
parameters: {
|
|
427
|
+
docs: {
|
|
428
|
+
source: {
|
|
429
|
+
code: `showSnackbar({ text: 'Changes saved', duration: 'default' });
|
|
430
|
+
showSnackbar({ text: 'Transfer deleted', duration: 'extended' });
|
|
431
|
+
showSnackbar({ text: 'Uploading document', duration: 'indefinite' });`,
|
|
52
432
|
},
|
|
53
433
|
},
|
|
54
|
-
source: {
|
|
55
|
-
type: 'code',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
render: function Render() {
|
|
59
|
-
return (
|
|
60
|
-
<SnackbarProvider>
|
|
61
|
-
<SnackbarConsumer>
|
|
62
|
-
{({ createSnackbar }) => {
|
|
63
|
-
return (
|
|
64
|
-
<Button
|
|
65
|
-
block
|
|
66
|
-
onClick={() => {
|
|
67
|
-
createSnackbar({
|
|
68
|
-
action: {
|
|
69
|
-
label: 'View',
|
|
70
|
-
onClick: fn(),
|
|
71
|
-
},
|
|
72
|
-
text: <span>Some text</span>,
|
|
73
|
-
});
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
76
|
-
Show Snackbar
|
|
77
|
-
</Button>
|
|
78
|
-
);
|
|
79
|
-
}}
|
|
80
|
-
</SnackbarConsumer>
|
|
81
|
-
</SnackbarProvider>
|
|
82
|
-
);
|
|
83
434
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
435
|
+
render: () => (
|
|
436
|
+
<SnackbarExamples
|
|
437
|
+
examples={[
|
|
438
|
+
{ label: 'Default · 4s', props: { text: 'Changes saved', duration: 'default' } },
|
|
439
|
+
{ label: 'Extended · 6s', props: { text: 'Transfer deleted', duration: 'extended' } },
|
|
440
|
+
{
|
|
441
|
+
label: 'Indefinite',
|
|
442
|
+
props: { text: 'Uploading document', duration: 'indefinite' },
|
|
443
|
+
},
|
|
444
|
+
]}
|
|
445
|
+
/>
|
|
446
|
+
),
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Snackbars dismiss after their duration, after their action is used, through the desktop close
|
|
451
|
+
* button, or with a downward swipe on mobile. A mobile swipe must reach 50% of the rendered
|
|
452
|
+
* Snackbar height; shorter or cancelled swipes return it to its resting position.
|
|
453
|
+
*
|
|
454
|
+
* `onDismiss` runs after the exit transition completes, regardless of how dismissal started.
|
|
455
|
+
*/
|
|
456
|
+
export const Dismissal: Story = {
|
|
457
|
+
args: {
|
|
458
|
+
duration: 'indefinite',
|
|
459
|
+
onDismiss: fn(),
|
|
460
|
+
text: 'Swipe down or use the close button',
|
|
461
|
+
},
|
|
462
|
+
parameters: {
|
|
463
|
+
docs: {
|
|
464
|
+
source: {
|
|
465
|
+
code: `showSnackbar({
|
|
466
|
+
text: 'Changes saved',
|
|
467
|
+
duration: 'indefinite',
|
|
468
|
+
onDismiss: handleDismiss,
|
|
469
|
+
});`,
|
|
470
|
+
},
|
|
471
|
+
},
|
|
88
472
|
},
|
|
473
|
+
render: (args) => <SnackbarExamples examples={[{ label: 'Show Snackbar', props: args }]} />,
|
|
89
474
|
};
|