33 changed files with 127 additions and 477 deletions
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
// import { useTheme } from "next-themes"
|
||||
import { useTheme } from '@/providers/ThemeProvider' |
||||
import { Toaster as Sonner } from 'sonner' |
||||
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner> |
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => { |
||||
// const { theme = "system" } = useTheme()
|
||||
const { themeSetting } = useTheme() |
||||
|
||||
return ( |
||||
<Sonner |
||||
theme={themeSetting} |
||||
className="toaster group" |
||||
richColors |
||||
mobileOffset={64} |
||||
toastOptions={{ |
||||
classNames: { |
||||
toast: |
||||
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg', |
||||
description: 'group-[.toast]:text-muted-foreground', |
||||
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground', |
||||
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground' |
||||
} |
||||
}} |
||||
{...props} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
export { Toaster } |
||||
@ -1,126 +0,0 @@
@@ -1,126 +0,0 @@
|
||||
import * as React from 'react' |
||||
import * as ToastPrimitives from '@radix-ui/react-toast' |
||||
import { cva, type VariantProps } from 'class-variance-authority' |
||||
import { X } from 'lucide-react' |
||||
|
||||
import { cn } from '@/lib/utils' |
||||
|
||||
const ToastProvider = ToastPrimitives.Provider |
||||
|
||||
const ToastViewport = React.forwardRef< |
||||
React.ElementRef<typeof ToastPrimitives.Viewport>, |
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> |
||||
>(({ className, ...props }, ref) => ( |
||||
<ToastPrimitives.Viewport |
||||
ref={ref} |
||||
className={cn( |
||||
'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]', |
||||
className |
||||
)} |
||||
{...props} |
||||
/> |
||||
)) |
||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName |
||||
|
||||
const toastVariants = cva( |
||||
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border 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', |
||||
{ |
||||
variants: { |
||||
variant: { |
||||
default: 'border bg-background text-foreground', |
||||
destructive: |
||||
'destructive group border-destructive bg-destructive text-destructive-foreground' |
||||
} |
||||
}, |
||||
defaultVariants: { |
||||
variant: 'default' |
||||
} |
||||
} |
||||
) |
||||
|
||||
const Toast = React.forwardRef< |
||||
React.ElementRef<typeof ToastPrimitives.Root>, |
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants> |
||||
>(({ className, variant, ...props }, ref) => { |
||||
return ( |
||||
<ToastPrimitives.Root |
||||
ref={ref} |
||||
className={cn(toastVariants({ variant }), className)} |
||||
{...props} |
||||
/> |
||||
) |
||||
}) |
||||
Toast.displayName = ToastPrimitives.Root.displayName |
||||
|
||||
const ToastAction = React.forwardRef< |
||||
React.ElementRef<typeof ToastPrimitives.Action>, |
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> |
||||
>(({ className, ...props }, ref) => ( |
||||
<ToastPrimitives.Action |
||||
ref={ref} |
||||
className={cn( |
||||
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring 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', |
||||
className |
||||
)} |
||||
{...props} |
||||
/> |
||||
)) |
||||
ToastAction.displayName = ToastPrimitives.Action.displayName |
||||
|
||||
const ToastClose = React.forwardRef< |
||||
React.ElementRef<typeof ToastPrimitives.Close>, |
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> |
||||
>(({ className, ...props }, ref) => ( |
||||
<ToastPrimitives.Close |
||||
ref={ref} |
||||
className={cn( |
||||
'absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground 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', |
||||
className |
||||
)} |
||||
toast-close="" |
||||
{...props} |
||||
> |
||||
<X className="h-4 w-4" /> |
||||
</ToastPrimitives.Close> |
||||
)) |
||||
ToastClose.displayName = ToastPrimitives.Close.displayName |
||||
|
||||
const ToastTitle = React.forwardRef< |
||||
React.ElementRef<typeof ToastPrimitives.Title>, |
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title> |
||||
>(({ className, ...props }, ref) => ( |
||||
<ToastPrimitives.Title |
||||
ref={ref} |
||||
className={cn('text-sm font-semibold [&+div]:text-xs', className)} |
||||
{...props} |
||||
/> |
||||
)) |
||||
ToastTitle.displayName = ToastPrimitives.Title.displayName |
||||
|
||||
const ToastDescription = React.forwardRef< |
||||
React.ElementRef<typeof ToastPrimitives.Description>, |
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description> |
||||
>(({ className, ...props }, ref) => ( |
||||
<ToastPrimitives.Description |
||||
ref={ref} |
||||
className={cn('text-sm opacity-90', className)} |
||||
{...props} |
||||
/> |
||||
)) |
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName |
||||
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast> |
||||
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction> |
||||
|
||||
export { |
||||
type ToastProps, |
||||
type ToastActionElement, |
||||
ToastProvider, |
||||
ToastViewport, |
||||
Toast, |
||||
ToastTitle, |
||||
ToastDescription, |
||||
ToastClose, |
||||
ToastAction |
||||
} |
||||
@ -1,31 +0,0 @@
@@ -1,31 +0,0 @@
|
||||
import { useToast } from '@/hooks/use-toast' |
||||
import { |
||||
Toast, |
||||
ToastClose, |
||||
ToastDescription, |
||||
ToastProvider, |
||||
ToastTitle, |
||||
ToastViewport |
||||
} from '@/components/ui/toast' |
||||
|
||||
export function Toaster() { |
||||
const { toasts } = useToast() |
||||
|
||||
return ( |
||||
<ToastProvider> |
||||
{toasts.map(function ({ id, title, description, action, ...props }) { |
||||
return ( |
||||
<Toast key={id} {...props}> |
||||
<div className="grid gap-1"> |
||||
{title && <ToastTitle>{title}</ToastTitle>} |
||||
{description && <ToastDescription>{description}</ToastDescription>} |
||||
</div> |
||||
{action} |
||||
<ToastClose /> |
||||
</Toast> |
||||
) |
||||
})} |
||||
<ToastViewport /> |
||||
</ToastProvider> |
||||
) |
||||
} |
||||
@ -1,189 +0,0 @@
@@ -1,189 +0,0 @@
|
||||
'use client' |
||||
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from 'react' |
||||
|
||||
import type { ToastActionElement, ToastProps } from '@/components/ui/toast' |
||||
|
||||
const TOAST_LIMIT = 1 |
||||
const TOAST_REMOVE_DELAY = 1000000 |
||||
|
||||
type ToasterToast = ToastProps & { |
||||
id: string |
||||
title?: React.ReactNode |
||||
description?: React.ReactNode |
||||
action?: ToastActionElement |
||||
} |
||||
|
||||
const actionTypes = { |
||||
ADD_TOAST: 'ADD_TOAST', |
||||
UPDATE_TOAST: 'UPDATE_TOAST', |
||||
DISMISS_TOAST: 'DISMISS_TOAST', |
||||
REMOVE_TOAST: 'REMOVE_TOAST' |
||||
} as const |
||||
|
||||
let count = 0 |
||||
|
||||
function genId() { |
||||
count = (count + 1) % Number.MAX_SAFE_INTEGER |
||||
return count.toString() |
||||
} |
||||
|
||||
type ActionType = typeof actionTypes |
||||
|
||||
type Action = |
||||
| { |
||||
type: ActionType['ADD_TOAST'] |
||||
toast: ToasterToast |
||||
} |
||||
| { |
||||
type: ActionType['UPDATE_TOAST'] |
||||
toast: Partial<ToasterToast> |
||||
} |
||||
| { |
||||
type: ActionType['DISMISS_TOAST'] |
||||
toastId?: ToasterToast['id'] |
||||
} |
||||
| { |
||||
type: ActionType['REMOVE_TOAST'] |
||||
toastId?: ToasterToast['id'] |
||||
} |
||||
|
||||
interface State { |
||||
toasts: ToasterToast[] |
||||
} |
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() |
||||
|
||||
const addToRemoveQueue = (toastId: string) => { |
||||
if (toastTimeouts.has(toastId)) { |
||||
return |
||||
} |
||||
|
||||
const timeout = setTimeout(() => { |
||||
toastTimeouts.delete(toastId) |
||||
dispatch({ |
||||
type: 'REMOVE_TOAST', |
||||
toastId: toastId |
||||
}) |
||||
}, TOAST_REMOVE_DELAY) |
||||
|
||||
toastTimeouts.set(toastId, timeout) |
||||
} |
||||
|
||||
export const reducer = (state: State, action: Action): State => { |
||||
switch (action.type) { |
||||
case 'ADD_TOAST': |
||||
return { |
||||
...state, |
||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT) |
||||
} |
||||
|
||||
case 'UPDATE_TOAST': |
||||
return { |
||||
...state, |
||||
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)) |
||||
} |
||||
|
||||
case 'DISMISS_TOAST': { |
||||
const { toastId } = action |
||||
|
||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||
// but I'll keep it here for simplicity
|
||||
if (toastId) { |
||||
addToRemoveQueue(toastId) |
||||
} else { |
||||
state.toasts.forEach((toast) => { |
||||
addToRemoveQueue(toast.id) |
||||
}) |
||||
} |
||||
|
||||
return { |
||||
...state, |
||||
toasts: state.toasts.map((t) => |
||||
t.id === toastId || toastId === undefined |
||||
? { |
||||
...t, |
||||
open: false |
||||
} |
||||
: t |
||||
) |
||||
} |
||||
} |
||||
case 'REMOVE_TOAST': |
||||
if (action.toastId === undefined) { |
||||
return { |
||||
...state, |
||||
toasts: [] |
||||
} |
||||
} |
||||
return { |
||||
...state, |
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId) |
||||
} |
||||
} |
||||
} |
||||
|
||||
const listeners: Array<(state: State) => void> = [] |
||||
|
||||
let memoryState: State = { toasts: [] } |
||||
|
||||
function dispatch(action: Action) { |
||||
memoryState = reducer(memoryState, action) |
||||
listeners.forEach((listener) => { |
||||
listener(memoryState) |
||||
}) |
||||
} |
||||
|
||||
type Toast = Omit<ToasterToast, 'id'> |
||||
|
||||
function toast({ ...props }: Toast) { |
||||
const id = genId() |
||||
|
||||
const update = (props: ToasterToast) => |
||||
dispatch({ |
||||
type: 'UPDATE_TOAST', |
||||
toast: { ...props, id } |
||||
}) |
||||
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id }) |
||||
|
||||
dispatch({ |
||||
type: 'ADD_TOAST', |
||||
toast: { |
||||
...props, |
||||
id, |
||||
open: true, |
||||
onOpenChange: (open) => { |
||||
if (!open) dismiss() |
||||
} |
||||
} |
||||
}) |
||||
|
||||
return { |
||||
id: id, |
||||
dismiss, |
||||
update |
||||
} |
||||
} |
||||
|
||||
function useToast() { |
||||
const [state, setState] = React.useState<State>(memoryState) |
||||
|
||||
React.useEffect(() => { |
||||
listeners.push(setState) |
||||
return () => { |
||||
const index = listeners.indexOf(setState) |
||||
if (index > -1) { |
||||
listeners.splice(index, 1) |
||||
} |
||||
} |
||||
}, [state]) |
||||
|
||||
return { |
||||
...state, |
||||
toast, |
||||
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }) |
||||
} |
||||
} |
||||
|
||||
export { useToast, toast } |
||||
Loading…
Reference in new issue