toast.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. "use client";
  2. import * as React from "react";
  3. import * as ToastPrimitives from "@radix-ui/react-toast";
  4. import { cva, type VariantProps } from "class-variance-authority";
  5. import { X } from "lucide-react";
  6. import { cn } from "@/lib/utils";
  7. const ToastProvider = ToastPrimitives.Provider;
  8. const ToastViewport = React.forwardRef<
  9. React.ElementRef<typeof ToastPrimitives.Viewport>,
  10. React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
  11. >(({ className, ...props }, ref) => (
  12. <ToastPrimitives.Viewport
  13. ref={ref}
  14. className={cn(
  15. "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]",
  16. className
  17. )}
  18. {...props}
  19. />
  20. ));
  21. ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
  22. const toastVariants = cva(
  23. "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-lg border p-4 pr-8 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",
  24. {
  25. variants: {
  26. variant: {
  27. default: "border bg-background text-foreground",
  28. success:
  29. "border-green-200 bg-green-50 text-green-900 dark:border-green-900 dark:bg-green-950 dark:text-green-100",
  30. destructive:
  31. "destructive group border-destructive bg-destructive text-destructive-foreground",
  32. },
  33. },
  34. defaultVariants: {
  35. variant: "default",
  36. },
  37. }
  38. );
  39. const Toast = React.forwardRef<
  40. React.ElementRef<typeof ToastPrimitives.Root>,
  41. React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
  42. VariantProps<typeof toastVariants>
  43. >(({ className, variant, ...props }, ref) => {
  44. return (
  45. <ToastPrimitives.Root
  46. ref={ref}
  47. className={cn(toastVariants({ variant }), className)}
  48. {...props}
  49. />
  50. );
  51. });
  52. Toast.displayName = ToastPrimitives.Root.displayName;
  53. const ToastAction = React.forwardRef<
  54. React.ElementRef<typeof ToastPrimitives.Action>,
  55. React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
  56. >(({ className, ...props }, ref) => (
  57. <ToastPrimitives.Action
  58. ref={ref}
  59. className={cn(
  60. "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
  61. className
  62. )}
  63. {...props}
  64. />
  65. ));
  66. ToastAction.displayName = ToastPrimitives.Action.displayName;
  67. const ToastClose = React.forwardRef<
  68. React.ElementRef<typeof ToastPrimitives.Close>,
  69. React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
  70. >(({ className, ...props }, ref) => (
  71. <ToastPrimitives.Close
  72. ref={ref}
  73. className={cn(
  74. "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground 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",
  75. className
  76. )}
  77. toast-close=""
  78. {...props}
  79. >
  80. <X className="h-4 w-4" />
  81. </ToastPrimitives.Close>
  82. ));
  83. ToastClose.displayName = ToastPrimitives.Close.displayName;
  84. const ToastTitle = React.forwardRef<
  85. React.ElementRef<typeof ToastPrimitives.Title>,
  86. React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
  87. >(({ className, ...props }, ref) => (
  88. <ToastPrimitives.Title
  89. ref={ref}
  90. className={cn("text-sm font-semibold", className)}
  91. {...props}
  92. />
  93. ));
  94. ToastTitle.displayName = ToastPrimitives.Title.displayName;
  95. const ToastDescription = React.forwardRef<
  96. React.ElementRef<typeof ToastPrimitives.Description>,
  97. React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
  98. >(({ className, ...props }, ref) => (
  99. <ToastPrimitives.Description
  100. ref={ref}
  101. className={cn("text-sm opacity-90", className)}
  102. {...props}
  103. />
  104. ));
  105. ToastDescription.displayName = ToastPrimitives.Description.displayName;
  106. type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
  107. type ToastActionElement = React.ReactElement<typeof ToastAction>;
  108. export {
  109. type ToastProps,
  110. type ToastActionElement,
  111. ToastProvider,
  112. ToastViewport,
  113. Toast,
  114. ToastTitle,
  115. ToastDescription,
  116. ToastClose,
  117. ToastAction,
  118. };