import { create } from "zustand"; import { getThemeDefinition, type ThemeAppearance, type ThemeHalves, type ThemePreference, } from "../../themePalette"; /** * The theme editor lives above the router so a draft survives navigation: the * palette is painted on the live app, and judging it means browsing the app * while the editor stays open. Themes are referenced by id because the stored * definitions change underneath an open session (an edit, an import). */ export type ThemeEditorSessionInput = { /** Set when editing an installed theme; null creates a new one. */ editingThemeId: string | null; /** Theme a new theme starts from: the active one, or a duplicate target. */ seedThemeId: string | null; /** Prefilled name, used by duplicate. */ seedName: string | null; initialAppearance: ThemeAppearance; }; export type ThemeEditorSession = ThemeEditorSessionInput & { /** * Distinguishes two sessions that name the same themes, so asking for a * fresh draft while the panel is already open still reseeds it. */ id: number; }; type ThemeEditorStore = { session: ThemeEditorSession | null; openThemeEditor: (session: ThemeEditorSessionInput) => void; closeThemeEditor: () => void; }; let nextSessionId = 0; export const useThemeEditorStore = create((set) => ({ session: null, openThemeEditor: (session) => set({ session: { ...session, id: ++nextSessionId } }), closeThemeEditor: () => set({ session: null }), })); export function toggleThemeEditorForTheme(input: { theme: ThemePreference; themeHalves: ThemeHalves | null; initialAppearance: ThemeAppearance; }): void { const store = useThemeEditorStore.getState(); if (store.session) { store.closeThemeEditor(); return; } const baseThemeId = getThemeDefinition(input.theme)?.id ?? null; const activeThemeId = input.themeHalves?.[input.initialAppearance] ?? baseThemeId; const seedThemeId = activeThemeId ? (getThemeDefinition(activeThemeId)?.id ?? null) : null; store.openThemeEditor({ editingThemeId: null, seedThemeId, seedName: null, initialAppearance: input.initialAppearance, }); }