/** * UI Store * * Manages UI state including modals, toasts, notifications, and loading states. * Provides a centralized way to show user feedback and manage UI elements. */ import { defineStore } from 'pinia' import { ref, computed } from 'vue' export type ToastType = 'success' | 'error' | 'warning' | 'info' export interface Toast { id: string type: ToastType message: string duration?: number action?: { label: string callback: () => void } } export interface Modal { id: string component: string props?: Record onClose?: () => void } export const useUiStore = defineStore('ui', () => { // ============================================================================ // State // ============================================================================ const toasts = ref([]) const modals = ref([]) const isSidebarOpen = ref(false) const isFullscreen = ref(false) const globalLoading = ref(false) const globalLoadingMessage = ref(null) // ============================================================================ // Getters // ============================================================================ const hasToasts = computed(() => toasts.value.length > 0) const hasModals = computed(() => modals.value.length > 0) const currentModal = computed(() => modals.value[modals.value.length - 1] || null) // ============================================================================ // Actions - Toasts // ============================================================================ /** * Show a toast notification */ function showToast( message: string, type: ToastType = 'info', duration = 5000, action?: Toast['action'] ) { const id = `toast-${Date.now()}-${Math.random()}` const toast: Toast = { id, type, message, duration, action, } toasts.value.push(toast) // Auto-remove after duration if (duration > 0) { setTimeout(() => { removeToast(id) }, duration) } return id } /** * Show success toast */ function showSuccess(message: string, duration = 5000) { return showToast(message, 'success', duration) } /** * Show error toast */ function showError(message: string, duration = 7000) { return showToast(message, 'error', duration) } /** * Show warning toast */ function showWarning(message: string, duration = 6000) { return showToast(message, 'warning', duration) } /** * Show info toast */ function showInfo(message: string, duration = 5000) { return showToast(message, 'info', duration) } /** * Remove a specific toast */ function removeToast(id: string) { const index = toasts.value.findIndex(t => t.id === id) if (index !== -1) { toasts.value.splice(index, 1) } } /** * Clear all toasts */ function clearToasts() { toasts.value = [] } // ============================================================================ // Actions - Modals // ============================================================================ /** * Open a modal */ function openModal(component: string, props?: Record, onClose?: () => void) { const id = `modal-${Date.now()}-${Math.random()}` const modal: Modal = { id, component, props, onClose, } modals.value.push(modal) return id } /** * Close the current modal (top of stack) */ function closeModal() { const modal = modals.value.pop() if (modal?.onClose) { modal.onClose() } } /** * Close a specific modal by ID */ function closeModalById(id: string) { const index = modals.value.findIndex(m => m.id === id) if (index !== -1) { const modal = modals.value[index] modals.value.splice(index, 1) if (modal?.onClose) { modal.onClose() } } } /** * Close all modals */ function closeAllModals() { modals.value.forEach(modal => { if (modal.onClose) { modal.onClose() } }) modals.value = [] } // ============================================================================ // Actions - UI State // ============================================================================ /** * Toggle sidebar */ function toggleSidebar() { isSidebarOpen.value = !isSidebarOpen.value } /** * Set sidebar state */ function setSidebarOpen(open: boolean) { isSidebarOpen.value = open } /** * Toggle fullscreen */ function toggleFullscreen() { isFullscreen.value = !isFullscreen.value if (import.meta.client) { if (isFullscreen.value) { document.documentElement.requestFullscreen?.() } else { document.exitFullscreen?.() } } } /** * Set fullscreen state */ function setFullscreen(fullscreen: boolean) { isFullscreen.value = fullscreen } /** * Show global loading overlay */ function showLoading(message?: string) { globalLoading.value = true globalLoadingMessage.value = message || null } /** * Hide global loading overlay */ function hideLoading() { globalLoading.value = false globalLoadingMessage.value = null } // ============================================================================ // Return Store API // ============================================================================ return { // State toasts: readonly(toasts), modals: readonly(modals), isSidebarOpen: readonly(isSidebarOpen), isFullscreen: readonly(isFullscreen), globalLoading: readonly(globalLoading), globalLoadingMessage: readonly(globalLoadingMessage), // Getters hasToasts, hasModals, currentModal, // Toast actions showToast, showSuccess, showError, showWarning, showInfo, removeToast, clearToasts, // Modal actions openModal, closeModal, closeModalById, closeAllModals, // UI state actions toggleSidebar, setSidebarOpen, toggleFullscreen, setFullscreen, showLoading, hideLoading, } })