uikit-react-public 0.30.1 → 0.32.4
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/dist/components/Heading/Heading.stories.d.ts +5 -7
- package/dist/components/MenuNew/Menu.context.d.ts +9 -0
- package/dist/components/MenuNew/Menu.d.ts +2 -1
- package/dist/components/MenuNew/MenuHeading.d.ts +1 -1
- package/dist/components/MenuNew/MenuItemText.d.ts +2 -1
- package/dist/components/MenuNew/PrimaryMenuItem.d.ts +1 -1
- package/dist/components/Select/Select.types.d.ts +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts +12 -0
- package/dist/components/Sidebar/Sidebar.stories.d.ts +38 -0
- package/dist/components/Sidebar/__tests__/Sidebar.test.d.ts +1 -0
- package/dist/components/Sidebar/index.d.ts +2 -0
- package/dist/components/Snackbar/Snackbar.d.ts +9 -5
- package/dist/components/Snackbar/Snackbar.stories.d.ts +3 -1
- package/dist/components/Snackbar/Snackbars.d.ts +19 -0
- package/dist/components/Snackbar/Snackbars.stories.d.ts +52 -0
- package/dist/components/Snackbar/__tests__/Snackbars.test.d.ts +1 -0
- package/dist/components/Snackbar/index.d.ts +2 -0
- package/dist/components/index.d.ts +4 -1
- package/dist/index.js +5979 -5448
- package/lib/components/Dropdown/Dropdown.stories.tsx +69 -74
- package/lib/components/Heading/Documentation.mdx +4 -14
- package/lib/components/Heading/Heading.stories.tsx +16 -30
- package/lib/components/MenuNew/Menu.context.tsx +18 -0
- package/lib/components/MenuNew/Menu.tsx +14 -9
- package/lib/components/MenuNew/MenuDivider.tsx +12 -1
- package/lib/components/MenuNew/MenuHeading.tsx +6 -0
- package/lib/components/MenuNew/MenuItemText.tsx +27 -3
- package/lib/components/MenuNew/MenuSection.tsx +11 -0
- package/lib/components/MenuNew/PrimaryMenuItem.tsx +71 -2
- package/lib/components/Select/Select.types.ts +1 -7
- package/lib/components/Select/__tests__/Select.test.tsx +112 -0
- package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +5 -0
- package/lib/components/Select/subcomponents/CustomSelect.tsx +75 -3
- package/lib/components/Select/subcomponents/FilterInput.tsx +1 -1
- package/lib/components/Sidebar/Sidebar.stories.tsx +94 -0
- package/lib/components/Sidebar/Sidebar.tsx +208 -0
- package/lib/components/Sidebar/__tests__/Sidebar.test.tsx +96 -0
- package/lib/components/Sidebar/__tests__/__snapshots__/Sidebar.test.tsx.snap +272 -0
- package/lib/components/Sidebar/index.ts +2 -0
- package/lib/components/Snackbar/Snackbar.stories.tsx +22 -0
- package/lib/components/Snackbar/Snackbar.tsx +168 -119
- package/lib/components/Snackbar/Snackbars.stories.tsx +141 -0
- package/lib/components/Snackbar/Snackbars.tsx +377 -0
- package/lib/components/Snackbar/__tests__/Snackbar.test.tsx +222 -10
- package/lib/components/Snackbar/__tests__/Snackbars.test.tsx +377 -0
- package/lib/components/Snackbar/__tests__/__snapshots__/Snackbar.test.tsx.snap +46 -25
- package/lib/components/Snackbar/index.ts +6 -0
- package/lib/components/index.ts +10 -1
- package/package.json +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import { css } from '@emotion/css';
|
|
4
|
+
import Button from '../Button/Button';
|
|
5
|
+
import Snackbars, { type SnackbarsItem } from './Snackbars';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Components/Snackbars',
|
|
9
|
+
component: Snackbars,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'fullscreen',
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component: 'Manages a stack of Snackbar notifications.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
argTypes: {
|
|
19
|
+
items: { control: false },
|
|
20
|
+
onDismiss: { control: false },
|
|
21
|
+
duration: { control: { type: 'number' } },
|
|
22
|
+
transitionDuration: { control: { type: 'number' } },
|
|
23
|
+
maxVisible: { control: { type: 'number' } },
|
|
24
|
+
placement: {
|
|
25
|
+
control: { type: 'select' },
|
|
26
|
+
options: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
args: {
|
|
30
|
+
duration: 0,
|
|
31
|
+
transitionDuration: 200,
|
|
32
|
+
maxVisible: 3,
|
|
33
|
+
placement: 'bottom-right',
|
|
34
|
+
},
|
|
35
|
+
tags: ['autodocs'],
|
|
36
|
+
} satisfies Meta<typeof Snackbars>;
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
|
|
40
|
+
type Story = StoryObj<typeof meta>;
|
|
41
|
+
|
|
42
|
+
const previewStyle = css`
|
|
43
|
+
min-height: 360px;
|
|
44
|
+
padding: 24px;
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
export const Interactive: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
duration: 5000,
|
|
50
|
+
items: [],
|
|
51
|
+
onDismiss: () => undefined,
|
|
52
|
+
},
|
|
53
|
+
render: (args) => {
|
|
54
|
+
const snackbarIdCounterRef = useRef(0);
|
|
55
|
+
const [items, setItems] = useState<SnackbarsItem[]>([
|
|
56
|
+
{
|
|
57
|
+
id: 'initial',
|
|
58
|
+
children: 'Use the buttons to add more snackbars',
|
|
59
|
+
duration: 0,
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const controlsStyle = css`
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-wrap: wrap;
|
|
66
|
+
gap: 12px;
|
|
67
|
+
padding: 24px;
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const getNextSnackbarId = () => {
|
|
71
|
+
snackbarIdCounterRef.current += 1;
|
|
72
|
+
return `storybook-snackbar-${snackbarIdCounterRef.current}`;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const addSnackbar = (item: Omit<SnackbarsItem, 'id'>) => {
|
|
76
|
+
setItems((current) => [
|
|
77
|
+
...current,
|
|
78
|
+
{
|
|
79
|
+
...item,
|
|
80
|
+
id: getNextSnackbarId(),
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className={previewStyle}>
|
|
87
|
+
<div className={controlsStyle}>
|
|
88
|
+
<Button
|
|
89
|
+
onClick={() =>
|
|
90
|
+
addSnackbar({
|
|
91
|
+
children: `Snackbar ${items.length + 1}`,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
>
|
|
95
|
+
Add snackbar
|
|
96
|
+
</Button>
|
|
97
|
+
<Button
|
|
98
|
+
onClick={() =>
|
|
99
|
+
addSnackbar({
|
|
100
|
+
action: 'undo',
|
|
101
|
+
children: 'Snackbar with undo',
|
|
102
|
+
onUndo: () => alert('Undo action triggered'),
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
>
|
|
106
|
+
Add snackbar with undo
|
|
107
|
+
</Button>
|
|
108
|
+
<Button
|
|
109
|
+
onClick={() =>
|
|
110
|
+
addSnackbar({
|
|
111
|
+
children: 'Error snackbar',
|
|
112
|
+
size: 'small',
|
|
113
|
+
variant: 'error',
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
Add error snackbar
|
|
118
|
+
</Button>
|
|
119
|
+
<Button
|
|
120
|
+
onClick={() =>
|
|
121
|
+
addSnackbar({
|
|
122
|
+
children: 'Success snackbar',
|
|
123
|
+
size: 'small',
|
|
124
|
+
variant: 'success',
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
>
|
|
128
|
+
Add success snackbar
|
|
129
|
+
</Button>
|
|
130
|
+
</div>
|
|
131
|
+
<Snackbars
|
|
132
|
+
{...args}
|
|
133
|
+
items={items}
|
|
134
|
+
onDismiss={(id) =>
|
|
135
|
+
setItems((current) => current.filter((item) => item.id !== id))
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type HTMLAttributes,
|
|
3
|
+
memo,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { css, cx, keyframes } from '@emotion/css';
|
|
11
|
+
import useTheme from '../../theme/useTheme';
|
|
12
|
+
import Snackbar, { type SnackbarProps } from './Snackbar';
|
|
13
|
+
|
|
14
|
+
export const NAME = 'ucl-uikit-snackbars';
|
|
15
|
+
|
|
16
|
+
export type SnackbarsPlacement =
|
|
17
|
+
'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
18
|
+
|
|
19
|
+
const DEFAULT_AUTO_DISMISS_DURATION = 5000;
|
|
20
|
+
|
|
21
|
+
export interface SnackbarsItem extends SnackbarProps {
|
|
22
|
+
id: string;
|
|
23
|
+
duration?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SnackbarsProps extends Omit<
|
|
27
|
+
HTMLAttributes<HTMLDivElement>,
|
|
28
|
+
'children'
|
|
29
|
+
> {
|
|
30
|
+
items: SnackbarsItem[];
|
|
31
|
+
onDismiss: (id: string) => void;
|
|
32
|
+
duration?: number;
|
|
33
|
+
transitionDuration?: number;
|
|
34
|
+
maxVisible?: number;
|
|
35
|
+
placement?: SnackbarsPlacement;
|
|
36
|
+
testId?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const Snackbars = ({
|
|
40
|
+
items,
|
|
41
|
+
onDismiss,
|
|
42
|
+
duration = DEFAULT_AUTO_DISMISS_DURATION,
|
|
43
|
+
transitionDuration = 200,
|
|
44
|
+
maxVisible = 3,
|
|
45
|
+
placement = 'bottom-right',
|
|
46
|
+
testId = NAME,
|
|
47
|
+
className,
|
|
48
|
+
...props
|
|
49
|
+
}: SnackbarsProps) => {
|
|
50
|
+
const [theme] = useTheme();
|
|
51
|
+
const [exitingIds, setExitingIds] = useState<Set<string>>(new Set());
|
|
52
|
+
const autoDismissTimersRef = useRef<Record<string, number>>({});
|
|
53
|
+
const autoDismissStartedAtRef = useRef<Record<string, number>>({});
|
|
54
|
+
const autoDismissRemainingRef = useRef<Record<string, number>>({});
|
|
55
|
+
const exitTimersRef = useRef<Record<string, number>>({});
|
|
56
|
+
const dismissRef = useRef(onDismiss);
|
|
57
|
+
const focusedIdsRef = useRef<Set<string>>(new Set());
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
dismissRef.current = onDismiss;
|
|
61
|
+
}, [onDismiss]);
|
|
62
|
+
|
|
63
|
+
const visibleItems = useMemo(
|
|
64
|
+
() => items.slice(0, maxVisible),
|
|
65
|
+
[items, maxVisible]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const visibleItemIds = useMemo(
|
|
69
|
+
() => new Set(visibleItems.map((item) => item.id)),
|
|
70
|
+
[visibleItems]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const itemIds = useMemo(() => new Set(items.map((item) => item.id)), [items]);
|
|
74
|
+
|
|
75
|
+
const clearAutoDismissTimer = useCallback((id: string) => {
|
|
76
|
+
if (autoDismissTimersRef.current[id] === undefined) return;
|
|
77
|
+
|
|
78
|
+
window.clearTimeout(autoDismissTimersRef.current[id]);
|
|
79
|
+
delete autoDismissTimersRef.current[id];
|
|
80
|
+
delete autoDismissStartedAtRef.current[id];
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
const beginDismiss = useCallback(
|
|
84
|
+
(id: string) => {
|
|
85
|
+
clearAutoDismissTimer(id);
|
|
86
|
+
delete autoDismissRemainingRef.current[id];
|
|
87
|
+
focusedIdsRef.current.delete(id);
|
|
88
|
+
|
|
89
|
+
setExitingIds((current) => {
|
|
90
|
+
if (current.has(id)) return current;
|
|
91
|
+
|
|
92
|
+
const next = new Set(current);
|
|
93
|
+
next.add(id);
|
|
94
|
+
return next;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (exitTimersRef.current[id] !== undefined) return;
|
|
98
|
+
|
|
99
|
+
exitTimersRef.current[id] = window.setTimeout(() => {
|
|
100
|
+
delete exitTimersRef.current[id];
|
|
101
|
+
setExitingIds((current) => {
|
|
102
|
+
const next = new Set(current);
|
|
103
|
+
next.delete(id);
|
|
104
|
+
return next;
|
|
105
|
+
});
|
|
106
|
+
dismissRef.current(id);
|
|
107
|
+
}, transitionDuration);
|
|
108
|
+
},
|
|
109
|
+
[clearAutoDismissTimer, transitionDuration]
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const startAutoDismissTimer = useCallback(
|
|
113
|
+
(id: string, timeoutDuration: number) => {
|
|
114
|
+
autoDismissStartedAtRef.current[id] = Date.now();
|
|
115
|
+
autoDismissTimersRef.current[id] = window.setTimeout(() => {
|
|
116
|
+
delete autoDismissTimersRef.current[id];
|
|
117
|
+
delete autoDismissStartedAtRef.current[id];
|
|
118
|
+
delete autoDismissRemainingRef.current[id];
|
|
119
|
+
beginDismiss(id);
|
|
120
|
+
}, timeoutDuration);
|
|
121
|
+
},
|
|
122
|
+
[beginDismiss]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const pauseAutoDismiss = useCallback(
|
|
126
|
+
(id: string) => {
|
|
127
|
+
if (autoDismissTimersRef.current[id] === undefined) return;
|
|
128
|
+
|
|
129
|
+
const startedAt = autoDismissStartedAtRef.current[id];
|
|
130
|
+
const remainingDuration = autoDismissRemainingRef.current[id];
|
|
131
|
+
|
|
132
|
+
if (startedAt !== undefined && remainingDuration !== undefined) {
|
|
133
|
+
autoDismissRemainingRef.current[id] = Math.max(
|
|
134
|
+
0,
|
|
135
|
+
remainingDuration - (Date.now() - startedAt)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
clearAutoDismissTimer(id);
|
|
140
|
+
},
|
|
141
|
+
[clearAutoDismissTimer]
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const resumeAutoDismiss = useCallback(
|
|
145
|
+
(id: string) => {
|
|
146
|
+
if (focusedIdsRef.current.has(id)) return;
|
|
147
|
+
if (autoDismissTimersRef.current[id] !== undefined) return;
|
|
148
|
+
|
|
149
|
+
const remainingDuration = autoDismissRemainingRef.current[id];
|
|
150
|
+
if (remainingDuration === undefined) return;
|
|
151
|
+
|
|
152
|
+
if (remainingDuration <= 0) {
|
|
153
|
+
beginDismiss(id);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
startAutoDismissTimer(id, remainingDuration);
|
|
158
|
+
},
|
|
159
|
+
[beginDismiss, startAutoDismissTimer]
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const getAutoDismissDuration = useCallback(
|
|
163
|
+
(item: SnackbarsItem) => {
|
|
164
|
+
const itemDuration = item.duration ?? duration;
|
|
165
|
+
|
|
166
|
+
if ((item.action ?? 'close') !== 'undo' || itemDuration > 0) {
|
|
167
|
+
return itemDuration;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return duration > 0 ? duration : DEFAULT_AUTO_DISMISS_DURATION;
|
|
171
|
+
},
|
|
172
|
+
[duration]
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
visibleItems.forEach((item) => {
|
|
177
|
+
if (exitingIds.has(item.id)) return;
|
|
178
|
+
if (autoDismissTimersRef.current[item.id] !== undefined) return;
|
|
179
|
+
|
|
180
|
+
const itemDuration = getAutoDismissDuration(item);
|
|
181
|
+
if (itemDuration <= 0) return;
|
|
182
|
+
|
|
183
|
+
const remainingDuration =
|
|
184
|
+
autoDismissRemainingRef.current[item.id] ?? itemDuration;
|
|
185
|
+
autoDismissRemainingRef.current[item.id] = remainingDuration;
|
|
186
|
+
|
|
187
|
+
if (focusedIdsRef.current.has(item.id)) return;
|
|
188
|
+
|
|
189
|
+
startAutoDismissTimer(item.id, remainingDuration);
|
|
190
|
+
});
|
|
191
|
+
}, [
|
|
192
|
+
clearAutoDismissTimer,
|
|
193
|
+
exitingIds,
|
|
194
|
+
getAutoDismissDuration,
|
|
195
|
+
startAutoDismissTimer,
|
|
196
|
+
visibleItems,
|
|
197
|
+
]);
|
|
198
|
+
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
setExitingIds((current) => {
|
|
201
|
+
const next = new Set([...current].filter((id) => visibleItemIds.has(id)));
|
|
202
|
+
return next.size === current.size ? current : next;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
Object.entries(exitTimersRef.current).forEach(([id, timerId]) => {
|
|
206
|
+
if (itemIds.has(id)) return;
|
|
207
|
+
|
|
208
|
+
window.clearTimeout(timerId);
|
|
209
|
+
delete exitTimersRef.current[id];
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
Object.entries(autoDismissTimersRef.current).forEach(([id, timerId]) => {
|
|
213
|
+
if (visibleItemIds.has(id)) return;
|
|
214
|
+
|
|
215
|
+
window.clearTimeout(timerId);
|
|
216
|
+
delete autoDismissTimersRef.current[id];
|
|
217
|
+
delete autoDismissStartedAtRef.current[id];
|
|
218
|
+
delete autoDismissRemainingRef.current[id];
|
|
219
|
+
focusedIdsRef.current.delete(id);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
Object.keys(autoDismissRemainingRef.current).forEach((id) => {
|
|
223
|
+
if (visibleItemIds.has(id)) return;
|
|
224
|
+
|
|
225
|
+
delete autoDismissRemainingRef.current[id];
|
|
226
|
+
focusedIdsRef.current.delete(id);
|
|
227
|
+
});
|
|
228
|
+
}, [itemIds, visibleItemIds]);
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
const autoDismissTimers = autoDismissTimersRef.current;
|
|
232
|
+
const exitTimers = exitTimersRef.current;
|
|
233
|
+
|
|
234
|
+
return () => {
|
|
235
|
+
Object.values(autoDismissTimers).forEach((timerId) =>
|
|
236
|
+
window.clearTimeout(timerId)
|
|
237
|
+
);
|
|
238
|
+
Object.values(exitTimers).forEach((timerId) =>
|
|
239
|
+
window.clearTimeout(timerId)
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
}, []);
|
|
243
|
+
|
|
244
|
+
const isBottomPlacement = placement.startsWith('bottom');
|
|
245
|
+
const isRightPlacement = placement.endsWith('right');
|
|
246
|
+
const stackedItems = isBottomPlacement
|
|
247
|
+
? [...visibleItems].reverse()
|
|
248
|
+
: visibleItems;
|
|
249
|
+
|
|
250
|
+
const enterAnimation = keyframes`
|
|
251
|
+
from {
|
|
252
|
+
opacity: 0;
|
|
253
|
+
transform: translateY(
|
|
254
|
+
${isBottomPlacement ? theme.margin.m8 : `-${theme.margin.m8}`}
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
to {
|
|
259
|
+
opacity: 1;
|
|
260
|
+
transform: translateY(0);
|
|
261
|
+
}
|
|
262
|
+
`;
|
|
263
|
+
|
|
264
|
+
const containerStyle = css`
|
|
265
|
+
position: fixed;
|
|
266
|
+
z-index: 9999;
|
|
267
|
+
display: flex;
|
|
268
|
+
flex-direction: column;
|
|
269
|
+
gap: ${theme.margin.m8};
|
|
270
|
+
pointer-events: none;
|
|
271
|
+
${
|
|
272
|
+
isBottomPlacement
|
|
273
|
+
? `bottom: ${theme.margin.m24};`
|
|
274
|
+
: `top: ${theme.margin.m24};`
|
|
275
|
+
}
|
|
276
|
+
right: ${theme.margin.m16};
|
|
277
|
+
left: ${theme.margin.m16};
|
|
278
|
+
|
|
279
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
280
|
+
${
|
|
281
|
+
isRightPlacement
|
|
282
|
+
? `right: ${theme.margin.m24}; left: auto;`
|
|
283
|
+
: `right: auto; left: ${theme.margin.m24};`
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
|
|
288
|
+
const snackbarStyle = css`
|
|
289
|
+
position: static;
|
|
290
|
+
pointer-events: auto;
|
|
291
|
+
opacity: 1;
|
|
292
|
+
transform: translateY(0);
|
|
293
|
+
transition:
|
|
294
|
+
opacity ${transitionDuration}ms ease,
|
|
295
|
+
transform ${transitionDuration}ms ease;
|
|
296
|
+
animation: ${enterAnimation} ${transitionDuration}ms ease;
|
|
297
|
+
|
|
298
|
+
@media (prefers-reduced-motion: reduce) {
|
|
299
|
+
transition: none;
|
|
300
|
+
animation: none;
|
|
301
|
+
}
|
|
302
|
+
`;
|
|
303
|
+
|
|
304
|
+
const exitingStyle = css`
|
|
305
|
+
animation: none;
|
|
306
|
+
opacity: 0;
|
|
307
|
+
transform: translateY(
|
|
308
|
+
${isBottomPlacement ? theme.margin.m8 : `-${theme.margin.m8}`}
|
|
309
|
+
);
|
|
310
|
+
`;
|
|
311
|
+
|
|
312
|
+
const style = cx(NAME, containerStyle, className);
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<div
|
|
316
|
+
className={style}
|
|
317
|
+
data-testid={testId}
|
|
318
|
+
{...props}
|
|
319
|
+
>
|
|
320
|
+
{stackedItems.map(({ id, ...item }) => {
|
|
321
|
+
const itemIsExiting = exitingIds.has(id);
|
|
322
|
+
delete item.duration;
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<Snackbar
|
|
326
|
+
key={id}
|
|
327
|
+
action={item.action ?? 'close'}
|
|
328
|
+
{...item}
|
|
329
|
+
actionDisabled={item.actionDisabled || itemIsExiting}
|
|
330
|
+
className={cx(
|
|
331
|
+
snackbarStyle,
|
|
332
|
+
itemIsExiting && exitingStyle,
|
|
333
|
+
item.className
|
|
334
|
+
)}
|
|
335
|
+
onClose={() => {
|
|
336
|
+
if (exitTimersRef.current[id] !== undefined) return;
|
|
337
|
+
|
|
338
|
+
item.onClose?.();
|
|
339
|
+
beginDismiss(id);
|
|
340
|
+
}}
|
|
341
|
+
onUndo={() => {
|
|
342
|
+
if (exitTimersRef.current[id] !== undefined) return;
|
|
343
|
+
|
|
344
|
+
item.onUndo?.();
|
|
345
|
+
beginDismiss(id);
|
|
346
|
+
}}
|
|
347
|
+
onFocus={(event) => {
|
|
348
|
+
item.onFocus?.(event);
|
|
349
|
+
focusedIdsRef.current.add(id);
|
|
350
|
+
pauseAutoDismiss(id);
|
|
351
|
+
}}
|
|
352
|
+
onBlur={(event) => {
|
|
353
|
+
item.onBlur?.(event);
|
|
354
|
+
const nextFocusedElement = event.relatedTarget;
|
|
355
|
+
if (
|
|
356
|
+
nextFocusedElement instanceof Node &&
|
|
357
|
+
event.currentTarget.contains(nextFocusedElement)
|
|
358
|
+
) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
focusedIdsRef.current.delete(id);
|
|
363
|
+
resumeAutoDismiss(id);
|
|
364
|
+
}}
|
|
365
|
+
size={item.size}
|
|
366
|
+
testId={item.testId ?? `${testId}-${id}`}
|
|
367
|
+
variant={item.variant}
|
|
368
|
+
>
|
|
369
|
+
{item.children}
|
|
370
|
+
</Snackbar>
|
|
371
|
+
);
|
|
372
|
+
})}
|
|
373
|
+
</div>
|
|
374
|
+
);
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
export default memo(Snackbars);
|