@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
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { userEvent, within, expect } from 'storybook/test';
|
|
1
|
+
import { userEvent, within, expect, waitFor } from 'storybook/test';
|
|
2
2
|
import { action } from 'storybook/actions';
|
|
3
|
+
import { Card } from '@transferwise/icons';
|
|
3
4
|
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
4
5
|
import { lorem20 } from '../../test-utils';
|
|
5
6
|
import { wait } from '../../test-utils/wait';
|
|
6
7
|
import Button from '../../Button';
|
|
7
|
-
import { Snackbar
|
|
8
|
+
import { Snackbar } from '../Snackbar';
|
|
8
9
|
import { SnackbarConsumer } from '../SnackbarContext';
|
|
9
10
|
import SnackbarProvider from '../SnackbarProvider';
|
|
11
|
+
import type { SnackbarProps } from '../Snackbar.types';
|
|
10
12
|
|
|
11
13
|
export default {
|
|
12
14
|
title: 'Prompts/Snackbar/Tests',
|
|
13
15
|
component: Snackbar,
|
|
14
|
-
argTypes: {},
|
|
15
16
|
tags: ['!autodocs', '!manifest'],
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -23,21 +24,18 @@ const launchSnackbar = async (canvasElement: HTMLElement) => {
|
|
|
23
24
|
|
|
24
25
|
type Args = Partial<SnackbarProps> & {
|
|
25
26
|
extraContext: React.ReactNode;
|
|
26
|
-
timeout: number;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const TIMEOUT = 10000000;
|
|
30
|
-
|
|
31
29
|
const Default = {
|
|
32
30
|
args: {
|
|
33
|
-
|
|
31
|
+
duration: 'indefinite',
|
|
34
32
|
} as Args,
|
|
35
33
|
play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
|
|
36
34
|
await launchSnackbar(canvasElement);
|
|
37
35
|
},
|
|
38
36
|
render: (args: Args) => {
|
|
39
37
|
return (
|
|
40
|
-
<SnackbarProvider
|
|
38
|
+
<SnackbarProvider>
|
|
41
39
|
<SnackbarConsumer>
|
|
42
40
|
{({ createSnackbar }) => (
|
|
43
41
|
<>
|
|
@@ -46,11 +44,18 @@ const Default = {
|
|
|
46
44
|
block
|
|
47
45
|
onClick={() => {
|
|
48
46
|
createSnackbar({
|
|
49
|
-
action:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
action:
|
|
48
|
+
args.action ??
|
|
49
|
+
({
|
|
50
|
+
label: 'View',
|
|
51
|
+
onClick: () => action('View button clicked.'),
|
|
52
|
+
} as const),
|
|
53
|
+
duration: args.duration,
|
|
54
|
+
loading: args.loading,
|
|
55
|
+
media: args.media,
|
|
56
|
+
onDismiss: args.onDismiss,
|
|
57
|
+
sentiment: args.sentiment,
|
|
58
|
+
text: args.text ?? <span>Some text</span>,
|
|
54
59
|
});
|
|
55
60
|
}}
|
|
56
61
|
>
|
|
@@ -72,10 +77,60 @@ export const Basic = {
|
|
|
72
77
|
},
|
|
73
78
|
};
|
|
74
79
|
|
|
80
|
+
export const CustomMedia = {
|
|
81
|
+
...Default,
|
|
82
|
+
args: {
|
|
83
|
+
...Default.args,
|
|
84
|
+
media: <Card title="Card" />,
|
|
85
|
+
sentiment: 'success',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const Loading = {
|
|
90
|
+
...Default,
|
|
91
|
+
args: {
|
|
92
|
+
...Default.args,
|
|
93
|
+
loading: true,
|
|
94
|
+
sentiment: 'negative',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const Proposition = {
|
|
99
|
+
...Default,
|
|
100
|
+
args: {
|
|
101
|
+
...Default.args,
|
|
102
|
+
sentiment: 'proposition',
|
|
103
|
+
loading: false,
|
|
104
|
+
media: null,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const NavigationAction = {
|
|
109
|
+
...Default,
|
|
110
|
+
args: {
|
|
111
|
+
...Default.args,
|
|
112
|
+
action: {
|
|
113
|
+
label: 'View transfer',
|
|
114
|
+
href: '/transfers/123',
|
|
115
|
+
target: '_blank',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
|
|
119
|
+
await launchSnackbar(canvasElement);
|
|
120
|
+
|
|
121
|
+
const link = await waitFor(() =>
|
|
122
|
+
within(document.body).getByRole('link', { name: 'View transfer' }),
|
|
123
|
+
);
|
|
124
|
+
await expect(link).toHaveAttribute('href', '/transfers/123');
|
|
125
|
+
await expect(link).toHaveAttribute('target', '_blank');
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
75
129
|
export const Mobile = {
|
|
76
130
|
...Default,
|
|
77
131
|
args: {
|
|
78
132
|
...Default.args,
|
|
133
|
+
media: null,
|
|
79
134
|
},
|
|
80
135
|
...withVariantConfig(['mobile'], Default),
|
|
81
136
|
};
|
|
@@ -88,9 +143,9 @@ const switchToDarkMode = async (context: { canvasElement: HTMLElement }) => {
|
|
|
88
143
|
if (body) {
|
|
89
144
|
body.setAttribute('class', 'np-theme-personal--dark');
|
|
90
145
|
}
|
|
91
|
-
const snackbar = document.querySelector('div.
|
|
146
|
+
const snackbar = document.querySelector('div.wds-Snackbar');
|
|
92
147
|
if (snackbar) {
|
|
93
|
-
snackbar.setAttribute('class', '
|
|
148
|
+
snackbar.setAttribute('class', 'wds-Snackbar np-theme-personal--dark');
|
|
94
149
|
}
|
|
95
150
|
};
|
|
96
151
|
|
|
@@ -98,6 +153,7 @@ export const DarkMode = {
|
|
|
98
153
|
...Default,
|
|
99
154
|
args: {
|
|
100
155
|
...Default.args,
|
|
156
|
+
sentiment: 'warning',
|
|
101
157
|
},
|
|
102
158
|
play: switchToDarkMode,
|
|
103
159
|
...withVariantConfig(['dark'], Default),
|
|
@@ -107,6 +163,14 @@ export const Zoom400 = {
|
|
|
107
163
|
...Default,
|
|
108
164
|
args: {
|
|
109
165
|
...Default.args,
|
|
166
|
+
action: {
|
|
167
|
+
label: 'View details',
|
|
168
|
+
onClick: () => action('View details button clicked.'),
|
|
169
|
+
},
|
|
170
|
+
sentiment: 'success',
|
|
171
|
+
text: (
|
|
172
|
+
<span>Your transfer has been completed successfully and is ready for you to review.</span>
|
|
173
|
+
),
|
|
110
174
|
extraContext: (
|
|
111
175
|
<div className="p-t-3">
|
|
112
176
|
Snackbar isn't transparent (I'm going to make this text very long so that it will
|
|
@@ -120,6 +184,17 @@ export const Zoom400 = {
|
|
|
120
184
|
...withVariantConfig(['400%'], Default),
|
|
121
185
|
};
|
|
122
186
|
|
|
187
|
+
export const Zoom400ShortMessage = {
|
|
188
|
+
...Default,
|
|
189
|
+
args: {
|
|
190
|
+
...Default.args,
|
|
191
|
+
media: <Card title="Card" />,
|
|
192
|
+
sentiment: 'success',
|
|
193
|
+
text: <span>Some text</span>,
|
|
194
|
+
},
|
|
195
|
+
...withVariantConfig(['400%'], Default),
|
|
196
|
+
};
|
|
197
|
+
|
|
123
198
|
/**
|
|
124
199
|
* Snackbar is only shown once even if trigger is clicked many times
|
|
125
200
|
*/
|
|
@@ -134,6 +209,6 @@ export const MultipleClicks = {
|
|
|
134
209
|
await userEvent.click(canvas.getByRole('button'));
|
|
135
210
|
}
|
|
136
211
|
|
|
137
|
-
await expect(within(document.body).getAllByRole('
|
|
212
|
+
await expect(within(document.body).getAllByRole('status')).toHaveLength(1);
|
|
138
213
|
},
|
|
139
214
|
};
|
package/src/Snackbar/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { Snackbar } from './Snackbar';
|
|
2
|
+
export { default, SnackbarPortal } from './SnackbarPortal';
|
|
2
3
|
|
|
3
4
|
export type { SnackbarProps } from './Snackbar';
|
|
5
|
+
export type { SnackbarAction, SnackbarDuration } from './Snackbar.types';
|
|
4
6
|
export type { SnackbarProviderProps } from './SnackbarProvider';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SNACKBAR_DEFAULT_DURATION, SNACKBAR_EXTENDED_DURATION } from './constants';
|
|
4
|
+
import type { SnackbarDuration } from './Snackbar.types';
|
|
5
|
+
|
|
6
|
+
type UseSnackbarAutoDismissOptions = {
|
|
7
|
+
duration?: SnackbarDuration;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
onDismissRequest: () => void;
|
|
10
|
+
resetKey: string | number;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
visible: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type InteractionState = {
|
|
16
|
+
focusWithin: boolean;
|
|
17
|
+
hovered: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getAutoDismissDuration = ({
|
|
21
|
+
duration,
|
|
22
|
+
loading,
|
|
23
|
+
timeout,
|
|
24
|
+
}: Pick<UseSnackbarAutoDismissOptions, 'duration' | 'loading' | 'timeout'>) => {
|
|
25
|
+
if (loading || duration === 'indefinite') {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (duration === 'extended') {
|
|
30
|
+
return SNACKBAR_EXTENDED_DURATION;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (duration === 'default') {
|
|
34
|
+
return SNACKBAR_DEFAULT_DURATION;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return timeout ?? SNACKBAR_DEFAULT_DURATION;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const useSnackbarAutoDismiss = ({
|
|
41
|
+
duration,
|
|
42
|
+
loading,
|
|
43
|
+
onDismissRequest,
|
|
44
|
+
resetKey,
|
|
45
|
+
timeout,
|
|
46
|
+
visible,
|
|
47
|
+
}: UseSnackbarAutoDismissOptions) => {
|
|
48
|
+
const surfaceRef = useRef<HTMLDivElement>(null);
|
|
49
|
+
const timeoutRef = useRef(0);
|
|
50
|
+
const remainingDurationRef = useRef<number | null>(null);
|
|
51
|
+
const timerStartedAtRef = useRef(0);
|
|
52
|
+
const interactionStateRef = useRef<InteractionState>({ focusWithin: false, hovered: false });
|
|
53
|
+
|
|
54
|
+
const isInterrupted = useCallback(() => {
|
|
55
|
+
const { focusWithin, hovered } = interactionStateRef.current;
|
|
56
|
+
return focusWithin || hovered;
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const clearTimer = useCallback(() => {
|
|
60
|
+
window.clearTimeout(timeoutRef.current);
|
|
61
|
+
timeoutRef.current = 0;
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
const startTimer = useCallback(
|
|
65
|
+
(remainingDuration = getAutoDismissDuration({ duration, loading, timeout })) => {
|
|
66
|
+
clearTimer();
|
|
67
|
+
remainingDurationRef.current = remainingDuration;
|
|
68
|
+
|
|
69
|
+
if (!visible || remainingDuration === null || isInterrupted()) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
timerStartedAtRef.current = Date.now();
|
|
74
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
75
|
+
timeoutRef.current = 0;
|
|
76
|
+
remainingDurationRef.current = 0;
|
|
77
|
+
onDismissRequest();
|
|
78
|
+
}, remainingDuration);
|
|
79
|
+
},
|
|
80
|
+
[clearTimer, duration, isInterrupted, loading, onDismissRequest, timeout, visible],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const pauseTimer = useCallback(() => {
|
|
84
|
+
if (!timeoutRef.current || remainingDurationRef.current === null) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
clearTimer();
|
|
89
|
+
remainingDurationRef.current = Math.max(
|
|
90
|
+
0,
|
|
91
|
+
remainingDurationRef.current - (Date.now() - timerStartedAtRef.current),
|
|
92
|
+
);
|
|
93
|
+
}, [clearTimer]);
|
|
94
|
+
|
|
95
|
+
const resumeTimer = useCallback(() => {
|
|
96
|
+
if (isInterrupted() || remainingDurationRef.current === null) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
startTimer(remainingDurationRef.current);
|
|
101
|
+
}, [isInterrupted, startTimer]);
|
|
102
|
+
|
|
103
|
+
const handleMouseEnter = useCallback(() => {
|
|
104
|
+
interactionStateRef.current = { ...interactionStateRef.current, hovered: true };
|
|
105
|
+
pauseTimer();
|
|
106
|
+
}, [pauseTimer]);
|
|
107
|
+
|
|
108
|
+
const handleMouseLeave = useCallback(() => {
|
|
109
|
+
interactionStateRef.current = { ...interactionStateRef.current, hovered: false };
|
|
110
|
+
resumeTimer();
|
|
111
|
+
}, [resumeTimer]);
|
|
112
|
+
|
|
113
|
+
const handleFocus = useCallback(() => {
|
|
114
|
+
interactionStateRef.current = { ...interactionStateRef.current, focusWithin: true };
|
|
115
|
+
pauseTimer();
|
|
116
|
+
}, [pauseTimer]);
|
|
117
|
+
|
|
118
|
+
const handleBlur = useCallback(
|
|
119
|
+
(event: React.FocusEvent<HTMLDivElement>) => {
|
|
120
|
+
if (event.currentTarget.contains(event.relatedTarget)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interactionStateRef.current = { ...interactionStateRef.current, focusWithin: false };
|
|
125
|
+
resumeTimer();
|
|
126
|
+
},
|
|
127
|
+
[resumeTimer],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!visible) {
|
|
132
|
+
clearTimer();
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const snackbar = surfaceRef.current;
|
|
137
|
+
interactionStateRef.current = {
|
|
138
|
+
focusWithin: Boolean(snackbar?.contains(document.activeElement)),
|
|
139
|
+
hovered: snackbar?.matches(':hover') ?? false,
|
|
140
|
+
};
|
|
141
|
+
startTimer();
|
|
142
|
+
|
|
143
|
+
return clearTimer;
|
|
144
|
+
}, [clearTimer, resetKey, startTimer, visible]);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
handleBlur,
|
|
148
|
+
handleFocus,
|
|
149
|
+
handleMouseEnter,
|
|
150
|
+
handleMouseLeave,
|
|
151
|
+
surfaceRef,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default useSnackbarAutoDismiss;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { useCallback, useEffect, useReducer, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { SnackbarProps } from './Snackbar.types';
|
|
4
|
+
|
|
5
|
+
type SnackbarInstance = {
|
|
6
|
+
id: number;
|
|
7
|
+
props: SnackbarProps;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type SnackbarManagerState = {
|
|
11
|
+
active: SnackbarInstance | null;
|
|
12
|
+
activeHasRendered: boolean;
|
|
13
|
+
announcementKey: number;
|
|
14
|
+
pending: SnackbarInstance | null;
|
|
15
|
+
visible: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type SnackbarManagerAction =
|
|
19
|
+
| { type: 'show'; instance: SnackbarInstance }
|
|
20
|
+
| { type: 'update'; instance: SnackbarInstance; reactivate: boolean }
|
|
21
|
+
| { type: 'rendered'; instanceId: number }
|
|
22
|
+
| { type: 'dismiss' }
|
|
23
|
+
| { type: 'exited'; instanceId: number };
|
|
24
|
+
|
|
25
|
+
const emptyState: SnackbarManagerState = {
|
|
26
|
+
active: null,
|
|
27
|
+
activeHasRendered: false,
|
|
28
|
+
announcementKey: 0,
|
|
29
|
+
pending: null,
|
|
30
|
+
visible: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const createInitialState = (initialSnackbar?: SnackbarProps): SnackbarManagerState =>
|
|
34
|
+
initialSnackbar
|
|
35
|
+
? {
|
|
36
|
+
active: { id: 1, props: initialSnackbar },
|
|
37
|
+
activeHasRendered: false,
|
|
38
|
+
announcementKey: 1,
|
|
39
|
+
pending: null,
|
|
40
|
+
visible: true,
|
|
41
|
+
}
|
|
42
|
+
: emptyState;
|
|
43
|
+
|
|
44
|
+
const snackbarManagerReducer = (
|
|
45
|
+
state: SnackbarManagerState,
|
|
46
|
+
action: SnackbarManagerAction,
|
|
47
|
+
): SnackbarManagerState => {
|
|
48
|
+
switch (action.type) {
|
|
49
|
+
case 'show':
|
|
50
|
+
if (!state.active || !state.activeHasRendered) {
|
|
51
|
+
// Nothing has rendered yet, so consecutive creates from the same React batch collapse to
|
|
52
|
+
// the latest Snackbar instead of trying to animate out an instance that never entered.
|
|
53
|
+
return {
|
|
54
|
+
active: action.instance,
|
|
55
|
+
activeHasRendered: false,
|
|
56
|
+
announcementKey: state.announcementKey + 1,
|
|
57
|
+
pending: null,
|
|
58
|
+
visible: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Latest wins: replace any pending Snackbar while the active one completes its exit.
|
|
63
|
+
return {
|
|
64
|
+
...state,
|
|
65
|
+
pending: action.instance,
|
|
66
|
+
visible: false,
|
|
67
|
+
};
|
|
68
|
+
case 'rendered':
|
|
69
|
+
if (state.active?.id !== action.instanceId || state.activeHasRendered) {
|
|
70
|
+
return state;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { ...state, activeHasRendered: true };
|
|
74
|
+
case 'update':
|
|
75
|
+
if (!state.active) {
|
|
76
|
+
return {
|
|
77
|
+
active: action.instance,
|
|
78
|
+
activeHasRendered: false,
|
|
79
|
+
announcementKey: state.announcementKey + 1,
|
|
80
|
+
pending: null,
|
|
81
|
+
visible: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
active: { ...state.active, props: action.instance.props },
|
|
88
|
+
pending: action.reactivate ? null : state.pending,
|
|
89
|
+
visible: action.reactivate ? true : state.visible,
|
|
90
|
+
announcementKey:
|
|
91
|
+
state.active.props.text === action.instance.props.text
|
|
92
|
+
? state.announcementKey
|
|
93
|
+
: state.announcementKey + 1,
|
|
94
|
+
};
|
|
95
|
+
case 'dismiss':
|
|
96
|
+
if (!state.active) {
|
|
97
|
+
return state;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return state.activeHasRendered
|
|
101
|
+
? { ...state, visible: false }
|
|
102
|
+
: { ...emptyState, announcementKey: state.announcementKey };
|
|
103
|
+
case 'exited':
|
|
104
|
+
if (state.active?.id !== action.instanceId) {
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (state.pending) {
|
|
109
|
+
return {
|
|
110
|
+
active: state.pending,
|
|
111
|
+
activeHasRendered: false,
|
|
112
|
+
announcementKey: state.announcementKey + 1,
|
|
113
|
+
pending: null,
|
|
114
|
+
visible: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { ...emptyState, announcementKey: state.announcementKey };
|
|
119
|
+
default:
|
|
120
|
+
return state;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Owns the shared latest-wins and exit-before-enter lifecycle used by Snackbar portals. */
|
|
125
|
+
const useSnackbarManager = (initialSnackbar?: SnackbarProps) => {
|
|
126
|
+
const [state, dispatch] = useReducer(snackbarManagerReducer, initialSnackbar, createInitialState);
|
|
127
|
+
const nextInstanceId = useRef(initialSnackbar ? 1 : 0);
|
|
128
|
+
|
|
129
|
+
const createInstance = useCallback((props: SnackbarProps): SnackbarInstance => {
|
|
130
|
+
nextInstanceId.current += 1;
|
|
131
|
+
return { id: nextInstanceId.current, props };
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const showSnackbar = useCallback(
|
|
135
|
+
(props: SnackbarProps) => dispatch({ type: 'show', instance: createInstance(props) }),
|
|
136
|
+
[createInstance],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const updateSnackbar = useCallback(
|
|
140
|
+
(props: SnackbarProps, reactivate = false) =>
|
|
141
|
+
dispatch({ type: 'update', instance: createInstance(props), reactivate }),
|
|
142
|
+
[createInstance],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const dismissSnackbar = useCallback(() => dispatch({ type: 'dismiss' }), []);
|
|
146
|
+
|
|
147
|
+
const handleExited = useCallback(() => {
|
|
148
|
+
if (!state.active) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
state.active.props.onDismiss?.();
|
|
154
|
+
} finally {
|
|
155
|
+
// Consumer callbacks must not prevent a pending Snackbar from being promoted.
|
|
156
|
+
dispatch({ type: 'exited', instanceId: state.active.id });
|
|
157
|
+
}
|
|
158
|
+
}, [state.active]);
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (!state.active || !state.visible || state.activeHasRendered) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// This runs after React commits the active Snackbar. Later creates can now safely wait for its
|
|
166
|
+
// exit transition; creates dispatched before this point continue to coalesce to the latest.
|
|
167
|
+
dispatch({ type: 'rendered', instanceId: state.active.id });
|
|
168
|
+
}, [state.active, state.activeHasRendered, state.visible]);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
active: state.active,
|
|
172
|
+
announcementKey: state.announcementKey,
|
|
173
|
+
dismissSnackbar,
|
|
174
|
+
handleExited,
|
|
175
|
+
showSnackbar,
|
|
176
|
+
updateSnackbar,
|
|
177
|
+
visible: state.visible,
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export default useSnackbarManager;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SWIPE_DISMISS_THRESHOLD_RATIO } from './constants';
|
|
4
|
+
|
|
5
|
+
const SWIPE_DISMISS_CLASS_NAME = 'wds-Snackbar--swipe-dismiss';
|
|
6
|
+
|
|
7
|
+
const useSnackbarSwipeDismiss = (onDismissRequest: () => void) => {
|
|
8
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
9
|
+
const initialXCoordinate = useRef(0);
|
|
10
|
+
const initialYCoordinate = useRef(0);
|
|
11
|
+
const moveOffsetX = useRef(0);
|
|
12
|
+
const moveOffsetY = useRef(0);
|
|
13
|
+
const dismissThreshold = useRef(0);
|
|
14
|
+
const activePointerId = useRef<number | null>(null);
|
|
15
|
+
const captureTarget = useRef<Element | null>(null);
|
|
16
|
+
const suppressClick = useRef(false);
|
|
17
|
+
|
|
18
|
+
const clearSwipePosition = useCallback(() => {
|
|
19
|
+
activePointerId.current = null;
|
|
20
|
+
captureTarget.current = null;
|
|
21
|
+
suppressClick.current = false;
|
|
22
|
+
moveOffsetX.current = 0;
|
|
23
|
+
moveOffsetY.current = 0;
|
|
24
|
+
containerRef.current?.classList.remove(SWIPE_DISMISS_CLASS_NAME);
|
|
25
|
+
containerRef.current?.style.removeProperty('transform');
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const updateMoveOffset = ({ clientX, clientY }: React.PointerEvent<HTMLDivElement>) => {
|
|
29
|
+
moveOffsetX.current = clientX - initialXCoordinate.current;
|
|
30
|
+
moveOffsetY.current = clientY - initialYCoordinate.current;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const releasePointerCapture = (pointerId: number) => {
|
|
34
|
+
const target = captureTarget.current;
|
|
35
|
+
if (target?.hasPointerCapture?.(pointerId)) {
|
|
36
|
+
target.releasePointerCapture(pointerId);
|
|
37
|
+
}
|
|
38
|
+
captureTarget.current = null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
42
|
+
if (event.button !== 0 || !event.isPrimary || activePointerId.current !== null) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const target = event.target as Element;
|
|
47
|
+
target.setPointerCapture?.(event.pointerId);
|
|
48
|
+
captureTarget.current = target;
|
|
49
|
+
activePointerId.current = event.pointerId;
|
|
50
|
+
initialXCoordinate.current = event.clientX;
|
|
51
|
+
initialYCoordinate.current = event.clientY;
|
|
52
|
+
moveOffsetX.current = 0;
|
|
53
|
+
moveOffsetY.current = 0;
|
|
54
|
+
dismissThreshold.current = event.currentTarget.offsetHeight * SWIPE_DISMISS_THRESHOLD_RATIO;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
58
|
+
if (activePointerId.current !== event.pointerId) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
updateMoveOffset(event);
|
|
63
|
+
|
|
64
|
+
if (moveOffsetY.current <= 0 || moveOffsetY.current <= Math.abs(moveOffsetX.current)) {
|
|
65
|
+
containerRef.current?.style.removeProperty('transform');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
containerRef.current?.style.setProperty('transform', `translateY(${moveOffsetY.current}px)`);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
73
|
+
if (activePointerId.current !== event.pointerId) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
updateMoveOffset(event);
|
|
78
|
+
releasePointerCapture(event.pointerId);
|
|
79
|
+
activePointerId.current = null;
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
moveOffsetY.current >= dismissThreshold.current &&
|
|
83
|
+
moveOffsetY.current > Math.abs(moveOffsetX.current)
|
|
84
|
+
) {
|
|
85
|
+
const container = containerRef.current;
|
|
86
|
+
if (container) {
|
|
87
|
+
const dismissOffset = Math.max(moveOffsetY.current, container.offsetHeight);
|
|
88
|
+
container.classList.add(SWIPE_DISMISS_CLASS_NAME);
|
|
89
|
+
container.style.setProperty('transform', `translateY(${dismissOffset}px)`);
|
|
90
|
+
}
|
|
91
|
+
suppressClick.current = true;
|
|
92
|
+
onDismissRequest();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
clearSwipePosition();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handlePointerCancel = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
100
|
+
if (activePointerId.current !== event.pointerId) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
releasePointerCapture(event.pointerId);
|
|
105
|
+
clearSwipePosition();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleClickCapture = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
109
|
+
if (!suppressClick.current) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
suppressClick.current = false;
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
event.stopPropagation();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
clearSwipePosition,
|
|
120
|
+
containerRef,
|
|
121
|
+
handleClickCapture,
|
|
122
|
+
handlePointerCancel,
|
|
123
|
+
handlePointerDown,
|
|
124
|
+
handlePointerMove,
|
|
125
|
+
handlePointerUp,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default useSnackbarSwipeDismiss;
|
|
@@ -146,6 +146,12 @@
|
|
|
146
146
|
border-radius: var(--radius-small);
|
|
147
147
|
color: #37517e;
|
|
148
148
|
color: var(--color-content-primary);
|
|
149
|
+
font-size: 0.875rem;
|
|
150
|
+
font-size: var(--font-size-14);
|
|
151
|
+
line-height: 155%;
|
|
152
|
+
letter-spacing: -0.006em;
|
|
153
|
+
font-weight: 400;
|
|
154
|
+
font-weight: var(--font-weight-regular);
|
|
149
155
|
word-wrap: break-word;
|
|
150
156
|
}
|
|
151
157
|
.np-theme-personal .np-dropdown-menu > li > a {
|