116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { Toast as ToastPrimitive } from "@base-ui/react/toast"
|
|
import { XIcon } from "lucide-react"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
|
|
export const toastManager = ToastPrimitive.createToastManager()
|
|
|
|
type ToastType = "default" | "success" | "error" | "warning" | "info"
|
|
|
|
interface ToastData {
|
|
type?: ToastType
|
|
}
|
|
|
|
function ToastList() {
|
|
const { toasts } = ToastPrimitive.useToastManager<ToastData>()
|
|
|
|
return (
|
|
<ToastPrimitive.Viewport className="fixed top-4 right-4 z-9999 flex w-full max-w-sm flex-col gap-2 outline-none sm:top-6 sm:right-6">
|
|
{toasts.map((toast) => (
|
|
<ToastPrimitive.Root
|
|
key={toast.id}
|
|
toast={toast}
|
|
className={cn(
|
|
"group relative flex w-full flex-col gap-1 overflow-hidden rounded-xl px-4 py-3 text-sm shadow-lg ring-1 transition-all duration-200",
|
|
"data-[transitionstatus=starting]:-translate-y-2 data-[transitionstatus=starting]:opacity-0",
|
|
"data-[transitionstatus=ending]:translate-x-full data-[transitionstatus=ending]:opacity-0",
|
|
toast.data?.type === "success" &&
|
|
"bg-emerald-50 text-emerald-900 ring-emerald-200 dark:bg-emerald-950 dark:text-emerald-100 dark:ring-emerald-800",
|
|
toast.data?.type === "error" &&
|
|
"bg-red-50 text-red-900 ring-red-200 dark:bg-red-950 dark:text-red-100 dark:ring-red-800",
|
|
toast.data?.type === "warning" &&
|
|
"bg-amber-50 text-amber-900 ring-amber-200 dark:bg-amber-950 dark:text-amber-100 dark:ring-amber-800",
|
|
toast.data?.type === "info" &&
|
|
"bg-sky-50 text-sky-900 ring-sky-200 dark:bg-sky-950 dark:text-sky-100 dark:ring-sky-800",
|
|
(!toast.data?.type || toast.data.type === "default") &&
|
|
"bg-white text-foreground ring-foreground/10 dark:bg-neutral-900"
|
|
)}
|
|
>
|
|
<ToastPrimitive.Content className="flex items-start justify-between gap-3">
|
|
<div className="flex flex-col gap-0.5">
|
|
{toast.title && (
|
|
<ToastPrimitive.Title className="font-semibold leading-snug">
|
|
{toast.title}
|
|
</ToastPrimitive.Title>
|
|
)}
|
|
{toast.description && (
|
|
<ToastPrimitive.Description className="text-xs opacity-80">
|
|
{toast.description}
|
|
</ToastPrimitive.Description>
|
|
)}
|
|
</div>
|
|
<ToastPrimitive.Close className="mt-0.5 shrink-0 rounded-md p-1 opacity-60 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
<XIcon className="size-3.5" />
|
|
<span className="sr-only">Dismiss</span>
|
|
</ToastPrimitive.Close>
|
|
</ToastPrimitive.Content>
|
|
|
|
{toast.actionProps && (
|
|
<ToastPrimitive.Action
|
|
{...toast.actionProps}
|
|
className="mt-1 self-start rounded-md px-2 py-1 text-xs font-medium underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
/>
|
|
)}
|
|
</ToastPrimitive.Root>
|
|
))}
|
|
</ToastPrimitive.Viewport>
|
|
)
|
|
}
|
|
|
|
function Toaster() {
|
|
return (
|
|
<ToastPrimitive.Provider toastManager={toastManager}>
|
|
<ToastList />
|
|
</ToastPrimitive.Provider>
|
|
)
|
|
}
|
|
|
|
function toast(options: {
|
|
title?: string
|
|
description?: string
|
|
type?: ToastType
|
|
timeout?: number
|
|
actionLabel?: string
|
|
onAction?: () => void
|
|
}) {
|
|
toastManager.add({
|
|
title: options.title,
|
|
description: options.description,
|
|
timeout: options.timeout ?? 4000,
|
|
data: { type: options.type ?? "default" },
|
|
...(options.actionLabel && {
|
|
actionProps: {
|
|
children: options.actionLabel,
|
|
onClick: options.onAction,
|
|
},
|
|
}),
|
|
})
|
|
}
|
|
|
|
toast.success = (title: string, description?: string) =>
|
|
toast({ title, description, type: "success" })
|
|
|
|
toast.error = (title: string, description?: string) =>
|
|
toast({ title, description, type: "error" })
|
|
|
|
toast.warning = (title: string, description?: string) =>
|
|
toast({ title, description, type: "warning" })
|
|
|
|
toast.info = (title: string, description?: string) =>
|
|
toast({ title, description, type: "info" })
|
|
|
|
export { Toaster, toast }
|