Skip to content

Commit 0b90bdb

Browse files
authored
feat(ToastProvider): migrate to 'sonner' for stacked, animated toasts (#1032)
* feat(ToastProvider): migrate to 'sonner' for animated, accessible toasts * feat(ToastProvider): restrict swipe to right only to match previous behavior
1 parent 2d883a0 commit 0b90bdb

3 files changed

Lines changed: 36 additions & 70 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
"@octokit/graphql": "^9.0.3",
4444
"@radix-ui/react-dialog": "^1.1.15",
4545
"@radix-ui/react-dropdown-menu": "^2.1.16",
46-
"@radix-ui/react-toast": "^1.2.15",
4746
"@radix-ui/react-tooltip": "^1.2.8",
4847
"@react-three/drei": "^10.7.7",
4948
"@react-three/fiber": "^9.5.0",
@@ -110,6 +109,7 @@
110109
"react-instantsearch": "^7.29.0",
111110
"remove-markdown": "^0.6.3",
112111
"resend": "^6.10.0",
112+
"sonner": "^2.0.7",
113113
"streamdown": "^2.5.0",
114114
"tailwind-merge": "^3.5.0",
115115
"tar-stream": "^3.1.8",

pnpm-lock.yaml

Lines changed: 14 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/ToastProvider.tsx

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import * as React from 'react'
2-
import * as Toast from '@radix-ui/react-toast'
2+
import { Toaster, toast } from 'sonner'
3+
import { useTheme } from '~/components/ThemeProvider'
34

45
type ToastOptions = {
56
durationMs?: number
67
}
78

8-
type ToastItem = {
9-
id: string
10-
content: React.ReactNode
11-
durationMs: number
12-
}
13-
149
type ToastContextValue = {
1510
notify: (content: React.ReactNode, options?: ToastOptions) => string
1611
}
@@ -30,46 +25,39 @@ export function ToastProvider({
3025
}: {
3126
children: React.ReactNode
3227
}): React.ReactElement {
33-
const [toasts, setToasts] = React.useState<ToastItem[]>([])
28+
const { resolvedTheme } = useTheme()
3429

3530
const notify = React.useCallback(
3631
(content: React.ReactNode, options?: ToastOptions) => {
37-
const id = Math.random().toString(36).slice(2)
38-
const durationMs = options?.durationMs ?? 2500
39-
setToasts((prev) => [...prev, { id, content, durationMs }])
40-
return id
32+
const id = toast(content, { duration: options?.durationMs ?? 2500 })
33+
return String(id)
4134
},
4235
[],
4336
)
4437

45-
const removeToast = React.useCallback((id: string) => {
46-
setToasts((prev) => prev.filter((t) => t.id !== id))
47-
}, [])
48-
4938
const contextValue = React.useMemo<ToastContextValue>(
5039
() => ({ notify }),
5140
[notify],
5241
)
5342

5443
return (
5544
<ToastContext.Provider value={contextValue}>
56-
<Toast.Provider swipeDirection="right">
57-
{children}
58-
{toasts.map((t) => (
59-
<Toast.Root
60-
key={t.id}
61-
defaultOpen
62-
duration={t.durationMs}
63-
onOpenChange={(open) => {
64-
if (!open) removeToast(t.id)
65-
}}
66-
className="relative z-[60] rounded-md border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 shadow-lg outline-none dark:border-gray-700 dark:bg-gray-800 dark:text-gray-50"
67-
>
68-
<div className="flex items-start gap-2">{t.content}</div>
69-
</Toast.Root>
70-
))}
71-
<Toast.Viewport className="fixed bottom-4 right-4 z-[60] flex max-h-screen w-96 flex-col gap-2 p-0 outline-none" />
72-
</Toast.Provider>
45+
{children}
46+
<Toaster
47+
theme={resolvedTheme}
48+
position="bottom-right"
49+
swipeDirections={['right']}
50+
offset={16}
51+
gap={8}
52+
toastOptions={{
53+
unstyled: true,
54+
classNames: {
55+
toast:
56+
'relative w-96 rounded-md border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 shadow-lg outline-none dark:border-gray-700 dark:bg-gray-800 dark:text-gray-50',
57+
content: 'flex items-start gap-2',
58+
},
59+
}}
60+
/>
7361
</ToastContext.Provider>
7462
)
7563
}

0 commit comments

Comments
 (0)