import { XIcon } from '@primer/octicons-react'; import { type AriaToastProps, useToast, useToastRegion } from '@react-aria/toast'; import { ToastQueue, type ToastState, useToastQueue } from '@react-stately/toast'; import { type ReactNode, useRef } from 'react'; import { Button } from 'react-aria-components'; import { createPortal } from 'react-dom'; import { ClientOnly } from 'remix-utils/client-only'; import { cn } from '~/utils/cn'; type ToastProps = AriaToastProps & { readonly state: ToastState; }; function Toast({ state, ...properties }: ToastProps) { const reference = useRef(null); // @ts-expect-error: RefObject doesn't map to FocusableElement? const { toastProps, titleProps, closeButtonProps } = useToast( properties, state, reference, ); return (
{properties.toast.content}
); } const toasts = new ToastQueue({ maxVisibleToasts: 5, }); export function toast(text: string) { return toasts.add(text, { timeout: 5000 }); } export function Toaster() { const reference = useRef(null); const state = useToastQueue(toasts); // @ts-expect-error: React 19 has weird types for Portal vs Node const { regionProps } = useToastRegion({}, state, reference); return ( { // @ts-expect-error: Portal doesn't match Node in React 19 yet () => createPortal( state.visibleToasts.length >= 0 ? (
{state.visibleToasts.map((toast) => ( ))}
) : undefined, document.body, ) }
); }