@transferwise/components 0.0.0-experimental-4b93e2b → 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/build/Calendar/DateHeader/DateHeader.js +6 -2
- package/build/Calendar/DateHeader/DateHeader.js.map +1 -1
- package/build/Calendar/DateHeader/DateHeader.mjs +7 -3
- package/build/Calendar/DateHeader/DateHeader.mjs.map +1 -1
- package/build/Carousel/Carousel.js +2 -2
- package/build/Carousel/Carousel.js.map +1 -1
- package/build/Carousel/Carousel.mjs +3 -3
- package/build/Carousel/Carousel.mjs.map +1 -1
- package/build/Inputs/SelectInput/OptionContent/SelectInputOptionContent.js +3 -4
- package/build/Inputs/SelectInput/OptionContent/SelectInputOptionContent.js.map +1 -1
- package/build/Inputs/SelectInput/OptionContent/SelectInputOptionContent.mjs +3 -4
- package/build/Inputs/SelectInput/OptionContent/SelectInputOptionContent.mjs.map +1 -1
- package/build/ListItem/Navigation/ListItemNavigation.js +1 -1
- package/build/ListItem/Navigation/ListItemNavigation.js.map +1 -1
- package/build/ListItem/Navigation/ListItemNavigation.mjs +2 -2
- package/build/ListItem/Navigation/ListItemNavigation.mjs.map +1 -1
- package/build/PhoneNumberInput/PhoneNumberInput.js +1 -2
- package/build/PhoneNumberInput/PhoneNumberInput.js.map +1 -1
- package/build/PhoneNumberInput/PhoneNumberInput.mjs +1 -2
- package/build/PhoneNumberInput/PhoneNumberInput.mjs.map +1 -1
- package/build/PromoCard/Indicator/PromoCardIndicator.js +1 -1
- package/build/PromoCard/Indicator/PromoCardIndicator.js.map +1 -1
- package/build/PromoCard/Indicator/PromoCardIndicator.mjs +2 -2
- package/build/PromoCard/Indicator/PromoCardIndicator.mjs.map +1 -1
- package/build/Snackbar/Snackbar.js +212 -108
- package/build/Snackbar/Snackbar.js.map +1 -1
- package/build/Snackbar/Snackbar.messages.js +14 -0
- package/build/Snackbar/Snackbar.messages.js.map +1 -0
- package/build/Snackbar/Snackbar.messages.mjs +10 -0
- package/build/Snackbar/Snackbar.messages.mjs.map +1 -0
- package/build/Snackbar/Snackbar.mjs +213 -107
- package/build/Snackbar/Snackbar.mjs.map +1 -1
- package/build/Snackbar/SnackbarContext.js.map +1 -1
- package/build/Snackbar/SnackbarContext.mjs.map +1 -1
- package/build/Snackbar/SnackbarPortal.js +92 -0
- package/build/Snackbar/SnackbarPortal.js.map +1 -0
- package/build/Snackbar/SnackbarPortal.mjs +89 -0
- package/build/Snackbar/SnackbarPortal.mjs.map +1 -0
- package/build/Snackbar/SnackbarProvider.js +21 -31
- package/build/Snackbar/SnackbarProvider.js.map +1 -1
- package/build/Snackbar/SnackbarProvider.mjs +22 -32
- package/build/Snackbar/SnackbarProvider.mjs.map +1 -1
- package/build/Snackbar/constants.js +14 -0
- package/build/Snackbar/constants.js.map +1 -0
- package/build/Snackbar/constants.mjs +8 -0
- package/build/Snackbar/constants.mjs.map +1 -0
- package/build/Snackbar/useSnackbarAutoDismiss.js +135 -0
- package/build/Snackbar/useSnackbarAutoDismiss.js.map +1 -0
- package/build/Snackbar/useSnackbarAutoDismiss.mjs +131 -0
- package/build/Snackbar/useSnackbarAutoDismiss.mjs.map +1 -0
- package/build/Snackbar/useSnackbarManager.js +164 -0
- package/build/Snackbar/useSnackbarManager.js.map +1 -0
- package/build/Snackbar/useSnackbarManager.mjs +160 -0
- package/build/Snackbar/useSnackbarManager.mjs.map +1 -0
- package/build/Snackbar/useSnackbarSwipeDismiss.js +114 -0
- package/build/Snackbar/useSnackbarSwipeDismiss.js.map +1 -0
- package/build/Snackbar/useSnackbarSwipeDismiss.mjs +110 -0
- package/build/Snackbar/useSnackbarSwipeDismiss.mjs.map +1 -0
- package/build/common/PropsValues/typography.js +0 -6
- package/build/common/PropsValues/typography.js.map +1 -1
- package/build/common/PropsValues/typography.mjs +0 -6
- package/build/common/PropsValues/typography.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 -1
- package/build/index.js.map +1 -1
- package/build/index.mjs +2 -1
- package/build/index.mjs.map +1 -1
- package/build/main.css +864 -1509
- package/build/styles/Button/Button.css +3 -0
- package/build/styles/DateInput/DateInput.css +0 -13
- package/build/styles/Inputs/Input/Input.css +24 -0
- package/build/styles/Inputs/TextArea/TextArea.css +24 -0
- package/build/styles/ListItem/ListItem.css +1 -1
- package/build/styles/PhoneNumberInput/PhoneNumberInput.css +0 -3
- package/build/styles/Popover/Popover.css +6 -0
- package/build/styles/PromoCard/PromoCard.css +3 -0
- package/build/styles/Snackbar/Snackbar.css +182 -61
- package/build/styles/_deprecated/Select/Select.css +6 -0
- package/build/styles/css/accordion.css +12 -0
- package/build/styles/css/button-groups.css +6 -0
- package/build/styles/css/buttons.css +2 -0
- package/build/styles/css/decision.css +12 -0
- package/build/styles/css/dropdowns.css +18 -2
- package/build/styles/css/droppable.css +6 -0
- package/build/styles/css/footer.css +12 -0
- package/build/styles/css/input-groups.css +42 -2
- package/build/styles/css/list-group.css +6 -0
- package/build/styles/css/navbar.css +18 -2
- package/build/styles/css/neptune-core.css +512 -4
- package/build/styles/css/neptune.css +640 -1426
- package/build/styles/css/popovers.css +18 -0
- package/build/styles/css/tooltip.css +6 -0
- package/build/styles/less/neptune-tokens.less +0 -8
- package/build/styles/main.css +864 -1509
- package/build/styles/props/neptune-tokens.css +0 -8
- package/build/styles/styles/less/accordion.css +12 -0
- package/build/styles/styles/less/button-groups.css +6 -0
- package/build/styles/styles/less/buttons.css +2 -0
- package/build/styles/styles/less/decision.css +12 -0
- package/build/styles/styles/less/dropdowns.css +18 -2
- package/build/styles/styles/less/droppable.css +6 -0
- package/build/styles/styles/less/footer.css +12 -0
- package/build/styles/styles/less/forms/bootstrap-forms.css +24 -2
- package/build/styles/styles/less/input-groups.css +42 -2
- package/build/styles/styles/less/list-group.css +6 -0
- package/build/styles/styles/less/navbar.css +18 -2
- package/build/styles/styles/less/neptune-core.css +512 -4
- package/build/styles/styles/less/neptune.css +640 -1426
- package/build/styles/styles/less/popovers.css +18 -0
- package/build/styles/styles/less/tooltip.css +6 -0
- package/build/types/Inputs/SelectInput/OptionContent/SelectInputOptionContent.d.ts +1 -2
- package/build/types/Inputs/SelectInput/OptionContent/SelectInputOptionContent.d.ts.map +1 -1
- package/build/types/PhoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
- package/build/types/Snackbar/Snackbar.d.ts +41 -24
- package/build/types/Snackbar/Snackbar.d.ts.map +1 -1
- package/build/types/Snackbar/Snackbar.messages.d.ts +9 -0
- package/build/types/Snackbar/Snackbar.messages.d.ts.map +1 -0
- package/build/types/Snackbar/Snackbar.types.d.ts +21 -0
- package/build/types/Snackbar/Snackbar.types.d.ts.map +1 -0
- package/build/types/Snackbar/SnackbarContext.d.ts +2 -2
- package/build/types/Snackbar/SnackbarContext.d.ts.map +1 -1
- package/build/types/Snackbar/SnackbarPortal.d.ts +5 -0
- package/build/types/Snackbar/SnackbarPortal.d.ts.map +1 -0
- package/build/types/Snackbar/SnackbarProvider.d.ts +6 -0
- package/build/types/Snackbar/SnackbarProvider.d.ts.map +1 -1
- package/build/types/Snackbar/constants.d.ts +6 -0
- package/build/types/Snackbar/constants.d.ts.map +1 -0
- package/build/types/Snackbar/index.d.ts +3 -1
- package/build/types/Snackbar/index.d.ts.map +1 -1
- package/build/types/Snackbar/useSnackbar.d.ts +1 -1
- package/build/types/Snackbar/useSnackbar.d.ts.map +1 -1
- package/build/types/Snackbar/useSnackbarAutoDismiss.d.ts +18 -0
- package/build/types/Snackbar/useSnackbarAutoDismiss.d.ts.map +1 -0
- package/build/types/Snackbar/useSnackbarManager.d.ts +17 -0
- package/build/types/Snackbar/useSnackbarManager.d.ts.map +1 -0
- package/build/types/Snackbar/useSnackbarSwipeDismiss.d.ts +11 -0
- package/build/types/Snackbar/useSnackbarSwipeDismiss.d.ts.map +1 -0
- package/build/types/common/PropsValues/typography.d.ts +1 -13
- package/build/types/common/PropsValues/typography.d.ts.map +1 -1
- package/build/types/index.d.ts +2 -2
- 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/Button/Button.css +3 -0
- package/src/Button/Button.less +16 -0
- package/src/Button/_stories/Button.test.story.tsx +12 -12
- package/src/Calendar/DateHeader/DateHeader.tsx +3 -3
- package/src/Carousel/Carousel.tsx +3 -3
- package/src/DateInput/DateInput.css +0 -13
- package/src/DateInput/DateInput.less +2 -10
- package/src/DateInput/_stories/DateInput.test.story.tsx +13 -0
- package/src/Display/_stories/Display.story.tsx +6 -20
- package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.rtl.test.story.tsx +1 -1
- package/src/InputWithDisplayFormat/_stories/InputWithDisplayFormat.test.story.tsx +0 -52
- package/src/Inputs/Input/Input.css +24 -0
- package/src/Inputs/SelectInput/OptionContent/SelectInputOptionContent.tsx +1 -4
- package/src/Inputs/SelectInput/SelectInput.test.tsx +0 -74
- package/src/Inputs/TextArea/TextArea.css +24 -0
- package/src/ListItem/ListItem.css +1 -1
- package/src/ListItem/ListItem.grid.less +4 -4
- package/src/ListItem/ListItem.less +1 -1
- package/src/ListItem/Navigation/ListItemNavigation.tsx +2 -2
- package/src/PhoneNumberInput/PhoneNumberInput.css +0 -3
- package/src/PhoneNumberInput/PhoneNumberInput.less +2 -10
- package/src/PhoneNumberInput/PhoneNumberInput.tsx +0 -1
- package/src/PhoneNumberInput/_stories/PhoneNumberInput.rtl.test.story.tsx +1 -1
- package/src/Popover/Popover.css +6 -0
- package/src/PromoCard/Indicator/PromoCardIndicator.tsx +2 -2
- package/src/PromoCard/PromoCard.css +3 -0
- package/src/PromoCard/PromoCard.less +5 -0
- package/src/Prompt/CriticalBanner/_stories/CriticalBanner.test.story.tsx +1 -1
- package/src/Snackbar/Snackbar.css +182 -61
- package/src/Snackbar/Snackbar.less +185 -56
- package/src/Snackbar/Snackbar.messages.ts +9 -0
- package/src/Snackbar/Snackbar.test.tsx +697 -0
- package/src/Snackbar/Snackbar.tsx +227 -96
- package/src/Snackbar/Snackbar.types.ts +26 -0
- package/src/Snackbar/SnackbarContext.ts +2 -2
- package/src/Snackbar/SnackbarPortal.tsx +110 -0
- package/src/Snackbar/SnackbarProvider.test.tsx +202 -0
- package/src/Snackbar/SnackbarProvider.tsx +25 -25
- package/src/Snackbar/_stories/Snackbar.accessibility.docs.mdx +73 -0
- package/src/Snackbar/_stories/Snackbar.rtl.test.story.tsx +1 -12
- package/src/Snackbar/_stories/Snackbar.story.tsx +447 -62
- package/src/Snackbar/_stories/Snackbar.test.story.tsx +91 -16
- package/src/Snackbar/constants.ts +5 -0
- package/src/Snackbar/index.ts +3 -1
- package/src/Snackbar/useSnackbarAutoDismiss.ts +155 -0
- package/src/Snackbar/useSnackbarManager.ts +181 -0
- package/src/Snackbar/useSnackbarSwipeDismiss.ts +129 -0
- package/src/_deprecated/Select/Select.css +6 -0
- package/src/common/PropsValues/typography.ts +1 -20
- package/src/i18n/en.json +1 -1
- package/src/index.ts +7 -2
- package/src/main.css +864 -1509
- package/src/styles/less/accordion.css +12 -0
- package/src/styles/less/button-groups.css +6 -0
- package/src/styles/less/buttons.css +2 -0
- package/src/styles/less/core/_typography.less +194 -26
- package/src/styles/less/decision.css +12 -0
- package/src/styles/less/dropdowns.css +18 -2
- package/src/styles/less/droppable.css +6 -0
- package/src/styles/less/footer.css +12 -0
- package/src/styles/less/forms/bootstrap-forms.css +24 -2
- package/src/styles/less/input-groups.css +42 -2
- package/src/styles/less/list-group.css +6 -0
- package/src/styles/less/navbar.css +18 -2
- package/src/styles/less/neptune-core.css +512 -4
- package/src/styles/less/neptune.bundle.less +0 -2
- package/src/styles/less/neptune.css +640 -1426
- package/src/styles/less/popovers.css +18 -0
- package/src/styles/less/tooltip.css +6 -0
- package/src/DateInput/_stories/DateInput.rtl.test.story.tsx +0 -36
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
|
|
3
|
+
import { act, mockMatchMedia, render, screen, userEvent } from '../test-utils';
|
|
4
|
+
import { resetLiveRegionAnnouncementQueue } from '../common/LiveRegion/LiveRegion';
|
|
5
|
+
|
|
6
|
+
import { SNACKBAR_EXIT_DURATION } from './constants';
|
|
7
|
+
import type { SnackbarCreationProps } from './Snackbar.types';
|
|
8
|
+
import SnackbarProvider from './SnackbarProvider';
|
|
9
|
+
import useSnackbar from './useSnackbar';
|
|
10
|
+
import useSnackbarManager from './useSnackbarManager';
|
|
11
|
+
|
|
12
|
+
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTimeAsync });
|
|
13
|
+
|
|
14
|
+
mockMatchMedia();
|
|
15
|
+
|
|
16
|
+
const advanceTimers = (duration: number) => {
|
|
17
|
+
act(() => {
|
|
18
|
+
jest.advanceTimersByTime(duration);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type SnackbarTrigger = {
|
|
23
|
+
label: string;
|
|
24
|
+
snackbar: SnackbarCreationProps;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const SnackbarTriggers = ({ triggers }: { triggers: SnackbarTrigger[] }) => {
|
|
28
|
+
const showSnackbar = useSnackbar();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
{triggers.map(({ label, snackbar }) => (
|
|
33
|
+
<button key={label} type="button" onClick={() => showSnackbar(snackbar)}>
|
|
34
|
+
{label}
|
|
35
|
+
</button>
|
|
36
|
+
))}
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const BatchedSnackbarTrigger = () => {
|
|
42
|
+
const showSnackbar = useSnackbar();
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => {
|
|
48
|
+
showSnackbar({ text: 'First message', duration: 'indefinite' });
|
|
49
|
+
showSnackbar({ text: 'Latest message', duration: 'indefinite' });
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
Show latest
|
|
53
|
+
</button>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const renderProvider = (triggers: SnackbarTrigger[], timeout?: number) =>
|
|
58
|
+
render(
|
|
59
|
+
<SnackbarProvider timeout={timeout}>
|
|
60
|
+
<SnackbarTriggers triggers={triggers} />
|
|
61
|
+
</SnackbarProvider>,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
describe('SnackbarProvider orchestration', () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
jest.useFakeTimers();
|
|
67
|
+
resetLiveRegionAnnouncementQueue();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
jest.clearAllTimers();
|
|
72
|
+
jest.useRealTimers();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('creates a Snackbar programmatically', async () => {
|
|
76
|
+
renderProvider([
|
|
77
|
+
{
|
|
78
|
+
label: 'Show confirmation',
|
|
79
|
+
snackbar: { text: 'Changes saved', sentiment: 'success' },
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
await user.click(screen.getByRole('button', { name: 'Show confirmation' }));
|
|
84
|
+
|
|
85
|
+
expect(screen.getByRole('status')).toHaveTextContent('Changes saved');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('uses an explicit semantic duration instead of the legacy Provider timeout', async () => {
|
|
89
|
+
renderProvider(
|
|
90
|
+
[
|
|
91
|
+
{
|
|
92
|
+
label: 'Show confirmation',
|
|
93
|
+
snackbar: { text: 'Changes saved', duration: 'default' },
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
1,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
await user.click(screen.getByRole('button', { name: 'Show confirmation' }));
|
|
100
|
+
advanceTimers(3999);
|
|
101
|
+
expect(screen.getByText('Changes saved')).toBeInTheDocument();
|
|
102
|
+
|
|
103
|
+
advanceTimers(1);
|
|
104
|
+
advanceTimers(SNACKBAR_EXIT_DURATION);
|
|
105
|
+
expect(screen.queryByText('Changes saved')).not.toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('retains an explicitly configured legacy Provider timeout', async () => {
|
|
109
|
+
renderProvider([{ label: 'Show confirmation', snackbar: { text: 'Changes saved' } }], 1000);
|
|
110
|
+
|
|
111
|
+
await user.click(screen.getByRole('button', { name: 'Show confirmation' }));
|
|
112
|
+
advanceTimers(999);
|
|
113
|
+
expect(screen.getByText('Changes saved')).toBeInTheDocument();
|
|
114
|
+
|
|
115
|
+
advanceTimers(1);
|
|
116
|
+
advanceTimers(SNACKBAR_EXIT_DURATION);
|
|
117
|
+
expect(screen.queryByText('Changes saved')).not.toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('exits the active Snackbar before showing the latest replacement', async () => {
|
|
121
|
+
renderProvider([
|
|
122
|
+
{ label: 'Show first', snackbar: { text: 'First message', duration: 'indefinite' } },
|
|
123
|
+
{ label: 'Show second', snackbar: { text: 'Second message', duration: 'indefinite' } },
|
|
124
|
+
{ label: 'Show latest', snackbar: { text: 'Latest message', duration: 'indefinite' } },
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
await user.click(screen.getByRole('button', { name: 'Show first' }));
|
|
128
|
+
await user.click(screen.getByRole('button', { name: 'Show second' }));
|
|
129
|
+
|
|
130
|
+
expect(screen.getByText('First message')).toBeInTheDocument();
|
|
131
|
+
expect(screen.queryByText('Second message')).not.toBeInTheDocument();
|
|
132
|
+
|
|
133
|
+
await user.click(screen.getByRole('button', { name: 'Show latest' }));
|
|
134
|
+
advanceTimers(SNACKBAR_EXIT_DURATION);
|
|
135
|
+
|
|
136
|
+
expect(screen.queryByText('First message')).not.toBeInTheDocument();
|
|
137
|
+
expect(screen.queryByText('Second message')).not.toBeInTheDocument();
|
|
138
|
+
expect(screen.getByText('Latest message')).toBeInTheDocument();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('shows the latest Snackbar when multiple creations are batched before the first render', async () => {
|
|
142
|
+
render(
|
|
143
|
+
<SnackbarProvider>
|
|
144
|
+
<BatchedSnackbarTrigger />
|
|
145
|
+
</SnackbarProvider>,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
await user.click(screen.getByRole('button', { name: 'Show latest' }));
|
|
149
|
+
|
|
150
|
+
expect(screen.queryByText('First message')).not.toBeInTheDocument();
|
|
151
|
+
expect(screen.getByText('Latest message')).toBeInTheDocument();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('calls onDismiss after the active Snackbar finishes exiting', async () => {
|
|
155
|
+
const onDismiss = jest.fn();
|
|
156
|
+
renderProvider([
|
|
157
|
+
{
|
|
158
|
+
label: 'Show first',
|
|
159
|
+
snackbar: { text: 'First message', duration: 'indefinite', onDismiss },
|
|
160
|
+
},
|
|
161
|
+
{ label: 'Replace', snackbar: { text: 'Replacement', duration: 'indefinite' } },
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
await user.click(screen.getByRole('button', { name: 'Show first' }));
|
|
165
|
+
await user.click(screen.getByRole('button', { name: 'Replace' }));
|
|
166
|
+
advanceTimers(SNACKBAR_EXIT_DURATION - 1);
|
|
167
|
+
expect(onDismiss).not.toHaveBeenCalled();
|
|
168
|
+
|
|
169
|
+
advanceTimers(1);
|
|
170
|
+
expect(onDismiss).toHaveBeenCalledTimes(1);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('promotes a pending Snackbar even when onDismiss throws', async () => {
|
|
174
|
+
const onDismiss = jest.fn(() => {
|
|
175
|
+
throw new Error('Dismiss callback failed');
|
|
176
|
+
});
|
|
177
|
+
const { result } = renderHook(() => useSnackbarManager());
|
|
178
|
+
|
|
179
|
+
await act(async () => result.current.showSnackbar({ text: 'First message', onDismiss }));
|
|
180
|
+
await act(async () => result.current.showSnackbar({ text: 'Replacement message' }));
|
|
181
|
+
|
|
182
|
+
await expect(act(async () => result.current.handleExited())).rejects.toThrow(
|
|
183
|
+
'Dismiss callback failed',
|
|
184
|
+
);
|
|
185
|
+
await act(async () => undefined);
|
|
186
|
+
expect(result.current.active?.props.text).toBe('Replacement message');
|
|
187
|
+
expect(result.current.visible).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('cleans up the active Snackbar timer when the Provider unmounts', async () => {
|
|
191
|
+
const view = renderProvider([
|
|
192
|
+
{ label: 'Show confirmation', snackbar: { text: 'Changes saved' } },
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
await user.click(screen.getByRole('button', { name: 'Show confirmation' }));
|
|
196
|
+
advanceTimers(300);
|
|
197
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
198
|
+
|
|
199
|
+
view.unmount();
|
|
200
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { useMemo
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
2
|
|
|
3
|
-
import SnackbarPortal, { SnackbarProps } from './Snackbar';
|
|
4
3
|
import { SnackbarContext } from './SnackbarContext';
|
|
4
|
+
import { PortalledSnackbar } from './SnackbarPortal';
|
|
5
|
+
import useSnackbarManager from './useSnackbarManager';
|
|
5
6
|
|
|
6
7
|
export interface SnackbarProviderProps {
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Configure each Snackbar with semantic `duration` instead. Existing millisecond
|
|
10
|
+
* values remain supported temporarily while consumers migrate. A Snackbar's `duration` takes
|
|
11
|
+
* precedence when both are provided.
|
|
12
|
+
*/
|
|
7
13
|
timeout?: number;
|
|
14
|
+
/** Application content that can create Snackbars through the Provider context. */
|
|
8
15
|
children?: React.ReactNode;
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
export default function SnackbarProvider({ timeout
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
16
|
-
const { action, text, theme, timestamp } = state;
|
|
18
|
+
export default function SnackbarProvider({ timeout, children }: SnackbarProviderProps) {
|
|
19
|
+
const { active, announcementKey, dismissSnackbar, handleExited, showSnackbar, visible } =
|
|
20
|
+
useSnackbarManager();
|
|
21
|
+
const contextValue = useMemo(() => ({ createSnackbar: showSnackbar }), [showSnackbar]);
|
|
17
22
|
|
|
18
23
|
return (
|
|
19
|
-
<SnackbarContext.Provider
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
text={text}
|
|
32
|
-
timestamp={timestamp}
|
|
33
|
-
timeout={timeout}
|
|
34
|
-
theme={theme}
|
|
35
|
-
/>
|
|
24
|
+
<SnackbarContext.Provider value={contextValue}>
|
|
25
|
+
{active ? (
|
|
26
|
+
<PortalledSnackbar
|
|
27
|
+
key={active.id}
|
|
28
|
+
{...active.props}
|
|
29
|
+
announcementKey={announcementKey}
|
|
30
|
+
timeout={timeout}
|
|
31
|
+
visible={visible}
|
|
32
|
+
onDismissRequest={dismissSnackbar}
|
|
33
|
+
onExited={handleExited}
|
|
34
|
+
/>
|
|
35
|
+
) : null}
|
|
36
36
|
{children}
|
|
37
37
|
</SnackbarContext.Provider>
|
|
38
38
|
);
|
|
@@ -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,4 +1,3 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
1
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
2
|
|
|
4
3
|
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
@@ -12,21 +11,11 @@ export default {
|
|
|
12
11
|
|
|
13
12
|
type Story = StoryObj<typeof Snackbar>;
|
|
14
13
|
|
|
15
|
-
const TIMEOUT = 10_000_000;
|
|
16
|
-
|
|
17
14
|
function SnackbarStory({ action }: { action?: { label: string } }) {
|
|
18
|
-
const [timestamp, setTimestamp] = useState(0);
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
22
|
-
setTimestamp(1);
|
|
23
|
-
}, []);
|
|
24
|
-
|
|
25
15
|
return (
|
|
26
16
|
<Snackbar
|
|
27
17
|
text={<span>Transfer completed successfully</span>}
|
|
28
|
-
|
|
29
|
-
timeout={TIMEOUT}
|
|
18
|
+
duration="indefinite"
|
|
30
19
|
action={action}
|
|
31
20
|
/>
|
|
32
21
|
);
|