Files
bakery-ia/frontend/src/stores/ui.store.ts
2025-10-30 21:08:07 +01:00

357 lines
9.7 KiB
TypeScript

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
export type Theme = 'light' | 'dark' | 'auto';
export type Language = 'es' | 'en' | 'eu';
export type ViewMode = 'list' | 'grid' | 'card';
export type SidebarState = 'expanded' | 'collapsed' | 'hidden';
// Toast interface kept for backward compatibility but toast functionality
// has been moved to src/utils/toast.ts using react-hot-toast
// This interface is deprecated and will be removed in a future version
export interface Toast {
id: string;
type: 'success' | 'error' | 'warning' | 'info';
title: string;
message?: string;
duration?: number;
action?: {
label: string;
onClick: () => void;
};
persistent?: boolean;
}
export interface Modal {
id: string;
type: 'dialog' | 'drawer' | 'fullscreen';
title: string;
content: React.ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl';
closeable?: boolean;
onClose?: () => void;
}
export interface UIState {
// Theme & Appearance
theme: Theme;
language: Language;
sidebarState: SidebarState;
compactMode: boolean;
reducedMotion: boolean;
// Layout & Navigation
currentPage: string;
breadcrumbs: Array<{ label: string; path: string }>;
viewMode: ViewMode;
// Loading States
globalLoading: boolean;
loadingStates: Record<string, boolean>;
// Modals & Dialogs
modals: Modal[];
// User Preferences
preferences: {
showTips: boolean;
autoSave: boolean;
confirmActions: boolean;
defaultPageSize: number;
dateFormat: string;
numberFormat: string;
timezone: string;
};
// Actions
setTheme: (theme: Theme) => void;
setLanguage: (language: Language) => void;
setSidebarState: (state: SidebarState) => void;
setCompactMode: (compact: boolean) => void;
setReducedMotion: (reduced: boolean) => void;
setCurrentPage: (page: string) => void;
setBreadcrumbs: (breadcrumbs: Array<{ label: string; path: string }>) => void;
setViewMode: (mode: ViewMode) => void;
setGlobalLoading: (loading: boolean) => void;
setLoading: (key: string, loading: boolean) => void;
isLoading: (key: string) => boolean;
showModal: (modal: Omit<Modal, 'id'>) => string;
hideModal: (id: string) => void;
clearModals: () => void;
updatePreference: <K extends keyof UIState['preferences']>(
key: K,
value: UIState['preferences'][K]
) => void;
resetPreferences: () => void;
}
const defaultPreferences = {
showTips: true,
autoSave: true,
confirmActions: true,
defaultPageSize: 25,
dateFormat: 'DD/MM/YYYY',
numberFormat: 'european', // european, american
timezone: 'Europe/Madrid',
};
export const useUIStore = create<UIState>()(
persist(
(set, get) => ({
// Initial state
theme: 'dark',
language: 'es',
sidebarState: 'expanded',
compactMode: false,
reducedMotion: false,
currentPage: '',
breadcrumbs: [],
viewMode: 'list',
globalLoading: false,
loadingStates: {},
modals: [],
preferences: defaultPreferences,
// Theme & Appearance actions
setTheme: (theme: Theme) => {
set({ theme });
// Apply theme to document
const root = document.documentElement;
if (theme === 'dark') {
root.classList.add('dark');
root.classList.remove('light');
} else if (theme === 'light') {
root.classList.add('light');
root.classList.remove('dark');
} else {
// Auto theme - check system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark) {
root.classList.add('dark');
root.classList.remove('light');
} else {
root.classList.add('light');
root.classList.remove('dark');
}
}
},
setLanguage: (language: Language) => {
set({ language });
// Trigger i18n language change only if different
import('../i18n').then(({ default: i18n }) => {
if (i18n.language !== language) {
i18n.changeLanguage(language);
}
});
},
setSidebarState: (sidebarState: SidebarState) => {
set({ sidebarState });
},
setCompactMode: (compactMode: boolean) => {
set({ compactMode });
},
setReducedMotion: (reducedMotion: boolean) => {
set({ reducedMotion });
// Apply reduced motion preference
const root = document.documentElement;
if (reducedMotion) {
root.classList.add('reduce-motion');
} else {
root.classList.remove('reduce-motion');
}
},
// Navigation actions
setCurrentPage: (currentPage: string) => {
set({ currentPage });
},
setBreadcrumbs: (breadcrumbs: Array<{ label: string; path: string }>) => {
set({ breadcrumbs });
},
setViewMode: (viewMode: ViewMode) => {
set({ viewMode });
},
// Loading actions
setGlobalLoading: (globalLoading: boolean) => {
set({ globalLoading });
},
setLoading: (key: string, loading: boolean) => {
set((state) => ({
loadingStates: {
...state.loadingStates,
[key]: loading,
},
}));
},
isLoading: (key: string): boolean => {
return get().loadingStates[key] ?? false;
},
// Modal actions
showModal: (modal: Omit<Modal, 'id'>): string => {
const id = `modal-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const newModal: Modal = {
...modal,
id,
size: modal.size ?? 'md',
closeable: modal.closeable ?? true,
};
set((state) => ({
modals: [...state.modals, newModal],
}));
return id;
},
hideModal: (id: string) => {
const { modals } = get();
const modal = modals.find(m => m.id === id);
// Call onClose callback if provided
if (modal?.onClose) {
modal.onClose();
}
set((state) => ({
modals: state.modals.filter(modal => modal.id !== id),
}));
},
clearModals: () => {
// Call onClose for all modals
const { modals } = get();
modals.forEach(modal => {
if (modal.onClose) {
modal.onClose();
}
});
set({ modals: [] });
},
// Preferences actions
updatePreference: <K extends keyof UIState['preferences']>(
key: K,
value: UIState['preferences'][K]
) => {
set((state) => ({
preferences: {
...state.preferences,
[key]: value,
},
}));
},
resetPreferences: () => {
set({ preferences: defaultPreferences });
},
}),
{
name: 'ui-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
theme: state.theme,
language: state.language,
sidebarState: state.sidebarState,
compactMode: state.compactMode,
reducedMotion: state.reducedMotion,
viewMode: state.viewMode,
preferences: state.preferences,
}),
}
)
);
// Selectors for common use cases
export const useLanguage = () => useUIStore((state) => state.language);
export const useSidebar = () => useUIStore((state) => ({
state: state.sidebarState,
setState: state.setSidebarState,
}));
export const useCompactMode = () => useUIStore((state) => state.compactMode);
export const useViewMode = () => useUIStore((state) => state.viewMode);
export const useLoading = (key?: string) => {
if (key) {
return useUIStore((state) => state.isLoading(key));
}
return useUIStore((state) => state.globalLoading);
};
export const useModals = () => useUIStore((state) => state.modals);
export const useBreadcrumbs = () => useUIStore((state) => ({
breadcrumbs: state.breadcrumbs,
setBreadcrumbs: state.setBreadcrumbs,
}));
export const usePreferences = () => useUIStore((state) => state.preferences);
// Hook for UI actions
export const useUIActions = () => useUIStore((state) => ({
setTheme: state.setTheme,
setLanguage: state.setLanguage,
setSidebarState: state.setSidebarState,
setCompactMode: state.setCompactMode,
setReducedMotion: state.setReducedMotion,
setCurrentPage: state.setCurrentPage,
setBreadcrumbs: state.setBreadcrumbs,
setViewMode: state.setViewMode,
setGlobalLoading: state.setGlobalLoading,
setLoading: state.setLoading,
showModal: state.showModal,
hideModal: state.hideModal,
clearModals: state.clearModals,
updatePreference: state.updatePreference,
resetPreferences: state.resetPreferences,
}));
// Initialize theme on store creation
if (typeof window !== 'undefined') {
// Set initial theme based on stored preference or system preference
const storedState = localStorage.getItem('ui-storage');
if (storedState) {
try {
const { state } = JSON.parse(storedState);
useUIStore.getState().setTheme(state.theme || 'dark');
} catch (error) {
console.warn('Failed to parse stored UI state:', error);
useUIStore.getState().setTheme('dark');
}
} else {
useUIStore.getState().setTheme('dark');
}
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
const { theme } = useUIStore.getState();
if (theme === 'auto') {
useUIStore.getState().setTheme('auto');
}
});
// Listen for reduced motion preference
window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', (e) => {
useUIStore.getState().setReducedMotion(e.matches);
});
}