@a-type/ui 3.0.44 → 3.1.0-beta.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/cjs/components/provider/Provider.js +2 -2
- package/dist/cjs/components/provider/Provider.js.map +1 -1
- package/dist/cjs/components/toasts/toasts.d.ts +34 -5
- package/dist/cjs/components/toasts/toasts.js +99 -34
- package/dist/cjs/components/toasts/toasts.js.map +1 -1
- package/dist/cjs/components/toasts/toasts.stories.js +44 -13
- package/dist/cjs/components/toasts/toasts.stories.js.map +1 -1
- package/dist/css/main.css +3 -4
- package/dist/esm/components/provider/Provider.js +3 -3
- package/dist/esm/components/provider/Provider.js.map +1 -1
- package/dist/esm/components/toasts/toasts.d.ts +34 -5
- package/dist/esm/components/toasts/toasts.js +96 -31
- package/dist/esm/components/toasts/toasts.js.map +1 -1
- package/dist/esm/components/toasts/toasts.stories.js +40 -9
- package/dist/esm/components/toasts/toasts.stories.js.map +1 -1
- package/package.json +3 -2
- package/src/components/provider/Provider.tsx +15 -11
- package/src/components/toasts/toasts.stories.tsx +50 -8
- package/src/components/toasts/toasts.tsx +248 -67
|
@@ -1,80 +1,261 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Toast,
|
|
3
|
+
ToastManagerAddOptions,
|
|
4
|
+
ToastManagerPromiseOptions,
|
|
5
|
+
ToastObject,
|
|
6
|
+
} from '@base-ui/react/toast';
|
|
3
7
|
import clsx from 'clsx';
|
|
4
|
-
import {
|
|
5
|
-
import { createPortal } from 'react-dom';
|
|
6
|
-
import { DefaultToastOptions, useToaster } from 'react-hot-toast';
|
|
8
|
+
import { ReactNode } from 'react';
|
|
7
9
|
import { useResolvedColorMode } from '../../colorMode.js';
|
|
10
|
+
import { Button, ButtonProps } from '../button/index.js';
|
|
8
11
|
import { Icon } from '../icon/Icon.js';
|
|
12
|
+
import { Spinner } from '../spinner/Spinner.js';
|
|
9
13
|
|
|
10
|
-
const
|
|
14
|
+
export const manager = Toast.createToastManager();
|
|
11
15
|
|
|
12
|
-
export const
|
|
16
|
+
export const DefaultToastProvider = ({
|
|
17
|
+
children,
|
|
18
|
+
...rest
|
|
19
|
+
}: {
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
timeout?: number;
|
|
22
|
+
}) => {
|
|
23
|
+
return (
|
|
24
|
+
<Toast.Provider toastManager={manager} {...rest}>
|
|
25
|
+
{children}
|
|
26
|
+
</Toast.Provider>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function Toaster() {
|
|
31
|
+
return (
|
|
32
|
+
<Toast.Portal>
|
|
33
|
+
<Toast.Viewport className="overflow-clip">
|
|
34
|
+
<ToastList />
|
|
35
|
+
</Toast.Viewport>
|
|
36
|
+
</Toast.Portal>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ToastList() {
|
|
41
|
+
const { toasts: untypedToasts } = Toast.useToastManager();
|
|
13
42
|
const mode = useResolvedColorMode();
|
|
14
|
-
const { toasts, handlers } = useToaster(toastOptions);
|
|
15
|
-
const { startPause, endPause } = handlers;
|
|
16
|
-
const visibleToasts = toasts.filter((t) => t.visible);
|
|
17
43
|
|
|
18
|
-
const
|
|
19
|
-
if (!target) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
44
|
+
const toasts = untypedToasts as Array<ToastObject<CustomToastData>>;
|
|
22
45
|
|
|
23
|
-
return
|
|
24
|
-
<
|
|
46
|
+
return toasts.map((toast) => (
|
|
47
|
+
<Toast.Root
|
|
48
|
+
key={toast.id}
|
|
49
|
+
toast={toast}
|
|
50
|
+
swipeDirection={['up', 'right', 'left']}
|
|
25
51
|
className={clsx(
|
|
26
|
-
|
|
52
|
+
// variable setup
|
|
53
|
+
'[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))]',
|
|
54
|
+
'[--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))]',
|
|
55
|
+
'[--offset-y:calc(var(--toast-offset-y)+calc(var(--toast-index)*var(--gap))+var(--toast-swipe-movement-y))]',
|
|
56
|
+
// basic positioning
|
|
57
|
+
'absolute left-0 top-xs left-auto z-[calc(100000-var(--toast-index))] mr-0 w-full origin-top',
|
|
58
|
+
'h-[--height]',
|
|
59
|
+
'flex flex-col gap-xs items-center',
|
|
60
|
+
// other properties
|
|
61
|
+
'select-none',
|
|
62
|
+
// animation and interaction
|
|
63
|
+
'translate-x-[--toast-swipe-movement-x] translate-y-[calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--peek))+(var(--shrink)*var(--height)))] scale-[var(--scale)]',
|
|
64
|
+
'[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]',
|
|
65
|
+
// ::after
|
|
66
|
+
'after:(absolute top-full left-0 h-[calc(var(--gap)+1px)] w-full content-empty)',
|
|
67
|
+
// starting style
|
|
68
|
+
'data-[starting-style]:(-translate-y-150%)',
|
|
69
|
+
// limited
|
|
70
|
+
'data-[limited]:opacity-0',
|
|
71
|
+
//expanded
|
|
72
|
+
'data-[expanded]:(h-[--toast-height] translate-x-[--toast-swipe-movement-x] translate-y-[--offset-y] scale-100)',
|
|
73
|
+
// ending styles
|
|
74
|
+
'data-[ending-style]:(opacity-0)',
|
|
75
|
+
// natural or close button
|
|
76
|
+
'[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:(-translate-y-150% scale-90 opacity-50)',
|
|
77
|
+
// swiping down
|
|
78
|
+
'data-[ending-style]:data-[swipe-direction=down]:(translate-y-[calc(var(--toast-swipe-movement-y)+150%)])',
|
|
79
|
+
'data-[expanded]:data-[ending-style]:data-[swipe-direction=down]:(translate-y-[calc(var(--toast-swipe-movement-y)+150%)])',
|
|
80
|
+
// swiping left
|
|
81
|
+
'data-[ending-style]:data-[swipe-direction=left]:(translate-x-[calc(var(--toast-swipe-movement-x)-150%)] translate-y-[var(--offset-y)])',
|
|
82
|
+
'data-[expanded]:data-[ending-style]:data-[swipe-direction=left]:(translate-x-[calc(var(--toast-swipe-movement-x)-150%)] translate-y-[var(--offset-y)])',
|
|
83
|
+
// swiping right
|
|
84
|
+
'data-[ending-style]:data-[swipe-direction=right]:(translate-x-[calc(var(--toast-swipe-movement-x)+150%)] translate-y-[var(--offset-y)])',
|
|
85
|
+
'data-[expanded]:data-[ending-style]:data-[swipe-direction=right]:(translate-x-[calc(var(--toast-swipe-movement-x)+150%)] translate-y-[var(--offset-y)])',
|
|
86
|
+
// swiping up
|
|
87
|
+
'data-[ending-style]:data-[swipe-direction=up]:(translate-y-[calc(var(--toast-swipe-movement-y)-150%)])',
|
|
88
|
+
'data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:(translate-y-[calc(var(--toast-swipe-movement-y)-150%)])',
|
|
89
|
+
// themeing
|
|
90
|
+
{
|
|
91
|
+
'palette-success': toast.type === 'success',
|
|
92
|
+
'palette-attention': toast.type === 'error',
|
|
93
|
+
'palette-info': toast.type === 'blank',
|
|
94
|
+
},
|
|
27
95
|
mode === 'dark' ? 'override-light' : 'override-dark',
|
|
28
|
-
props.className,
|
|
29
96
|
)}
|
|
30
|
-
onMouseEnter={startPause}
|
|
31
|
-
onMouseLeave={endPause}
|
|
32
97
|
>
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
98
|
+
<Toast.Content className="[&[data-behind]:not([data-expanded])]:pointer-events-none flex flex-col gap-2px max-w-sm">
|
|
99
|
+
<div
|
|
100
|
+
className={clsx(
|
|
101
|
+
'layer-components:(bg-main-wash color-black rounded-md b-1 b-solid b-black shadow-md pl-md pr-sm py-sm relative)',
|
|
102
|
+
'layer-components:(flex flex-row gap-sm)',
|
|
103
|
+
'[[data-behind]:not([data-expanded])_&]:(bg-darken-2 max-h-[--height])',
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
className={clsx(
|
|
108
|
+
'flex flex-row gap-xs items-center',
|
|
109
|
+
'[[data-behind]:not([data-expanded])_&]:(opacity-0) [[data-expanded]_&]:(opacity-100) transition-opacity [transition-duration:250ms]',
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
<div className="flex flex-col gap-xs">
|
|
113
|
+
<Toast.Title className="text-sm leading-tight font-bold m-0" />
|
|
114
|
+
<div className="flex gap-sm">
|
|
115
|
+
{toast.data?.loading ? (
|
|
116
|
+
<Spinner size={15} className="relative top-2px" />
|
|
117
|
+
) : toast.type === 'success' ? (
|
|
118
|
+
<Icon
|
|
119
|
+
name="check"
|
|
120
|
+
color="success"
|
|
121
|
+
className="relative top-2px"
|
|
122
|
+
/>
|
|
123
|
+
) : toast.type === 'error' ? (
|
|
124
|
+
<Icon
|
|
125
|
+
name="warning"
|
|
126
|
+
color="attention"
|
|
127
|
+
className="relative top-2px"
|
|
128
|
+
/>
|
|
129
|
+
) : null}
|
|
130
|
+
<Toast.Description className="text-sm m-0" />
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<Toast.Close
|
|
134
|
+
className="mb-auto [[data-behind]:not([data-expanded])_&]:(invisible)"
|
|
135
|
+
aria-label="Close"
|
|
136
|
+
render={
|
|
137
|
+
<Button size="small" emphasis="ghost">
|
|
138
|
+
<Icon name="x" />
|
|
139
|
+
</Button>
|
|
140
|
+
}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
{toast.data?.actions && (
|
|
145
|
+
<div className="flex gap-xxs items-center ml-auto [[data-behind]:not([data-expanded])_&]:(opacity-0) transition-opacity">
|
|
146
|
+
{toast.data.actions.toReversed().map((action, index: number) => (
|
|
147
|
+
<Toast.Action
|
|
148
|
+
key={index}
|
|
149
|
+
className="text-xs"
|
|
150
|
+
onClick={action.onClick}
|
|
151
|
+
render={
|
|
152
|
+
<Button
|
|
153
|
+
size="small"
|
|
154
|
+
emphasis={action.emphasis}
|
|
155
|
+
color={action.color}
|
|
156
|
+
/>
|
|
70
157
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
}
|
|
158
|
+
>
|
|
159
|
+
{action.label}
|
|
160
|
+
</Toast.Action>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</Toast.Content>
|
|
165
|
+
</Toast.Root>
|
|
166
|
+
));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface CustomToastData {
|
|
170
|
+
actions?: {
|
|
171
|
+
label: ReactNode;
|
|
172
|
+
onClick: () => void;
|
|
173
|
+
emphasis?: ButtonProps['emphasis'];
|
|
174
|
+
color?: ButtonProps['color'];
|
|
175
|
+
}[];
|
|
176
|
+
loading?: boolean;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface ToastOptions extends ToastManagerAddOptions<CustomToastData> {
|
|
180
|
+
/** @deprecated - use timeout */
|
|
181
|
+
duration?: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function baseToast(
|
|
185
|
+
messageOrOptions: string | ToastOptions,
|
|
186
|
+
maybeOptions?: ToastOptions,
|
|
187
|
+
) {
|
|
188
|
+
const description =
|
|
189
|
+
typeof messageOrOptions === 'string' ? messageOrOptions : undefined;
|
|
190
|
+
const options =
|
|
191
|
+
typeof messageOrOptions === 'string' ? maybeOptions : messageOrOptions;
|
|
192
|
+
const extraOptions =
|
|
193
|
+
typeof messageOrOptions === 'string' && maybeOptions ? maybeOptions : {};
|
|
194
|
+
|
|
195
|
+
const finalOptions = {
|
|
196
|
+
description,
|
|
197
|
+
timeout:
|
|
198
|
+
options?.duration ??
|
|
199
|
+
extraOptions?.duration ??
|
|
200
|
+
options?.timeout ??
|
|
201
|
+
extraOptions?.timeout,
|
|
202
|
+
...options,
|
|
203
|
+
...extraOptions,
|
|
204
|
+
};
|
|
205
|
+
if (options?.id) {
|
|
206
|
+
manager.update<CustomToastData>(options.id, finalOptions);
|
|
207
|
+
return options.id;
|
|
208
|
+
}
|
|
209
|
+
return manager.add(finalOptions);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const toast = Object.assign(baseToast, {
|
|
213
|
+
success(
|
|
214
|
+
messageOrOptions: string | ToastOptions,
|
|
215
|
+
maybeOptions?: ToastOptions,
|
|
216
|
+
) {
|
|
217
|
+
return baseToast(messageOrOptions, {
|
|
218
|
+
type: 'success',
|
|
219
|
+
...maybeOptions,
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
error(messageOrOptions: string | ToastOptions, maybeOptions?: ToastOptions) {
|
|
223
|
+
return baseToast(messageOrOptions, {
|
|
224
|
+
type: 'error',
|
|
225
|
+
...maybeOptions,
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
promise: function <T>(
|
|
229
|
+
promise: Promise<T>,
|
|
230
|
+
options: ToastManagerPromiseOptions<T, CustomToastData>,
|
|
231
|
+
) {
|
|
232
|
+
return manager.promise(promise, options);
|
|
233
|
+
},
|
|
234
|
+
loading: function (
|
|
235
|
+
messageOrOptions: string | ToastOptions,
|
|
236
|
+
maybeOptions?: ToastOptions,
|
|
237
|
+
) {
|
|
238
|
+
const id = baseToast(messageOrOptions, {
|
|
239
|
+
timeout: 0,
|
|
240
|
+
data: { loading: true },
|
|
241
|
+
...maybeOptions,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
id,
|
|
246
|
+
complete: (
|
|
247
|
+
messageOrOptions: string | ToastOptions,
|
|
248
|
+
maybeOptions?: ToastOptions,
|
|
249
|
+
) => {
|
|
250
|
+
baseToast(messageOrOptions, {
|
|
251
|
+
id,
|
|
252
|
+
data: { loading: false },
|
|
253
|
+
...maybeOptions,
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
export type * from '@base-ui/react/toast';
|
|
261
|
+
export { Toast };
|