-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
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
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-only
import * as RadixToast from "@radix-ui/react-toast";
import { Flex } from "@radix-ui/themes";
import { createContext, type FC, type ReactNode, use, useMemo, useState } from "react";
import { Toast, type ToastProps } from "../components/Toast.tsx";
interface ToastController {
open(props: ToastProps): () => void;
}
const ToastContext = createContext<ToastController>({
open() {
return () => {};
},
});
export interface ToastProviderProps {
children: ReactNode;
}
export const ToastProvider: FC<ToastProviderProps> = ({ children }) => {
const [toasts, setToasts] = useState<readonly (ToastProps & { key: string })[]>([]);
const controller = useMemo<ToastController>(() => {
return {
open(props) {
const queue: ToastProps & { key: string } = {
...props,
key: crypto.randomUUID(),
onOpenChange(open) {
props.onOpenChange?.(open);
if (!open) {
setToasts((prev) => prev.filter((p) => p !== queue));
}
},
};
setToasts((prev) => [...prev, queue]);
return () => void setToasts((prev) => prev.filter((p) => p !== queue));
},
};
}, []);
return (
<RadixToast.Provider duration={3_000}>
<ToastContext.Provider value={controller}>{children}</ToastContext.Provider>
{toasts.map(({ key, ...toast }) => (
<Toast
key={key}
{...toast}
style={{ pointerEvents: "auto", viewTransitionName: `toast-${key}` }}
/>
))}
<RadixToast.Viewport asChild>
<Flex
position="absolute"
top={{ initial: "0", md: "unset" }}
right="0"
bottom={{ md: "0" }}
left={{ initial: "0", md: "unset" }}
p="2"
direction={{ initial: "column", md: "column-reverse" }}
align="center"
gap="2"
overflowX="hidden"
style={{ pointerEvents: "none", zIndex: 999 }}
/>
</RadixToast.Viewport>
</RadixToast.Provider>
);
};
export function useToast() {
return use(ToastContext);
}