@transferwise/components 46.167.0 → 46.168.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/README.md +1 -1
- package/build/Link/Link.js.map +1 -1
- package/build/Link/Link.mjs.map +1 -1
- package/build/Loader/Loader.js.map +1 -1
- package/build/Loader/Loader.mjs.map +1 -1
- package/build/Prompt/PrimitivePrompt/PrimitivePrompt.js +2 -1
- package/build/Prompt/PrimitivePrompt/PrimitivePrompt.js.map +1 -1
- package/build/Prompt/PrimitivePrompt/PrimitivePrompt.mjs +2 -1
- package/build/Prompt/PrimitivePrompt/PrimitivePrompt.mjs.map +1 -1
- package/build/Prompt/Snackbar/Snackbar.js +205 -0
- package/build/Prompt/Snackbar/Snackbar.js.map +1 -0
- package/build/Prompt/Snackbar/Snackbar.messages.js +14 -0
- package/build/Prompt/Snackbar/Snackbar.messages.js.map +1 -0
- package/build/Prompt/Snackbar/Snackbar.messages.mjs +10 -0
- package/build/Prompt/Snackbar/Snackbar.messages.mjs.map +1 -0
- package/build/Prompt/Snackbar/Snackbar.mjs +203 -0
- package/build/Prompt/Snackbar/Snackbar.mjs.map +1 -0
- package/build/Prompt/Snackbar/SnackbarContext.js.map +1 -0
- package/build/Prompt/Snackbar/SnackbarContext.mjs.map +1 -0
- package/build/Prompt/Snackbar/SnackbarPortal.js +12 -0
- package/build/Prompt/Snackbar/SnackbarPortal.js.map +1 -0
- package/build/Prompt/Snackbar/SnackbarPortal.mjs +8 -0
- package/build/Prompt/Snackbar/SnackbarPortal.mjs.map +1 -0
- package/build/Prompt/Snackbar/SnackbarProvider.js +42 -0
- package/build/Prompt/Snackbar/SnackbarProvider.js.map +1 -0
- package/build/Prompt/Snackbar/SnackbarProvider.mjs +38 -0
- package/build/Prompt/Snackbar/SnackbarProvider.mjs.map +1 -0
- package/build/Prompt/Snackbar/constants.js +17 -0
- package/build/Prompt/Snackbar/constants.js.map +1 -0
- package/build/Prompt/Snackbar/constants.mjs +11 -0
- package/build/Prompt/Snackbar/constants.mjs.map +1 -0
- package/build/Prompt/Snackbar/useSnackbar.js.map +1 -0
- package/build/Prompt/Snackbar/useSnackbar.mjs.map +1 -0
- package/build/Prompt/Snackbar/useSnackbarAutoDismiss.js +66 -0
- package/build/Prompt/Snackbar/useSnackbarAutoDismiss.js.map +1 -0
- package/build/Prompt/Snackbar/useSnackbarAutoDismiss.mjs +62 -0
- package/build/Prompt/Snackbar/useSnackbarAutoDismiss.mjs.map +1 -0
- package/build/Prompt/Snackbar/useSnackbarManager.js +153 -0
- package/build/Prompt/Snackbar/useSnackbarManager.js.map +1 -0
- package/build/Prompt/Snackbar/useSnackbarManager.mjs +149 -0
- package/build/Prompt/Snackbar/useSnackbarManager.mjs.map +1 -0
- package/build/Prompt/Snackbar/useSnackbarSwipeDismiss.js +135 -0
- package/build/Prompt/Snackbar/useSnackbarSwipeDismiss.js.map +1 -0
- package/build/Prompt/Snackbar/useSnackbarSwipeDismiss.mjs +131 -0
- package/build/Prompt/Snackbar/useSnackbarSwipeDismiss.mjs.map +1 -0
- package/build/Section/Section.js.map +1 -1
- package/build/Section/Section.mjs.map +1 -1
- package/build/_deprecated/Select/Select.js +1 -1
- package/build/_deprecated/Select/Select.js.map +1 -1
- package/build/_deprecated/Select/Select.mjs +1 -1
- package/build/_deprecated/Select/Select.mjs.map +1 -1
- package/build/common/BottomSheet/BottomSheet.js +1 -3
- package/build/common/BottomSheet/BottomSheet.js.map +1 -1
- package/build/common/BottomSheet/BottomSheet.mjs +1 -3
- package/build/common/BottomSheet/BottomSheet.mjs.map +1 -1
- package/build/i18n/en.json +1 -0
- package/build/i18n/en.json.js +1 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +1 -0
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/index.js +3 -3
- package/build/index.mjs +3 -3
- package/build/main.css +137 -75
- package/build/styles/IconButton/IconButton.css +3 -4
- package/build/styles/Prompt/Snackbar/Snackbar.css +134 -0
- package/build/styles/main.css +137 -75
- package/build/types/Link/Link.d.ts +1 -1
- package/build/types/Loader/Loader.d.ts +1 -1
- package/build/types/Prompt/PrimitivePrompt/PrimitivePrompt.d.ts +11 -0
- package/build/types/Prompt/PrimitivePrompt/PrimitivePrompt.d.ts.map +1 -1
- package/build/types/Prompt/Snackbar/Snackbar.d.ts +62 -0
- package/build/types/Prompt/Snackbar/Snackbar.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/Snackbar.messages.d.ts +9 -0
- package/build/types/Prompt/Snackbar/Snackbar.messages.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/Snackbar.types.d.ts +12 -0
- package/build/types/Prompt/Snackbar/Snackbar.types.d.ts.map +1 -0
- package/build/types/{Snackbar → Prompt/Snackbar}/SnackbarContext.d.ts +2 -2
- package/build/types/Prompt/Snackbar/SnackbarContext.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/SnackbarPortal.d.ts +4 -0
- package/build/types/Prompt/Snackbar/SnackbarPortal.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/SnackbarProvider.d.ts +13 -0
- package/build/types/Prompt/Snackbar/SnackbarProvider.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/constants.d.ts +9 -0
- package/build/types/Prompt/Snackbar/constants.d.ts.map +1 -0
- package/build/types/{Snackbar → Prompt/Snackbar}/index.d.ts +1 -1
- package/build/types/Prompt/Snackbar/index.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/useSnackbar.d.ts +3 -0
- package/build/types/Prompt/Snackbar/useSnackbar.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/useSnackbarAutoDismiss.d.ts +13 -0
- package/build/types/Prompt/Snackbar/useSnackbarAutoDismiss.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/useSnackbarManager.d.ts +20 -0
- package/build/types/Prompt/Snackbar/useSnackbarManager.d.ts.map +1 -0
- package/build/types/Prompt/Snackbar/useSnackbarSwipeDismiss.d.ts +22 -0
- package/build/types/Prompt/Snackbar/useSnackbarSwipeDismiss.d.ts.map +1 -0
- package/build/types/Section/Section.d.ts +1 -1
- package/build/types/_deprecated/Select/Select.d.ts +1 -1
- package/build/types/common/BottomSheet/BottomSheet.d.ts +1 -3
- package/build/types/common/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/build/types/index.d.ts +5 -5
- package/build/types/index.d.ts.map +1 -1
- package/build/types/test-utils/index.d.ts +2 -0
- package/build/types/test-utils/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/IconButton/IconButton.css +3 -4
- package/src/IconButton/IconButton.less +14 -1
- package/src/Link/Link.tsx +1 -1
- package/src/ListItem/AdditionalInfo/_stories/ListItemAdditionalInfo.story.tsx +1 -1
- package/src/ListItem/AvatarView/_stories/ListItemAvatarView.story.tsx +3 -3
- package/src/ListItem/_stories/ListItem.disabled.story.tsx +1 -1
- package/src/ListItem/_stories/ListItem.scenarios.story.tsx +2 -2
- package/src/Loader/Loader.tsx +1 -1
- package/src/Prompt/PrimitivePrompt/PrimitivePrompt.test.tsx +24 -1
- package/src/Prompt/PrimitivePrompt/PrimitivePrompt.tsx +8 -2
- package/src/Prompt/Snackbar/Snackbar.css +134 -0
- package/src/Prompt/Snackbar/Snackbar.less +159 -0
- package/src/Prompt/Snackbar/Snackbar.messages.ts +9 -0
- package/src/Prompt/Snackbar/Snackbar.test.tsx +1079 -0
- package/src/Prompt/Snackbar/Snackbar.tsx +245 -0
- package/src/Prompt/Snackbar/Snackbar.types.ts +17 -0
- package/src/{Snackbar → Prompt/Snackbar}/SnackbarContext.ts +2 -2
- package/src/Prompt/Snackbar/SnackbarPortal.tsx +8 -0
- package/src/Prompt/Snackbar/SnackbarProvider.test.tsx +783 -0
- package/src/Prompt/Snackbar/SnackbarProvider.tsx +41 -0
- package/src/Prompt/Snackbar/_stories/Snackbar.accessibility.docs.mdx +58 -0
- package/src/{Snackbar → Prompt/Snackbar}/_stories/Snackbar.rtl.test.story.tsx +17 -9
- package/src/Prompt/Snackbar/_stories/Snackbar.story.tsx +416 -0
- package/src/Prompt/Snackbar/_stories/Snackbar.test.story.tsx +263 -0
- package/src/Prompt/Snackbar/constants.ts +10 -0
- package/src/{Snackbar → Prompt/Snackbar}/index.ts +1 -1
- package/src/Prompt/Snackbar/useSnackbarAutoDismiss.ts +74 -0
- package/src/Prompt/Snackbar/useSnackbarManager.ts +169 -0
- package/src/Prompt/Snackbar/useSnackbarSwipeDismiss.ts +169 -0
- package/src/Section/Section.tsx +1 -1
- package/src/_deprecated/Select/Select.tsx +1 -1
- package/src/_deprecated/Select/_stories/Select.story.tsx +1 -1
- package/src/common/BottomSheet/BottomSheet.tsx +1 -3
- package/src/i18n/en.json +1 -0
- package/src/index.ts +5 -5
- package/src/main.css +137 -75
- package/src/main.less +1 -1
- package/build/Snackbar/Snackbar.js +0 -128
- package/build/Snackbar/Snackbar.js.map +0 -1
- package/build/Snackbar/Snackbar.mjs +0 -123
- package/build/Snackbar/Snackbar.mjs.map +0 -1
- package/build/Snackbar/SnackbarContext.js.map +0 -1
- package/build/Snackbar/SnackbarContext.mjs.map +0 -1
- package/build/Snackbar/SnackbarProvider.js +0 -46
- package/build/Snackbar/SnackbarProvider.js.map +0 -1
- package/build/Snackbar/SnackbarProvider.mjs +0 -42
- package/build/Snackbar/SnackbarProvider.mjs.map +0 -1
- package/build/Snackbar/useSnackbar.js.map +0 -1
- package/build/Snackbar/useSnackbar.mjs.map +0 -1
- package/build/styles/Snackbar/Snackbar.css +0 -71
- package/build/types/Snackbar/Snackbar.d.ts +0 -16
- package/build/types/Snackbar/Snackbar.d.ts.map +0 -1
- package/build/types/Snackbar/SnackbarContext.d.ts.map +0 -1
- package/build/types/Snackbar/SnackbarProvider.d.ts +0 -6
- package/build/types/Snackbar/SnackbarProvider.d.ts.map +0 -1
- package/build/types/Snackbar/index.d.ts.map +0 -1
- package/build/types/Snackbar/useSnackbar.d.ts +0 -3
- package/build/types/Snackbar/useSnackbar.d.ts.map +0 -1
- package/src/Snackbar/Snackbar.css +0 -71
- package/src/Snackbar/Snackbar.less +0 -80
- package/src/Snackbar/Snackbar.test.tsx +0 -206
- package/src/Snackbar/Snackbar.tsx +0 -129
- package/src/Snackbar/SnackbarProvider.tsx +0 -33
- package/src/Snackbar/_stories/Snackbar.story.tsx +0 -89
- package/src/Snackbar/_stories/Snackbar.test.story.tsx +0 -139
- /package/build/{Snackbar → Prompt/Snackbar}/SnackbarContext.js +0 -0
- /package/build/{Snackbar → Prompt/Snackbar}/SnackbarContext.mjs +0 -0
- /package/build/{Snackbar → Prompt/Snackbar}/useSnackbar.js +0 -0
- /package/build/{Snackbar → Prompt/Snackbar}/useSnackbar.mjs +0 -0
- /package/src/{Snackbar → Prompt/Snackbar}/useSnackbar.ts +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import SnackbarPortal from './SnackbarPortal';
|
|
4
|
+
import { SnackbarContext } from './SnackbarContext';
|
|
5
|
+
import useSnackbarManager from './useSnackbarManager';
|
|
6
|
+
|
|
7
|
+
export interface SnackbarProviderProps {
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Use per-Snackbar `duration` instead. Explicit millisecond values remain supported
|
|
10
|
+
* and are not automatically remapped when no `duration` is supplied.
|
|
11
|
+
* When neither option is supplied, the default is 4000ms (previously 4500ms), after entry finishes.
|
|
12
|
+
* To migrate, use `default` for 0–5000ms, `extended` for values above 5000ms, and `indefinite`
|
|
13
|
+
* for intentionally persistent notifications.
|
|
14
|
+
*/
|
|
15
|
+
timeout?: number;
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function SnackbarProvider({ timeout, children }: SnackbarProviderProps) {
|
|
20
|
+
const { active, announcementKey, createSnackbar, dismissSnackbar, handleExited, visible } =
|
|
21
|
+
useSnackbarManager();
|
|
22
|
+
const contextValue = useMemo(() => ({ createSnackbar }), [createSnackbar]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<SnackbarContext.Provider value={contextValue}>
|
|
26
|
+
{active ? (
|
|
27
|
+
<SnackbarPortal
|
|
28
|
+
// Each instance gets a fresh appear transition and live-region announcement.
|
|
29
|
+
key={active.id}
|
|
30
|
+
{...active.props}
|
|
31
|
+
announcementKey={announcementKey}
|
|
32
|
+
timeout={timeout}
|
|
33
|
+
visible={visible}
|
|
34
|
+
onDismissRequest={dismissSnackbar}
|
|
35
|
+
onExited={handleExited}
|
|
36
|
+
/>
|
|
37
|
+
) : null}
|
|
38
|
+
{children}
|
|
39
|
+
</SnackbarContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Meta, Source } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Prompts/Snackbar/Accessibility" />
|
|
4
|
+
|
|
5
|
+
# Accessibility
|
|
6
|
+
|
|
7
|
+
## Announcements
|
|
8
|
+
|
|
9
|
+
Snackbar uses `role="status"`, `aria-live="polite"` and `aria-atomic="true"` for every sentiment,
|
|
10
|
+
including negative. Showing a message requests a polite announcement without moving focus.
|
|
11
|
+
There is no live region while the Provider is idle.
|
|
12
|
+
|
|
13
|
+
The localised hint “Press Escape to dismiss.” follows the message, including on touch-only devices.
|
|
14
|
+
|
|
15
|
+
## Keyboard and dismissal
|
|
16
|
+
|
|
17
|
+
- The action follows normal tab order. Buttons support Enter and Space; links support Enter.
|
|
18
|
+
- Escape dismisses unless another overlay has already handled it.
|
|
19
|
+
- Desktop has a close IconButton labelled “Close”. On small screens it is hidden and not focusable.
|
|
20
|
+
- Swipe or drag down at least half the surface height to dismiss. Shorter or cancelled gestures return to place.
|
|
21
|
+
|
|
22
|
+
There is no shortcut to reach the action. Do not make Snackbar the only way to access an essential action.
|
|
23
|
+
|
|
24
|
+
## Reduced motion
|
|
25
|
+
|
|
26
|
+
Reduced motion removes entry/exit movement and scale, and swipe transitions.
|
|
27
|
+
|
|
28
|
+
## Media
|
|
29
|
+
|
|
30
|
+
Include essential meaning in the message: custom media is outside its live region.
|
|
31
|
+
Hide decorative icons from assistive technology:
|
|
32
|
+
|
|
33
|
+
<Source
|
|
34
|
+
dark
|
|
35
|
+
code={`import { Button, SnackbarProvider, useSnackbar } from '@transferwise/components';
|
|
36
|
+
import { Card } from '@transferwise/icons';
|
|
37
|
+
|
|
38
|
+
function CardFeedback() {
|
|
39
|
+
const createSnackbar = useSnackbar();
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Button v2 size="md" onClick={() => createSnackbar({
|
|
43
|
+
text: 'Card added to your wallet',
|
|
44
|
+
media: <Card aria-hidden />,
|
|
45
|
+
})}>
|
|
46
|
+
Show card confirmation
|
|
47
|
+
</Button>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default function App() {
|
|
52
|
+
return (
|
|
53
|
+
<SnackbarProvider>
|
|
54
|
+
<CardFeedback />
|
|
55
|
+
</SnackbarProvider>
|
|
56
|
+
);
|
|
57
|
+
}`}
|
|
58
|
+
/>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { useEffect
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
3
|
|
|
4
|
-
import { useRtlDiffI18n, withRTLDiff } from '
|
|
4
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../../.storybook/decorators/RtlDiffDecorator';
|
|
5
5
|
import { Snackbar } from '../Snackbar';
|
|
6
|
+
import type { SnackbarCreationProps } from '../Snackbar.types';
|
|
7
|
+
import SnackbarProvider from '../SnackbarProvider';
|
|
8
|
+
import useSnackbar from '../useSnackbar';
|
|
6
9
|
|
|
7
10
|
export default {
|
|
8
11
|
component: Snackbar,
|
|
@@ -12,18 +15,23 @@ export default {
|
|
|
12
15
|
|
|
13
16
|
type Story = StoryObj<typeof Snackbar>;
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function SnackbarStory({ text, action }: { text: string; action?: { label: string } }) {
|
|
18
|
-
const [timestamp, setTimestamp] = useState(0);
|
|
18
|
+
function ShowOnMount(props: SnackbarCreationProps) {
|
|
19
|
+
const createSnackbar = useSnackbar();
|
|
19
20
|
|
|
20
21
|
useEffect(() => {
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
createSnackbar(props);
|
|
23
|
+
// This visual fixture creates once per mount, not on every render of its props.
|
|
24
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
23
25
|
}, []);
|
|
24
26
|
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function SnackbarStory({ text, action }: { text: string; action?: { label: string } }) {
|
|
25
31
|
return (
|
|
26
|
-
<
|
|
32
|
+
<SnackbarProvider>
|
|
33
|
+
<ShowOnMount text={<span>{text}</span>} action={action} duration="indefinite" />
|
|
34
|
+
</SnackbarProvider>
|
|
27
35
|
);
|
|
28
36
|
}
|
|
29
37
|
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { fn } from 'storybook/test';
|
|
3
|
+
import { Card } from '@transferwise/icons';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createSandboxStory,
|
|
7
|
+
globalScope,
|
|
8
|
+
} from '../../../../.storybook/components/sandbox/SandboxEditor';
|
|
9
|
+
import Button from '../../../Button';
|
|
10
|
+
import { Snackbar } from '../Snackbar';
|
|
11
|
+
import type { SnackbarCreationProps } from '../Snackbar.types';
|
|
12
|
+
import SnackbarProvider from '../SnackbarProvider';
|
|
13
|
+
import useSnackbar from '../useSnackbar';
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
component: Snackbar,
|
|
17
|
+
title: 'Prompts/Snackbar',
|
|
18
|
+
tags: ['new'],
|
|
19
|
+
parameters: {
|
|
20
|
+
docs: {
|
|
21
|
+
description: {
|
|
22
|
+
component: `Snackbar provides short feedback after an action, with one optional action of its own.
|
|
23
|
+
Use \`useSnackbar()\` inside \`SnackbarProvider\`. A newer message replaces the current one; Snackbars do not stack.
|
|
24
|
+
|
|
25
|
+
Change the Playground controls, then click **Show Snackbar** to try them. Keep messages short and plain.
|
|
26
|
+
|
|
27
|
+
See [Accessibility](?path=/docs/prompts-snackbar-accessibility--docs) for keyboard and announcement behaviour.`,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
argTypes: {
|
|
32
|
+
text: { control: 'text', table: { type: { summary: 'ReactNode' } } },
|
|
33
|
+
sentiment: {
|
|
34
|
+
control: 'select',
|
|
35
|
+
options: ['neutral', 'success', 'warning', 'negative', 'proposition'],
|
|
36
|
+
},
|
|
37
|
+
duration: { control: 'select', options: ['default', 'extended', 'indefinite'] },
|
|
38
|
+
loading: { control: 'boolean' },
|
|
39
|
+
media: { control: false, table: { type: { summary: 'ReactNode' } } },
|
|
40
|
+
action: {
|
|
41
|
+
control: false,
|
|
42
|
+
table: {
|
|
43
|
+
type: {
|
|
44
|
+
summary: 'SnackbarAction',
|
|
45
|
+
detail:
|
|
46
|
+
'{ label: ReactNode; href?: string; target?: string; onClick?: MouseEventHandler }',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
onDismiss: { control: false },
|
|
51
|
+
announcementKey: { table: { disable: true } },
|
|
52
|
+
onDismissRequest: { table: { disable: true } },
|
|
53
|
+
onExited: { table: { disable: true } },
|
|
54
|
+
visible: { table: { disable: true } },
|
|
55
|
+
timeout: { table: { disable: true } },
|
|
56
|
+
timestamp: { table: { disable: true } },
|
|
57
|
+
theme: { table: { disable: true } },
|
|
58
|
+
},
|
|
59
|
+
} satisfies Meta<typeof Snackbar>;
|
|
60
|
+
|
|
61
|
+
export default meta;
|
|
62
|
+
|
|
63
|
+
type PreviewStoryArgs = Pick<
|
|
64
|
+
SnackbarCreationProps,
|
|
65
|
+
'sentiment' | 'duration' | 'loading' | 'onDismiss'
|
|
66
|
+
> & {
|
|
67
|
+
text: string;
|
|
68
|
+
previewMedia: 'default' | 'card';
|
|
69
|
+
previewActionLabel: string;
|
|
70
|
+
previewActionHref: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function SnackbarTrigger({
|
|
74
|
+
label = 'Show Snackbar',
|
|
75
|
+
...props
|
|
76
|
+
}: SnackbarCreationProps & { label?: string }) {
|
|
77
|
+
const createSnackbar = useSnackbar();
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Button v2 size="md" onClick={() => createSnackbar(props)}>
|
|
81
|
+
{label}
|
|
82
|
+
</Button>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const onActionClick = fn();
|
|
87
|
+
|
|
88
|
+
type SnackbarExample = SnackbarCreationProps & { label: string };
|
|
89
|
+
|
|
90
|
+
function SnackbarExamples({ examples }: { examples: SnackbarExample[] }) {
|
|
91
|
+
return (
|
|
92
|
+
<SnackbarProvider>
|
|
93
|
+
<div className="d-flex flex-wrap" style={{ gap: 'var(--size-8)' }}>
|
|
94
|
+
{examples.map(({ label, ...props }) => (
|
|
95
|
+
<SnackbarTrigger key={label} label={label} {...props} />
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
</SnackbarProvider>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const getExamplesCode = (examples: string, withMedia = false) =>
|
|
103
|
+
`import { Button, SnackbarProvider, useSnackbar } from '@transferwise/components';
|
|
104
|
+
${withMedia ? "import { Card } from '@transferwise/icons';\n" : ''}
|
|
105
|
+
function App() {
|
|
106
|
+
return (
|
|
107
|
+
<SnackbarProvider>
|
|
108
|
+
<ShowExamples />
|
|
109
|
+
</SnackbarProvider>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ShowExamples() {
|
|
114
|
+
const createSnackbar = useSnackbar();
|
|
115
|
+
const examples = ${examples};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className="d-flex flex-wrap" style={{ gap: 'var(--size-8)' }}>
|
|
119
|
+
{examples.map(({ label, ...props }) => (
|
|
120
|
+
<Button key={label} v2 size="md" onClick={() => createSnackbar(props)}>
|
|
121
|
+
{label}
|
|
122
|
+
</Button>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default App;`;
|
|
129
|
+
|
|
130
|
+
const getExampleCode = ({
|
|
131
|
+
text,
|
|
132
|
+
sentiment,
|
|
133
|
+
duration,
|
|
134
|
+
loading,
|
|
135
|
+
previewMedia,
|
|
136
|
+
previewActionLabel,
|
|
137
|
+
previewActionHref,
|
|
138
|
+
}: PreviewStoryArgs) => `function App() {
|
|
139
|
+
return (
|
|
140
|
+
<SnackbarProvider>
|
|
141
|
+
<ShowSnackbar />
|
|
142
|
+
</SnackbarProvider>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function ShowSnackbar() {
|
|
147
|
+
const createSnackbar = useSnackbar();
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<Button v2 size="md" onClick={() => createSnackbar({
|
|
151
|
+
text: ${JSON.stringify(text)},
|
|
152
|
+
sentiment: ${JSON.stringify(sentiment)},
|
|
153
|
+
duration: ${JSON.stringify(duration)},${loading ? '\n loading: true,' : ''}${previewMedia === 'card' ? '\n media: <Card aria-hidden />,' : ''}${
|
|
154
|
+
previewActionLabel
|
|
155
|
+
? `
|
|
156
|
+
action: {
|
|
157
|
+
label: ${JSON.stringify(previewActionLabel)},${previewActionHref ? `\n href: ${JSON.stringify(previewActionHref)},` : ''}
|
|
158
|
+
onClick: () => console.log('Action clicked'),
|
|
159
|
+
},`
|
|
160
|
+
: ''
|
|
161
|
+
}
|
|
162
|
+
onDismiss: () => console.log('Dismissed'),
|
|
163
|
+
})}>
|
|
164
|
+
Show Snackbar
|
|
165
|
+
</Button>
|
|
166
|
+
);
|
|
167
|
+
}`;
|
|
168
|
+
|
|
169
|
+
/** Change the controls, then click Show Snackbar to create a message with those values. */
|
|
170
|
+
export const Playground: StoryObj<PreviewStoryArgs> = {
|
|
171
|
+
args: {
|
|
172
|
+
text: 'Changes saved',
|
|
173
|
+
sentiment: 'success',
|
|
174
|
+
duration: 'default',
|
|
175
|
+
loading: false,
|
|
176
|
+
previewMedia: 'default',
|
|
177
|
+
previewActionLabel: '',
|
|
178
|
+
previewActionHref: '',
|
|
179
|
+
onDismiss: fn(),
|
|
180
|
+
},
|
|
181
|
+
argTypes: {
|
|
182
|
+
previewMedia: {
|
|
183
|
+
name: 'Preview with custom media',
|
|
184
|
+
control: 'select',
|
|
185
|
+
options: ['default', 'card'],
|
|
186
|
+
table: { category: 'Storybook Preview options' },
|
|
187
|
+
},
|
|
188
|
+
previewActionLabel: {
|
|
189
|
+
name: 'Action label (empty for no action)',
|
|
190
|
+
control: 'text',
|
|
191
|
+
table: { category: 'Storybook Preview options' },
|
|
192
|
+
},
|
|
193
|
+
previewActionHref: {
|
|
194
|
+
name: 'Action href (empty for callback only)',
|
|
195
|
+
control: 'text',
|
|
196
|
+
table: { category: 'Storybook Preview options' },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
render: ({ previewMedia, previewActionLabel, previewActionHref, ...props }) => (
|
|
200
|
+
<SnackbarProvider>
|
|
201
|
+
<SnackbarTrigger
|
|
202
|
+
{...props}
|
|
203
|
+
media={previewMedia === 'card' ? <Card aria-hidden /> : undefined}
|
|
204
|
+
action={
|
|
205
|
+
previewActionLabel
|
|
206
|
+
? {
|
|
207
|
+
label: previewActionLabel,
|
|
208
|
+
href: previewActionHref || undefined,
|
|
209
|
+
onClick: onActionClick,
|
|
210
|
+
}
|
|
211
|
+
: undefined
|
|
212
|
+
}
|
|
213
|
+
/>
|
|
214
|
+
</SnackbarProvider>
|
|
215
|
+
),
|
|
216
|
+
parameters: {
|
|
217
|
+
chromatic: { disableSnapshot: true },
|
|
218
|
+
docs: {
|
|
219
|
+
source: {
|
|
220
|
+
type: 'dynamic',
|
|
221
|
+
transform: (source: string, { args }: { args: PreviewStoryArgs }) =>
|
|
222
|
+
`import { Button, SnackbarProvider, useSnackbar } from '@transferwise/components';\n${args.previewMedia === 'card' ? "import { Card } from '@transferwise/icons';\n" : ''}\n${getExampleCode(args)}\n\nexport default App;`,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export const Sandbox = createSandboxStory({
|
|
229
|
+
code: getExampleCode({
|
|
230
|
+
text: 'Card added',
|
|
231
|
+
sentiment: 'neutral',
|
|
232
|
+
duration: 'extended',
|
|
233
|
+
previewMedia: 'card',
|
|
234
|
+
previewActionLabel: 'View',
|
|
235
|
+
previewActionHref: '',
|
|
236
|
+
}),
|
|
237
|
+
scope: globalScope,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Choose a sentiment to match the message. Each has default media; all use polite announcements.
|
|
242
|
+
* Show one at a time using the buttons below.
|
|
243
|
+
*/
|
|
244
|
+
export const Sentiments: StoryObj = {
|
|
245
|
+
render: () => (
|
|
246
|
+
<SnackbarExamples
|
|
247
|
+
examples={(['neutral', 'success', 'warning', 'negative', 'proposition'] as const).map(
|
|
248
|
+
(sentiment) => ({ label: sentiment, sentiment, text: 'Your details have been updated' }),
|
|
249
|
+
)}
|
|
250
|
+
/>
|
|
251
|
+
),
|
|
252
|
+
parameters: {
|
|
253
|
+
controls: { disable: true },
|
|
254
|
+
chromatic: { disableSnapshot: true },
|
|
255
|
+
docs: {
|
|
256
|
+
source: {
|
|
257
|
+
code: getExamplesCode(`(['neutral', 'success', 'warning', 'negative', 'proposition'] as const).map(
|
|
258
|
+
(sentiment) => ({ label: sentiment, sentiment, text: 'Your details have been updated' }),
|
|
259
|
+
)`),
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Default media follows the sentiment. Override it with a custom icon, including for neutral and
|
|
267
|
+
* proposition. Wise icons fill the 24×24px slot without an explicit size.
|
|
268
|
+
*/
|
|
269
|
+
export const MediaTypes: StoryObj = {
|
|
270
|
+
render: () => (
|
|
271
|
+
<SnackbarExamples
|
|
272
|
+
examples={[
|
|
273
|
+
{ label: 'Default media', text: 'Changes saved', sentiment: 'success' },
|
|
274
|
+
{ label: 'Custom neutral', text: 'Card added', media: <Card aria-hidden /> },
|
|
275
|
+
{
|
|
276
|
+
label: 'Custom proposition',
|
|
277
|
+
text: 'Your new card is ready',
|
|
278
|
+
sentiment: 'proposition',
|
|
279
|
+
media: <Card aria-hidden />,
|
|
280
|
+
},
|
|
281
|
+
]}
|
|
282
|
+
/>
|
|
283
|
+
),
|
|
284
|
+
parameters: {
|
|
285
|
+
controls: { disable: true },
|
|
286
|
+
chromatic: { disableSnapshot: true },
|
|
287
|
+
docs: {
|
|
288
|
+
source: {
|
|
289
|
+
code: getExamplesCode(
|
|
290
|
+
`[
|
|
291
|
+
{ label: 'Default media', text: 'Changes saved', sentiment: 'success' as const },
|
|
292
|
+
{ label: 'Custom neutral', text: 'Card added', media: <Card aria-hidden /> },
|
|
293
|
+
{
|
|
294
|
+
label: 'Custom proposition',
|
|
295
|
+
text: 'Your new card is ready',
|
|
296
|
+
sentiment: 'proposition' as const,
|
|
297
|
+
media: <Card aria-hidden />,
|
|
298
|
+
},
|
|
299
|
+
]`,
|
|
300
|
+
true,
|
|
301
|
+
),
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/** Loading replaces media, uses neutral styling and prevents auto-dismiss, regardless of duration. */
|
|
308
|
+
export const Loading: StoryObj<PreviewStoryArgs> = {
|
|
309
|
+
...Playground,
|
|
310
|
+
args: { ...Playground.args, text: 'Uploading document', loading: true },
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Add one action: a callback with `onClick`, navigation with `href`, or both.
|
|
315
|
+
* Activating it requests dismissal after the callback runs.
|
|
316
|
+
*/
|
|
317
|
+
export const WithAction: StoryObj = {
|
|
318
|
+
render: () => (
|
|
319
|
+
<SnackbarExamples
|
|
320
|
+
examples={[
|
|
321
|
+
{
|
|
322
|
+
label: 'Callback action',
|
|
323
|
+
text: 'Transfer deleted',
|
|
324
|
+
duration: 'extended',
|
|
325
|
+
action: { label: 'Undo', onClick: onActionClick },
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
label: 'Navigation action',
|
|
329
|
+
text: 'Your transfer is ready',
|
|
330
|
+
duration: 'extended',
|
|
331
|
+
action: { label: 'View transfer', href: '/transfers/123', target: '_blank' },
|
|
332
|
+
},
|
|
333
|
+
]}
|
|
334
|
+
/>
|
|
335
|
+
),
|
|
336
|
+
parameters: {
|
|
337
|
+
controls: { disable: true },
|
|
338
|
+
chromatic: { disableSnapshot: true },
|
|
339
|
+
docs: {
|
|
340
|
+
source: {
|
|
341
|
+
code: getExamplesCode(`[
|
|
342
|
+
{
|
|
343
|
+
label: 'Callback action',
|
|
344
|
+
text: 'Transfer deleted',
|
|
345
|
+
duration: 'extended' as const,
|
|
346
|
+
action: { label: 'Undo', onClick: () => console.log('Undo clicked') },
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
label: 'Navigation action',
|
|
350
|
+
text: 'Your transfer is ready',
|
|
351
|
+
duration: 'extended' as const,
|
|
352
|
+
action: { label: 'View transfer', href: '/transfers/123', target: '_blank' },
|
|
353
|
+
},
|
|
354
|
+
]`),
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* `default` lasts 4 seconds, `extended` lasts 6, and `indefinite` has no timer.
|
|
362
|
+
* Timers begin after entry and pause during hover, focus or a swipe, then resume with the remaining time.
|
|
363
|
+
*/
|
|
364
|
+
export const Durations: StoryObj = {
|
|
365
|
+
render: () => (
|
|
366
|
+
<SnackbarExamples
|
|
367
|
+
examples={[
|
|
368
|
+
{ label: 'Default (4s)', text: 'Changes saved', duration: 'default' },
|
|
369
|
+
{
|
|
370
|
+
label: 'Extended (6s)',
|
|
371
|
+
text: 'Transfer deleted',
|
|
372
|
+
duration: 'extended',
|
|
373
|
+
action: { label: 'Undo', onClick: onActionClick },
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
label: 'Indefinite',
|
|
377
|
+
text: 'Uploading document',
|
|
378
|
+
duration: 'indefinite',
|
|
379
|
+
loading: true,
|
|
380
|
+
},
|
|
381
|
+
]}
|
|
382
|
+
/>
|
|
383
|
+
),
|
|
384
|
+
parameters: {
|
|
385
|
+
controls: { disable: true },
|
|
386
|
+
chromatic: { disableSnapshot: true },
|
|
387
|
+
docs: {
|
|
388
|
+
source: {
|
|
389
|
+
code: getExamplesCode(`[
|
|
390
|
+
{ label: 'Default (4s)', text: 'Changes saved', duration: 'default' as const },
|
|
391
|
+
{
|
|
392
|
+
label: 'Extended (6s)',
|
|
393
|
+
text: 'Transfer deleted',
|
|
394
|
+
duration: 'extended' as const,
|
|
395
|
+
action: { label: 'Undo', onClick: () => console.log('Undo clicked') },
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
label: 'Indefinite',
|
|
399
|
+
text: 'Uploading document',
|
|
400
|
+
duration: 'indefinite' as const,
|
|
401
|
+
loading: true,
|
|
402
|
+
},
|
|
403
|
+
]`),
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Dismiss with Escape, the desktop close button, an action, or a downward swipe/drag of at least half
|
|
411
|
+
* the surface height. `onDismiss` fires after exit; check the Actions panel to see it.
|
|
412
|
+
*/
|
|
413
|
+
export const Dismissal: StoryObj<PreviewStoryArgs> = {
|
|
414
|
+
...Playground,
|
|
415
|
+
args: { ...Playground.args, duration: 'indefinite' },
|
|
416
|
+
};
|