/ src / components / ui / toast.tsx
toast.tsx
  1  import { Cross2Icon } from "@radix-ui/react-icons";
  2  import * as ToastPrimitives from "@radix-ui/react-toast";
  3  import { cva, type VariantProps } from "class-variance-authority";
  4  import * as React from "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-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto 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    "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-slate-200 p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full dark:border-slate-800",
 27    {
 28      variants: {
 29        variant: {
 30          default:
 31            "border bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50",
 32          destructive:
 33            "destructive group border-red-500 bg-red-500 text-slate-50 dark:border-red-900 dark:bg-red-900 dark:text-slate-50",
 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-1 focus:ring-slate-950 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-slate-100/40 group-[.destructive]:hover:border-red-500/30 group-[.destructive]:hover:bg-red-500 group-[.destructive]:hover:text-slate-50 group-[.destructive]:focus:ring-red-500 dark:border-slate-800 dark:hover:bg-slate-800 dark:focus:ring-slate-300 dark:group-[.destructive]:border-slate-800/40 dark:group-[.destructive]:hover:border-red-900/30 dark:group-[.destructive]:hover:bg-red-900 dark:group-[.destructive]:hover:text-slate-50 dark:group-[.destructive]:focus:ring-red-900",
 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 right-1 top-1 rounded-md p-1 text-slate-950/50 opacity-0 transition-opacity hover:text-slate-950 focus:opacity-100 focus:outline-none focus:ring-1 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:text-slate-50/50 dark:hover:text-slate-50",
 80        className,
 81      )}
 82      toast-close=""
 83      {...props}
 84    >
 85      <Cross2Icon 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 [&+div]:text-xs", 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    Toast,
120    ToastAction,
121    ToastClose,
122    ToastDescription,
123    ToastProvider,
124    ToastTitle,
125    ToastViewport,
126    type ToastActionElement,
127    type ToastProps,
128  };