Skip to content

Commit a94375f

Browse files
authored
feat(toast): add toast component
1 parent e246a11 commit a94375f

File tree

15 files changed

+6083
-2539
lines changed

15 files changed

+6083
-2539
lines changed

apps/www/app/layout.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SiteFooter } from "@/components/site-footer"
77
import { SiteHeader } from "@/components/site-header"
88
import { TailwindIndicator } from "@/components/tailwind-indicator"
99
import { ThemeProvider } from "@/components/theme-provider"
10+
import { Toaster } from "@/components/ui/toaster"
1011

1112
const fontSans = FontSans({
1213
subsets: ["latin"],
@@ -37,9 +38,10 @@ export default function RootLayout({ children }: RootLayoutProps) {
3738
</div>
3839
<TailwindIndicator />
3940
</ThemeProvider>
41+
<Analytics />
42+
<Toaster />
4043
</body>
4144
</html>
42-
<Analytics />
4345
</>
4446
)
4547
}

apps/www/components/examples/index.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ import { TextareaDisabled } from "@/components/examples/textarea/disabled"
4747
import { TextareaWithButton } from "@/components/examples/textarea/with-button"
4848
import { TextareaWithLabel } from "@/components/examples/textarea/with-label"
4949
import { TextareaWithText } from "@/components/examples/textarea/with-text"
50+
import { ToastDemo } from "@/components/examples/toast/demo"
51+
import { ToastDestructive } from "@/components/examples/toast/destructive"
52+
import { ToastSimple } from "@/components/examples/toast/simple"
53+
import { ToastWithAction } from "@/components/examples/toast/with-action"
54+
import { ToastWithTitle } from "@/components/examples/toast/with-title"
5055
import { ToggleDemo } from "@/components/examples/toggle/demo"
5156
import { ToggleDisabled } from "@/components/examples/toggle/disabled"
5257
import { ToggleLg } from "@/components/examples/toggle/lg"
@@ -119,6 +124,11 @@ export const examples = {
119124
TextareaWithButton,
120125
TextareaWithLabel,
121126
TextareaWithText,
127+
ToastDemo,
128+
ToastDestructive,
129+
ToastSimple,
130+
ToastWithTitle,
131+
ToastWithAction,
122132
TooltipDemo,
123133
TypographyBlockquote,
124134
TypographyDemo,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use client"
2+
3+
import { useToast } from "@/hooks/use-toast"
4+
5+
import { Button } from "@/components/ui/button"
6+
import { ToastAction } from "@/components/ui/toast"
7+
8+
export function ToastDemo() {
9+
const { toast } = useToast()
10+
11+
return (
12+
<Button
13+
variant="outline"
14+
onClick={() => {
15+
toast({
16+
title: "Scheduled: Catch up ",
17+
description: "Friday, February 10, 2023 at 5:57 PM",
18+
action: (
19+
<ToastAction altText="Goto schedule to undo">Undo</ToastAction>
20+
),
21+
})
22+
}}
23+
>
24+
Add to calendar
25+
</Button>
26+
)
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"use client"
2+
3+
import { useToast } from "@/hooks/use-toast"
4+
5+
import { Button } from "@/components/ui/button"
6+
import { ToastAction } from "@/components/ui/toast"
7+
8+
export function ToastDestructive() {
9+
const { toast } = useToast()
10+
11+
return (
12+
<Button
13+
variant="outline"
14+
onClick={() => {
15+
toast({
16+
variant: "destructive",
17+
title: "Uh oh! Something went wrong.",
18+
description: "There was a problem with your request.",
19+
action: <ToastAction altText="Try again">Try again</ToastAction>,
20+
})
21+
}}
22+
>
23+
Show Toast
24+
</Button>
25+
)
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client"
2+
3+
import { useToast } from "@/hooks/use-toast"
4+
5+
import { Button } from "@/components/ui/button"
6+
7+
export function ToastSimple() {
8+
const { toast } = useToast()
9+
10+
return (
11+
<Button
12+
variant="outline"
13+
onClick={() => {
14+
toast({
15+
description: "Your message has been sent.",
16+
})
17+
}}
18+
>
19+
Show Toast
20+
</Button>
21+
)
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use client"
2+
3+
import { useToast } from "@/hooks/use-toast"
4+
5+
import { Button } from "@/components/ui/button"
6+
import { ToastAction } from "@/components/ui/toast"
7+
8+
export function ToastWithAction() {
9+
const { toast } = useToast()
10+
11+
return (
12+
<Button
13+
variant="outline"
14+
onClick={() => {
15+
toast({
16+
title: "Uh oh! Something went wrong.",
17+
description: "There was a problem with your request.",
18+
action: <ToastAction altText="Try again">Try again</ToastAction>,
19+
})
20+
}}
21+
>
22+
Show Toast
23+
</Button>
24+
)
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use client"
2+
3+
import { useToast } from "@/hooks/use-toast"
4+
5+
import { Button } from "@/components/ui/button"
6+
7+
export function ToastWithTitle() {
8+
const { toast } = useToast()
9+
10+
return (
11+
<Button
12+
variant="outline"
13+
onClick={() => {
14+
toast({
15+
title: "Uh oh! Something went wrong.",
16+
description: "There was a problem with your request.",
17+
})
18+
}}
19+
>
20+
Show Toast
21+
</Button>
22+
)
23+
}

apps/www/components/ui/toast.tsx

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import * as React from "react"
2+
import * as ToastPrimitives from "@radix-ui/react-toast"
3+
import { VariantProps, cva } from "class-variance-authority"
4+
import { X } from "lucide-react"
5+
6+
import { cn } from "@/lib/utils"
7+
8+
const ToastProvider = ToastPrimitives.Provider
9+
10+
const ToastViewport = React.forwardRef<
11+
React.ElementRef<typeof ToastPrimitives.Viewport>,
12+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
13+
>(({ className, ...props }, ref) => (
14+
<ToastPrimitives.Viewport
15+
ref={ref}
16+
className={cn(
17+
"fixed top-0 z-50 flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:bottom-0 sm:right-0 sm:flex-col md:max-w-[420px]",
18+
className
19+
)}
20+
{...props}
21+
/>
22+
))
23+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24+
25+
const toastVariants = cva(
26+
"data-[swipe=move]:transition-none grow-1 group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full mt-4 data-[state=closed]:slide-out-to-right-full dark:border-slate-700 last:mt-0 sm:last:mt-4",
27+
{
28+
variants: {
29+
variant: {
30+
default:
31+
"bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700",
32+
destructive:
33+
"group destructive bg-red-600 text-white border-red-600 dark:border-red-600",
34+
},
35+
},
36+
defaultVariants: {
37+
variant: "default",
38+
},
39+
}
40+
)
41+
42+
const Toast = React.forwardRef<
43+
React.ElementRef<typeof ToastPrimitives.Root>,
44+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
45+
VariantProps<typeof toastVariants>
46+
>(({ className, variant, ...props }, ref) => {
47+
return (
48+
<ToastPrimitives.Root
49+
ref={ref}
50+
className={cn(toastVariants({ variant }), className)}
51+
{...props}
52+
/>
53+
)
54+
})
55+
Toast.displayName = ToastPrimitives.Root.displayName
56+
57+
const ToastAction = React.forwardRef<
58+
React.ElementRef<typeof ToastPrimitives.Action>,
59+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
60+
>(({ className, ...props }, ref) => (
61+
<ToastPrimitives.Action
62+
ref={ref}
63+
className={cn(
64+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border border-slate-200 bg-transparent px-3 text-sm font-medium transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-red-100 group-[.destructive]:hover:border-slate-50 group-[.destructive]:hover:bg-red-100 group-[.destructive]:hover:text-red-600 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:hover:text-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800",
65+
className
66+
)}
67+
{...props}
68+
/>
69+
))
70+
ToastAction.displayName = ToastPrimitives.Action.displayName
71+
72+
const ToastClose = React.forwardRef<
73+
React.ElementRef<typeof ToastPrimitives.Close>,
74+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
75+
>(({ className, ...props }, ref) => (
76+
<ToastPrimitives.Close
77+
ref={ref}
78+
className={cn(
79+
"absolute top-2 right-2 rounded-md p-1 text-slate-500 opacity-0 transition-opacity hover:text-slate-900 focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:hover:text-slate-50",
80+
className
81+
)}
82+
toast-close=""
83+
{...props}
84+
>
85+
<X className="h-4 w-4" />
86+
</ToastPrimitives.Close>
87+
))
88+
ToastClose.displayName = ToastPrimitives.Close.displayName
89+
90+
const ToastTitle = React.forwardRef<
91+
React.ElementRef<typeof ToastPrimitives.Title>,
92+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
93+
>(({ className, ...props }, ref) => (
94+
<ToastPrimitives.Title
95+
ref={ref}
96+
className={cn("text-sm font-semibold", className)}
97+
{...props}
98+
/>
99+
))
100+
ToastTitle.displayName = ToastPrimitives.Title.displayName
101+
102+
const ToastDescription = React.forwardRef<
103+
React.ElementRef<typeof ToastPrimitives.Description>,
104+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
105+
>(({ className, ...props }, ref) => (
106+
<ToastPrimitives.Description
107+
ref={ref}
108+
className={cn("text-sm opacity-90", className)}
109+
{...props}
110+
/>
111+
))
112+
ToastDescription.displayName = ToastPrimitives.Description.displayName
113+
114+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
115+
116+
type ToastActionElement = React.ReactElement<typeof ToastAction>
117+
118+
export {
119+
type ToastProps,
120+
type ToastActionElement,
121+
ToastProvider,
122+
ToastViewport,
123+
Toast,
124+
ToastTitle,
125+
ToastDescription,
126+
ToastClose,
127+
ToastAction,
128+
}

apps/www/components/ui/toaster.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"use client"
2+
3+
import { useToast } from "@/hooks/use-toast"
4+
5+
import {
6+
Toast,
7+
ToastClose,
8+
ToastDescription,
9+
ToastProvider,
10+
ToastTitle,
11+
ToastViewport,
12+
} from "@/components/ui/toast"
13+
14+
export function Toaster() {
15+
const { toasts } = useToast()
16+
17+
return (
18+
<ToastProvider>
19+
{toasts.map(function ({ id, title, description, action, ...props }) {
20+
return (
21+
<Toast key={id} {...props}>
22+
<div className="grid gap-1">
23+
{title && <ToastTitle>{title}</ToastTitle>}
24+
{description && (
25+
<ToastDescription>{description}</ToastDescription>
26+
)}
27+
</div>
28+
{action}
29+
<ToastClose />
30+
</Toast>
31+
)
32+
})}
33+
<ToastViewport />
34+
</ToastProvider>
35+
)
36+
}

apps/www/config/docs.ts

+5
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ export const docsConfig: DocsConfig = {
200200
{
201201
title: "Toggle",
202202
href: "/docs/primitives/toggle",
203+
items: [],
204+
},
205+
{
206+
title: "Toast",
207+
href: "/docs/primitives/toast",
203208
label: "New",
204209
items: [],
205210
},

0 commit comments

Comments
 (0)