68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
"use client";
|
||
|
||
// Toast-Basis-Komponente — SHL-01. WCAG: aria-live sorgt dafür, dass Screenreader Meldungen ansagen.
|
||
|
||
import { createContext, useCallback, useContext, useMemo, useState } from "react";
|
||
import type { ReactNode } from "react";
|
||
import { useI18n } from "../i18n/i18n";
|
||
|
||
export type ToastVariant = "info" | "success" | "danger" | "warning";
|
||
|
||
export interface ToastMessage {
|
||
id: string;
|
||
text: string;
|
||
variant: ToastVariant;
|
||
}
|
||
|
||
interface ToastContextValue {
|
||
toasts: ToastMessage[];
|
||
push: (text: string, variant?: ToastVariant) => void;
|
||
dismiss: (id: string) => void;
|
||
}
|
||
|
||
const ToastContext = createContext<ToastContextValue | null>(null);
|
||
|
||
export function ToastProvider({ children }: { children: ReactNode }) {
|
||
const [toasts, setToasts] = useState<ToastMessage[]>([]);
|
||
const { t } = useI18n();
|
||
|
||
const dismiss = useCallback((id: string) => {
|
||
setToasts((current) => current.filter((toast) => toast.id !== id));
|
||
}, []);
|
||
|
||
const push = useCallback((text: string, variant: ToastVariant = "info") => {
|
||
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||
setToasts((current) => [...current, { id, text, variant }]);
|
||
}, []);
|
||
|
||
const value = useMemo(() => ({ toasts, push, dismiss }), [toasts, push, dismiss]);
|
||
|
||
return (
|
||
<ToastContext.Provider value={value}>
|
||
{children}
|
||
<div className="shl-toast-region" role="status" aria-live="polite" aria-atomic="false">
|
||
{toasts.map((toast) => (
|
||
<div key={toast.id} className={`shl-toast shl-toast-${toast.variant}`}>
|
||
<span>{toast.text}</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => dismiss(toast.id)}
|
||
aria-label={t("shl.toast.dismiss")}
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</ToastContext.Provider>
|
||
);
|
||
}
|
||
|
||
export function useToast(): ToastContextValue {
|
||
const ctx = useContext(ToastContext);
|
||
if (!ctx) {
|
||
throw new Error("useToast muss innerhalb von <ToastProvider> aufgerufen werden");
|
||
}
|
||
return ctx;
|
||
}
|