46 lines
1.6 KiB
TypeScript
46 lines
1.6 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
||
import { useToast } from '@/hooks/useToast';
|
||
|
||
export function ToastViewport(): JSX.Element {
|
||
const { t } = useTranslation();
|
||
const { toasts, dismiss } = useToast();
|
||
return (
|
||
<div
|
||
aria-live="polite"
|
||
aria-atomic="true"
|
||
className="fixed bottom-xl right-xl z-50 flex flex-col gap-sm w-[320px] pointer-events-none"
|
||
>
|
||
{toasts.map((toast) => {
|
||
const isError = toast.kind === 'error';
|
||
const isSuccess = toast.kind === 'success';
|
||
const surface = isError
|
||
? 'bg-paper text-ink border border-hairline border-l-4 border-l-bloom-deep'
|
||
: isSuccess
|
||
? 'bg-paper text-ink border border-hairline border-l-4 border-l-success'
|
||
: 'bg-paper text-ink border border-hairline border-l-4 border-l-primary';
|
||
return (
|
||
<div
|
||
key={toast.id}
|
||
role="status"
|
||
data-testid="toast"
|
||
data-kind={toast.kind}
|
||
className={`pointer-events-auto rounded-none px-md py-sm text-[14px] leading-[1.4] ${surface}`}
|
||
>
|
||
<div className="flex items-start justify-between gap-sm">
|
||
<span className="flex-1">{toast.message}</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => dismiss(toast.id)}
|
||
aria-label={t('toast.dismiss')}
|
||
className="text-current opacity-70 hover:opacity-100"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
);
|
||
}
|