Files
2026-07-08 11:08:24 +05:30

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 }