headplane/app/components/ToastProvider.tsx
2025-01-28 16:06:41 -05:00

88 lines
2.1 KiB
TypeScript

import {
AriaToastProps,
AriaToastRegionProps,
useToast,
useToastRegion,
} from '@react-aria/toast';
import { ToastQueue, ToastState, useToastQueue } from '@react-stately/toast';
import { X } from 'lucide-react';
import React, { useRef } from 'react';
import IconButton from '~/components/IconButton';
import cn from '~/utils/cn';
interface ToastProps extends AriaToastProps<React.ReactNode> {
state: ToastState<React.ReactNode>;
}
function Toast({ state, ...props }: ToastProps) {
const ref = useRef<HTMLDivElement | null>(null);
const { toastProps, contentProps, titleProps, closeButtonProps } = useToast(
props,
state,
ref,
);
return (
<div
{...toastProps}
ref={ref}
className={cn(
'flex items-center justify-between gap-x-3 pl-4 pr-3',
'text-white shadow-lg dark:shadow-md rounded-xl py-3',
'bg-headplane-900 dark:bg-headplane-950',
)}
>
<div {...contentProps} className="flex flex-col gap-2">
<div {...titleProps}>{props.toast.content}</div>
</div>
<IconButton
{...closeButtonProps}
label="Close"
className={cn(
'bg-transparent hover:bg-headplane-700',
'dark:bg-transparent dark:hover:bg-headplane-800',
)}
>
<X className="p-1" />
</IconButton>
</div>
);
}
interface ToastRegionProps extends AriaToastRegionProps {
state: ToastState<React.ReactNode>;
}
function ToastRegion({ state, ...props }: ToastRegionProps) {
const ref = useRef<HTMLDivElement | null>(null);
const { regionProps } = useToastRegion(props, state, ref);
return (
<div
{...regionProps}
ref={ref}
className={cn('fixed bottom-20 right-4', 'flex flex-col gap-4')}
>
{state.visibleToasts.map((toast) => (
<Toast key={toast.key} toast={toast} state={state} />
))}
</div>
);
}
export interface ToastProviderProps extends AriaToastRegionProps {
queue: ToastQueue<React.ReactNode>;
}
export default function ToastProvider({ queue, ...props }: ToastProviderProps) {
const state = useToastQueue(queue);
return (
<>
{state.visibleToasts.length > 0 && (
<ToastRegion {...props} state={state} />
)}
</>
);
}