- Add NavigationRail component (desktop vertical icon sidebar + mobile bottom tab bar) - Add ConfirmDialog with promise-based useConfirmDialog hook for async confirmation flow - Add WelcomeBanner onboarding component (one-time display, localStorage persistence) - Polish login form UX (shake on error, TOTP slide animation, password visibility toggle, session expired banner) - Add inline form validation with shake animation in email composer and contacts - Add empty state patterns for contacts (no data vs no search results with contextual actions) - Improve toast notification system with undo action support and typed durations - Add WCAG AA prefers-reduced-motion media query, safe area insets, sr-only live regions - Add template settings tab and keyboard shortcut integration - Update all 8 locale translations
61 lines
1.6 KiB
TypeScript
61 lines
1.6 KiB
TypeScript
import { create } from "zustand";
|
|
import { Toast, ToastAction } from "@/components/ui/toast";
|
|
|
|
interface ToastStore {
|
|
toasts: Toast[];
|
|
addToast: (toast: Omit<Toast, "id">) => void;
|
|
removeToast: (id: string) => void;
|
|
clearToasts: () => void;
|
|
}
|
|
|
|
export const useToastStore = create<ToastStore>((set) => ({
|
|
toasts: [],
|
|
|
|
addToast: (toast) => {
|
|
const id = Math.random().toString(36).substring(2, 11);
|
|
const newToast: Toast = {
|
|
...toast,
|
|
id,
|
|
duration: toast.duration ?? 5000,
|
|
};
|
|
|
|
set((state) => ({
|
|
toasts: [...state.toasts, newToast],
|
|
}));
|
|
},
|
|
|
|
removeToast: (id) => {
|
|
set((state) => ({
|
|
toasts: state.toasts.filter((toast) => toast.id !== id),
|
|
}));
|
|
},
|
|
|
|
clearToasts: () => {
|
|
set({ toasts: [] });
|
|
},
|
|
}));
|
|
|
|
interface ToastOptions {
|
|
message?: string;
|
|
action?: ToastAction;
|
|
duration?: number;
|
|
}
|
|
|
|
function showToast(type: Toast["type"], title: string, options?: string | ToastOptions, defaultDuration?: number): void {
|
|
const opts = typeof options === "string" ? { message: options } : options;
|
|
useToastStore.getState().addToast({
|
|
type,
|
|
title,
|
|
message: opts?.message,
|
|
action: opts?.action,
|
|
duration: opts?.duration ?? defaultDuration,
|
|
});
|
|
}
|
|
|
|
export const toast = {
|
|
success: (title: string, options?: string | ToastOptions) => showToast("success", title, options),
|
|
error: (title: string, options?: string | ToastOptions) => showToast("error", title, options, 10000),
|
|
info: (title: string, options?: string | ToastOptions) => showToast("info", title, options),
|
|
warning: (title: string, options?: string | ToastOptions) => showToast("warning", title, options),
|
|
};
|