@a-type/ui 3.0.43 → 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.
Files changed (54) hide show
  1. package/dist/cjs/components/layouts/PageNav.js +1 -1
  2. package/dist/cjs/components/layouts/PageNav.js.map +1 -1
  3. package/dist/cjs/components/particles/ParticleLayer.js +2 -1
  4. package/dist/cjs/components/particles/ParticleLayer.js.map +1 -1
  5. package/dist/cjs/components/provider/Provider.js +2 -2
  6. package/dist/cjs/components/provider/Provider.js.map +1 -1
  7. package/dist/cjs/components/toasts/toasts.d.ts +34 -5
  8. package/dist/cjs/components/toasts/toasts.js +100 -31
  9. package/dist/cjs/components/toasts/toasts.js.map +1 -1
  10. package/dist/cjs/components/toasts/toasts.stories.js +44 -13
  11. package/dist/cjs/components/toasts/toasts.stories.js.map +1 -1
  12. package/dist/cjs/components/viewport/ViewportState.js +8 -4
  13. package/dist/cjs/components/viewport/ViewportState.js.map +1 -1
  14. package/dist/cjs/hooks/useOverrideTheme.js +1 -1
  15. package/dist/cjs/hooks/useOverrideTheme.js.map +1 -1
  16. package/dist/cjs/hooks/useSize.d.ts +1 -1
  17. package/dist/cjs/hooks/useSize.js.map +1 -1
  18. package/dist/cjs/hooks/useTitleBarColor.js +3 -0
  19. package/dist/cjs/hooks/useTitleBarColor.js.map +1 -1
  20. package/dist/cjs/hooks/useVisualViewportOffset.js +10 -1
  21. package/dist/cjs/hooks/useVisualViewportOffset.js.map +1 -1
  22. package/dist/css/main.css +3 -4
  23. package/dist/esm/components/layouts/PageNav.js +1 -1
  24. package/dist/esm/components/layouts/PageNav.js.map +1 -1
  25. package/dist/esm/components/particles/ParticleLayer.js +2 -1
  26. package/dist/esm/components/particles/ParticleLayer.js.map +1 -1
  27. package/dist/esm/components/provider/Provider.js +3 -3
  28. package/dist/esm/components/provider/Provider.js.map +1 -1
  29. package/dist/esm/components/toasts/toasts.d.ts +34 -5
  30. package/dist/esm/components/toasts/toasts.js +97 -28
  31. package/dist/esm/components/toasts/toasts.js.map +1 -1
  32. package/dist/esm/components/toasts/toasts.stories.js +40 -9
  33. package/dist/esm/components/toasts/toasts.stories.js.map +1 -1
  34. package/dist/esm/components/viewport/ViewportState.js +8 -4
  35. package/dist/esm/components/viewport/ViewportState.js.map +1 -1
  36. package/dist/esm/hooks/useOverrideTheme.js +1 -1
  37. package/dist/esm/hooks/useOverrideTheme.js.map +1 -1
  38. package/dist/esm/hooks/useSize.d.ts +1 -1
  39. package/dist/esm/hooks/useSize.js.map +1 -1
  40. package/dist/esm/hooks/useTitleBarColor.js +3 -0
  41. package/dist/esm/hooks/useTitleBarColor.js.map +1 -1
  42. package/dist/esm/hooks/useVisualViewportOffset.js +10 -1
  43. package/dist/esm/hooks/useVisualViewportOffset.js.map +1 -1
  44. package/package.json +3 -2
  45. package/src/components/layouts/PageNav.tsx +3 -1
  46. package/src/components/particles/ParticleLayer.tsx +3 -1
  47. package/src/components/provider/Provider.tsx +15 -11
  48. package/src/components/toasts/toasts.stories.tsx +50 -8
  49. package/src/components/toasts/toasts.tsx +249 -63
  50. package/src/components/viewport/ViewportState.ts +8 -4
  51. package/src/hooks/useOverrideTheme.ts +1 -1
  52. package/src/hooks/useSize.ts +1 -1
  53. package/src/hooks/useTitleBarColor.ts +3 -0
  54. package/src/hooks/useVisualViewportOffset.ts +38 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-type/ui",
3
- "version": "3.0.43",
3
+ "version": "3.1.0-beta.4",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "url": "https://github.com/a-type/ui"
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@a-type/utils": "^1.1.4",
44
+ "@base-ui/react": "^1.0.0",
44
45
  "@radix-ui/react-accordion": "^1.2.2",
45
46
  "@radix-ui/react-checkbox": "^1.1.3",
46
47
  "@radix-ui/react-collapsible": "^1.1.2",
@@ -85,13 +86,13 @@
85
86
  "@storybook/react": "8.4.7",
86
87
  "@storybook/react-vite": "8.4.7",
87
88
  "@storybook/test": "8.4.7",
89
+ "@types/dom-chromium-installation-events": "^101.0.4",
88
90
  "@types/pluralize": "^0.0.33",
89
91
  "@types/react": "^19.0.1",
90
92
  "@types/react-dom": "^19.0.2",
91
93
  "@types/react-is": "^19.0.0",
92
94
  "@types/w3c-image-capture": "^1.0.10",
93
95
  "@types/web-app-manifest": "^1.0.9",
94
- "@types/dom-chromium-installation-events": "^101.0.4",
95
96
  "@unocss/cli": "66.5.4",
96
97
  "@unocss/core": "66.5.4",
97
98
  "@unocss/transformer-variant-group": "66.5.4",
@@ -11,7 +11,9 @@ export function PageNav({
11
11
  children,
12
12
  ...props
13
13
  }: HTMLAttributes<HTMLDivElement>) {
14
- const bodyRef = useRef(document.documentElement);
14
+ const bodyRef = useRef(
15
+ typeof document === 'undefined' ? null : document.documentElement,
16
+ );
15
17
  const ref = useBoundsCssVars<HTMLDivElement>(undefined, bodyRef, {
16
18
  left: '--nav-left',
17
19
  top: '--nav-top',
@@ -31,9 +31,11 @@ export function ParticleLayer({
31
31
  />
32
32
  );
33
33
 
34
+ const ssr = typeof document === 'undefined';
35
+
34
36
  return (
35
37
  <ParticlesProvider value={particles}>
36
- {noPortal ? canvas : createPortal(canvas, document.body)}
38
+ {noPortal || ssr ? canvas : createPortal(canvas, document.body)}
37
39
  {children}
38
40
  </ParticlesProvider>
39
41
  );
@@ -4,7 +4,7 @@ import { useVirtualKeyboardBehavior } from '../../hooks/useVirtualKeyboardBehavi
4
4
  import { IconSpritesheet } from '../icon/index.js';
5
5
  import { ParticleLayer } from '../particles/index.js';
6
6
  import { PwaInstall } from '../pwaInstall/PwaInstall.js';
7
- import { Toaster } from '../toasts/toasts.js';
7
+ import { DefaultToastProvider, Toaster } from '../toasts/toasts.js';
8
8
  import { TooltipProvider } from '../tooltip/index.js';
9
9
 
10
10
  export interface ProviderProps {
@@ -47,10 +47,12 @@ export function Provider({
47
47
  <ConfigContext.Provider
48
48
  value={{ virtualKeyboardBehavior: supportedVirtualKeyboardBehavior }}
49
49
  >
50
- <TooltipProvider>
51
- {children}
52
- {otherStuff}
53
- </TooltipProvider>
50
+ <DefaultToastProvider>
51
+ <TooltipProvider>
52
+ {children}
53
+ {otherStuff}
54
+ </TooltipProvider>
55
+ </DefaultToastProvider>
54
56
  </ConfigContext.Provider>
55
57
  );
56
58
 
@@ -58,12 +60,14 @@ export function Provider({
58
60
  <ConfigContext.Provider
59
61
  value={{ virtualKeyboardBehavior: supportedVirtualKeyboardBehavior }}
60
62
  >
61
- <TooltipProvider>
62
- <ParticleLayer>
63
- {children}
64
- {otherStuff}
65
- </ParticleLayer>
66
- </TooltipProvider>
63
+ <DefaultToastProvider>
64
+ <TooltipProvider>
65
+ <ParticleLayer>
66
+ {children}
67
+ {otherStuff}
68
+ </ParticleLayer>
69
+ </TooltipProvider>
70
+ </DefaultToastProvider>
67
71
  </ConfigContext.Provider>
68
72
  );
69
73
  }
@@ -1,7 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
- import { toast } from 'react-hot-toast';
3
2
  import { Box } from '../box/Box.js';
4
3
  import { Button } from '../button/Button.js';
4
+ import { toast } from './toasts.js';
5
5
 
6
6
  const meta = {
7
7
  title: 'Components/toasts',
@@ -24,7 +24,7 @@ export const Default: Story = {
24
24
  toast(
25
25
  'This is a default toast! With a lot of text. Enough to wrap around.',
26
26
  {
27
- duration: 10_000,
27
+ duration: 120_000,
28
28
  },
29
29
  );
30
30
  }}
@@ -35,7 +35,7 @@ export const Default: Story = {
35
35
  color="success"
36
36
  onClick={() => {
37
37
  toast.success('This is a success toast!', {
38
- duration: 10_000,
38
+ duration: 20_000,
39
39
  });
40
40
  }}
41
41
  >
@@ -45,7 +45,7 @@ export const Default: Story = {
45
45
  color="attention"
46
46
  onClick={() => {
47
47
  toast.error('This is an error toast!', {
48
- duration: 10_000,
48
+ duration: 20_000,
49
49
  });
50
50
  }}
51
51
  >
@@ -53,10 +53,13 @@ export const Default: Story = {
53
53
  </Button>
54
54
  <Button
55
55
  onClick={() => {
56
- const id = toast.loading('This is a loading toast!');
56
+ const { complete } = toast.loading('This is a loading toast!');
57
57
  setTimeout(() => {
58
- toast.success('Loading complete!', { id, duration: 5000 });
59
- }, 3000);
58
+ complete('Loading complete!', {
59
+ duration: 5000,
60
+ type: 'success',
61
+ });
62
+ }, 5000);
60
63
  }}
61
64
  >
62
65
  Show Loading Toast
@@ -68,7 +71,8 @@ export const Default: Story = {
68
71
  await new Promise((resolve) => setTimeout(resolve, 3000));
69
72
  })(),
70
73
  {
71
- loading: 'Promise is loading...',
74
+ loading:
75
+ 'Promise is loading... This text is longer to test animation of transition',
72
76
  success: 'Promise resolved!',
73
77
  error: 'Promise rejected.',
74
78
  },
@@ -77,6 +81,44 @@ export const Default: Story = {
77
81
  >
78
82
  Show Promise Toast
79
83
  </Button>
84
+ <Button
85
+ onClick={() => {
86
+ toast('This is a toast with actions!', {
87
+ timeout: 20_000,
88
+ data: {
89
+ actions: [
90
+ {
91
+ label: 'Retry',
92
+ emphasis: 'primary',
93
+ onClick: () => {
94
+ alert('Retry clicked!');
95
+ },
96
+ },
97
+ {
98
+ label: 'Undo',
99
+ emphasis: 'light',
100
+ onClick: () => {
101
+ alert('Undo clicked!');
102
+ },
103
+ },
104
+ ],
105
+ },
106
+ });
107
+ }}
108
+ >
109
+ Show Toast with Actions
110
+ </Button>
111
+ <Button
112
+ onClick={() => {
113
+ toast({
114
+ title: 'Rich Toast',
115
+ description: 'This toast has both a title and a description.',
116
+ timeout: 20_000,
117
+ });
118
+ }}
119
+ >
120
+ Show Rich Toast
121
+ </Button>
80
122
  </Box>
81
123
  );
82
124
  },
@@ -1,75 +1,261 @@
1
- export type * from 'react-hot-toast';
2
- export { toast } from 'react-hot-toast';
1
+ import {
2
+ Toast,
3
+ ToastManagerAddOptions,
4
+ ToastManagerPromiseOptions,
5
+ ToastObject,
6
+ } from '@base-ui/react/toast';
3
7
  import clsx from 'clsx';
4
- import { AnimatePresence, motion } from 'motion/react';
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 toastOptions: DefaultToastOptions = {};
14
+ export const manager = Toast.createToastManager();
11
15
 
12
- export const Toaster = (props: { className?: string }) => {
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
- return createPortal(
19
- <div
44
+ const toasts = untypedToasts as Array<ToastObject<CustomToastData>>;
45
+
46
+ return toasts.map((toast) => (
47
+ <Toast.Root
48
+ key={toast.id}
49
+ toast={toast}
50
+ swipeDirection={['up', 'right', 'left']}
20
51
  className={clsx(
21
- 'fixed z-100000 flex flex-col items-center gap-xs left-1/2 center-x top-sm max-w-400px',
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
+ },
22
95
  mode === 'dark' ? 'override-light' : 'override-dark',
23
- props.className,
24
96
  )}
25
- onMouseEnter={startPause}
26
- onMouseLeave={endPause}
27
97
  >
28
- <AnimatePresence>
29
- {visibleToasts.map((toast) => {
30
- const message =
31
- typeof toast.message === 'function'
32
- ? toast.message(toast)
33
- : toast.message;
34
- return (
35
- <motion.div
36
- key={toast.id}
37
- className={clsx(
38
- {
39
- 'palette-success': toast.type === 'success',
40
- 'palette-attention': toast.type === 'error',
41
- 'palette-info': toast.type === 'blank',
42
- },
43
- 'bg-main-wash color-black rounded-md shadow-md px-md py-sm',
44
- 'flex flex-row gap-sm',
45
- )}
46
- {...toast.ariaProps}
47
- initial={{ scale: 0.8, opacity: 0, y: -20 }}
48
- exit={{ scale: 0.8, opacity: 0, y: -20 }}
49
- animate={{
50
- scale: 1,
51
- opacity: 1,
52
- y: 0,
53
- }}
54
- layout
55
- >
56
- <Icon
57
- className="mt-2px"
58
- loading={toast.type === 'loading'}
59
- name={
60
- toast.type === 'success'
61
- ? 'check'
62
- : toast.type === 'error'
63
- ? 'warning'
64
- : 'info'
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
+ />
65
157
  }
66
- />
67
- {message}
68
- </motion.div>
69
- );
70
- })}
71
- </AnimatePresence>
72
- </div>,
73
- document.body,
74
- );
75
- };
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 };
@@ -136,8 +136,10 @@ export class ViewportState extends EventSubscriber<ViewportEvents> {
136
136
  this._center = this.getCanvasCenter();
137
137
 
138
138
  this.bindRoot(boundElement ?? null);
139
- document.addEventListener('gesturestart', preventDefault);
140
- document.addEventListener('gesturechange', preventDefault);
139
+ if (typeof document !== 'undefined') {
140
+ document.addEventListener('gesturestart', preventDefault);
141
+ document.addEventListener('gesturechange', preventDefault);
142
+ }
141
143
  }
142
144
 
143
145
  private setBoundElementSize = (size: Size, offset?: Vector2) => {
@@ -368,8 +370,10 @@ export class ViewportState extends EventSubscriber<ViewportEvents> {
368
370
  }
369
371
 
370
372
  dispose = () => {
371
- document.removeEventListener('gesturestart', preventDefault);
372
- document.removeEventListener('gesturechange', preventDefault);
373
+ if (typeof document !== 'undefined') {
374
+ document.removeEventListener('gesturestart', preventDefault);
375
+ document.removeEventListener('gesturechange', preventDefault);
376
+ }
373
377
  };
374
378
 
375
379
  updateConfig = (config: Partial<ViewportConfig>) => {
@@ -3,7 +3,7 @@ import { PaletteName } from '../uno/index.js';
3
3
 
4
4
  export function useOverrideTheme(theme: PaletteName | null | undefined) {
5
5
  useLayoutEffect(() => {
6
- if (!theme) {
6
+ if (!theme || typeof document === 'undefined') {
7
7
  return;
8
8
  }
9
9
  document.body.classList.add(`theme-override-${theme}`);
@@ -107,7 +107,7 @@ export function useBounds<E extends HTMLElement>(
107
107
 
108
108
  export function useBoundsCssVars<E extends HTMLElement>(
109
109
  debounceMs?: number,
110
- applyToRef?: RefObject<HTMLElement>,
110
+ applyToRef?: RefObject<HTMLElement | null>,
111
111
  propertyNames?: {
112
112
  left: string;
113
113
  top: string;
@@ -18,6 +18,9 @@ if (typeof document !== 'undefined') {
18
18
  }
19
19
 
20
20
  function changeThemeColor(color: string) {
21
+ if (typeof document === 'undefined') {
22
+ return;
23
+ }
21
24
  // evaluate css var if necessary
22
25
  if (color.startsWith('var(')) {
23
26
  const root = document.documentElement;