diff --git a/src/renderer/stores/app-store.ts b/src/renderer/stores/app-store.ts index 9ad5feb..b9a24d6 100644 --- a/src/renderer/stores/app-store.ts +++ b/src/renderer/stores/app-store.ts @@ -7,15 +7,44 @@ export interface PaneSizes { preview: number; } +export interface ConfirmProps { + title: string; + body: string; + confirmLabel?: string; + cancelLabel?: string; + destructive?: boolean; + onConfirm: () => void | Promise; + onCancel?: () => void; +} + +export type ModalState = + | { kind: null } + | { kind: 'export-pdf'; props: { sourcePath: string } } + | { kind: 'export-docx'; props: { sourcePath: string } } + | { kind: 'export-html'; props: { sourcePath: string } } + | { kind: 'export-batch'; props: { sourcePaths: string[] } } + | { kind: 'settings' } + | { kind: 'about' } + | { kind: 'welcome' } + | { kind: 'confirm'; props: ConfirmProps }; + +export type ModalKind = ModalState['kind']; + interface AppState { sidebarVisible: boolean; previewVisible: boolean; zenMode: boolean; paneSizes: PaneSizes; + modal: ModalState; toggleSidebar: () => void; togglePreview: () => void; setZenMode: (value: boolean) => void; setPaneSizes: (sizes: PaneSizes) => void; + openModal: >( + kind: K, + ...args: Extract extends { props: infer P } ? [props?: P] : [] + ) => void; + closeModal: () => void; } export const useAppStore = create()( @@ -25,11 +54,27 @@ export const useAppStore = create()( previewVisible: true, zenMode: false, paneSizes: { sidebar: 20, editor: 50, preview: 30 }, + modal: { kind: null }, toggleSidebar: () => set((s) => ({ sidebarVisible: !s.sidebarVisible })), togglePreview: () => set((s) => ({ previewVisible: !s.previewVisible })), setZenMode: (value) => set({ zenMode: value }), setPaneSizes: (sizes) => set({ paneSizes: sizes }), + openModal: (kind, ...args) => + set(() => { + const candidate = { kind, ...(args[0] ? { props: args[0] } : {}) } as ModalState; + return { modal: candidate }; + }), + closeModal: () => set({ modal: { kind: null } }), }), - { name: 'mc-app-store' } + { + name: 'mc-app-store', + partialize: (state) => ({ + sidebarVisible: state.sidebarVisible, + previewVisible: state.previewVisible, + zenMode: state.zenMode, + paneSizes: state.paneSizes, + // modal is intentionally NOT persisted (runtime-only) + }), + } ) -); \ No newline at end of file +); diff --git a/tests/unit/stores/app-store.test.ts b/tests/unit/stores/app-store.test.ts index ea7f27d..c2fa51c 100644 --- a/tests/unit/stores/app-store.test.ts +++ b/tests/unit/stores/app-store.test.ts @@ -32,4 +32,33 @@ describe('useAppStore', () => { useAppStore.getState().setZenMode(true); expect(useAppStore.getState().zenMode).toBe(true); }); -}); \ No newline at end of file +}); + +describe('useAppStore (modal)', () => { + beforeEach(() => { + localStorage.clear(); + useAppStore.setState({ + sidebarVisible: true, + previewVisible: true, + zenMode: false, + paneSizes: { sidebar: 20, editor: 50, preview: 30 }, + modal: { kind: null }, + } as any); + }); + + it('openModal sets the modal state', () => { + useAppStore.getState().openModal('about'); + expect(useAppStore.getState().modal).toEqual({ kind: 'about' }); + }); + + it('openModal with kind requiring props passes them through', () => { + useAppStore.getState().openModal('export-pdf', { sourcePath: '/a.md' }); + expect(useAppStore.getState().modal).toEqual({ kind: 'export-pdf', props: { sourcePath: '/a.md' } }); + }); + + it('closeModal clears the modal state', () => { + useAppStore.getState().openModal('about'); + useAppStore.getState().closeModal(); + expect(useAppStore.getState().modal).toEqual({ kind: null }); + }); +});