import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; /** * Tipos de tema */ type Theme = 'light' | 'dark' | 'system'; /** * Tipo de notificación */ interface Notification { id: string; type: 'success' | 'error' | 'warning' | 'info'; title: string; message?: string; duration?: number; action?: { label: string; onClick: () => void; }; } /** * Modal state */ interface ModalState { isOpen: boolean; type: string | null; data?: unknown; } /** * Estado del store de UI */ interface UIState { // Sidebar sidebarOpen: boolean; sidebarCollapsed: boolean; // Theme theme: Theme; resolvedTheme: 'light' | 'dark'; // Notifications notifications: Notification[]; // Modal modal: ModalState; // Loading states globalLoading: boolean; loadingMessage: string | null; // Mobile isMobile: boolean; // Acciones Sidebar toggleSidebar: () => void; setSidebarOpen: (open: boolean) => void; toggleSidebarCollapsed: () => void; setSidebarCollapsed: (collapsed: boolean) => void; // Acciones Theme setTheme: (theme: Theme) => void; toggleTheme: () => void; // Acciones Notifications addNotification: (notification: Omit) => void; removeNotification: (id: string) => void; clearNotifications: () => void; // Acciones Modal openModal: (type: string, data?: unknown) => void; closeModal: () => void; // Acciones Loading setGlobalLoading: (loading: boolean, message?: string) => void; // Acciones Mobile setIsMobile: (isMobile: boolean) => void; } /** * Genera un ID único para notificaciones */ function generateNotificationId(): string { return `notification-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; } /** * Detecta el tema del sistema */ function getSystemTheme(): 'light' | 'dark' { if (typeof window === 'undefined') return 'dark'; return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } /** * Store de UI con persistencia parcial */ export const useUIStore = create()( persist( (set, get) => ({ // Estado inicial sidebarOpen: true, sidebarCollapsed: false, theme: 'dark', resolvedTheme: 'dark', notifications: [], modal: { isOpen: false, type: null, data: undefined, }, globalLoading: false, loadingMessage: null, isMobile: false, // Sidebar toggleSidebar: () => { set((state) => ({ sidebarOpen: !state.sidebarOpen })); }, setSidebarOpen: (open: boolean) => { set({ sidebarOpen: open }); }, toggleSidebarCollapsed: () => { set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })); }, setSidebarCollapsed: (collapsed: boolean) => { set({ sidebarCollapsed: collapsed }); }, // Theme setTheme: (theme: Theme) => { const resolvedTheme = theme === 'system' ? getSystemTheme() : theme; // Aplicar clase al documento if (typeof document !== 'undefined') { document.documentElement.classList.remove('light', 'dark'); document.documentElement.classList.add(resolvedTheme); } set({ theme, resolvedTheme }); }, toggleTheme: () => { const { theme } = get(); const newTheme = theme === 'dark' ? 'light' : 'dark'; get().setTheme(newTheme); }, // Notifications addNotification: (notification) => { const id = generateNotificationId(); const newNotification: Notification = { ...notification, id, duration: notification.duration ?? 5000, }; set((state) => ({ notifications: [...state.notifications, newNotification], })); // Auto-remove after duration if (newNotification.duration && newNotification.duration > 0) { setTimeout(() => { get().removeNotification(id); }, newNotification.duration); } }, removeNotification: (id: string) => { set((state) => ({ notifications: state.notifications.filter((n) => n.id !== id), })); }, clearNotifications: () => { set({ notifications: [] }); }, // Modal openModal: (type: string, data?: unknown) => { set({ modal: { isOpen: true, type, data, }, }); }, closeModal: () => { set({ modal: { isOpen: false, type: null, data: undefined, }, }); }, // Loading setGlobalLoading: (loading: boolean, message?: string) => { set({ globalLoading: loading, loadingMessage: loading ? (message || null) : null, }); }, // Mobile setIsMobile: (isMobile: boolean) => { set({ isMobile }); // Cerrar sidebar en mobile if (isMobile) { set({ sidebarOpen: false }); } }, }), { name: 'horux-ui-storage', storage: createJSONStorage(() => localStorage), partialize: (state) => ({ theme: state.theme, sidebarCollapsed: state.sidebarCollapsed, }), onRehydrateStorage: () => (state) => { // Aplicar tema al rehidratar if (state) { const resolvedTheme = state.theme === 'system' ? getSystemTheme() : state.theme; if (typeof document !== 'undefined') { document.documentElement.classList.remove('light', 'dark'); document.documentElement.classList.add(resolvedTheme); } state.resolvedTheme = resolvedTheme; } }, } ) ); /** * Hook helper para notificaciones */ export const useNotifications = () => { const { addNotification, removeNotification, clearNotifications, notifications } = useUIStore(); return { notifications, notify: addNotification, remove: removeNotification, clear: clearNotifications, success: (title: string, message?: string) => addNotification({ type: 'success', title, message }), error: (title: string, message?: string) => addNotification({ type: 'error', title, message }), warning: (title: string, message?: string) => addNotification({ type: 'warning', title, message }), info: (title: string, message?: string) => addNotification({ type: 'info', title, message }), }; }; /** * Hook helper para modales */ export const useModal = () => { const { modal, openModal, closeModal } = useUIStore(); return { ...modal, open: openModal, close: closeModal, }; }; /** * Hook helper para el tema */ export const useTheme = () => { const { theme, resolvedTheme, setTheme, toggleTheme } = useUIStore(); return { theme, resolvedTheme, setTheme, toggleTheme, isDark: resolvedTheme === 'dark', isLight: resolvedTheme === 'light', }; };