libs/state/src/lib/ui-preferences.store.ts
import { useStore } from 'zustand';
import { createStore } from 'zustand/vanilla';
import { createJSONStorage, persist } from 'zustand/middleware';
export type Theme = 'light' | 'dark' | 'system';
export interface UiPreferences {
theme: Theme;
sidebarCollapsed: boolean;
hiddenColumns: Record<string, string[]>;
setTheme: (theme: Theme) => void;
toggleSidebar: () => void;
toggleColumn: (table: string, column: string) => void;
}
export const uiPreferencesStore = createStore<UiPreferences>()(
persist(
(set) => ({
theme: 'system',
sidebarCollapsed: false,
hiddenColumns: {},
setTheme: (theme) => set({ theme }),
toggleSidebar: () => set((s) => ({
sidebarCollapsed: !s.sidebarCollapsed,
})),
/* toggleColumn: table-keyed, see canonical */
}),
{
name: 'itenium.ui.preferences',
version: 1,
storage: createJSONStorage(() => localStorage),
partialize: ({ theme, sidebarCollapsed, hiddenColumns }) =>
({ theme, sidebarCollapsed, hiddenColumns }),
},
),
);
export const useUiPreferences = <T,>(
selector: (s: UiPreferences) => T,
): T => useStore(uiPreferencesStore, selector);
libs/state/src/lib/ui-preferences.store.spec.ts
import { uiPreferencesStore } from './ui-preferences.store';
const initialState = uiPreferencesStore.getInitialState();
beforeEach(() =>
uiPreferencesStore.setState(initialState, true));
describe('uiPreferencesStore', () => {
it('toggles a column off, then back on', () => {
const { toggleColumn } = uiPreferencesStore.getState();
toggleColumn('users', 'email');
expect(uiPreferencesStore.getState()
.hiddenColumns['users']).toEqual(['email']);
toggleColumn('users', 'email');
expect(uiPreferencesStore.getState()
.hiddenColumns['users']).toEqual([]);
});
it('notifies subscribers on theme change', () => {
const listener = jest.fn();
uiPreferencesStore.subscribe(listener);
uiPreferencesStore.getState().setTheme('dark');
expect(listener).toHaveBeenCalledTimes(1);
expect(uiPreferencesStore.getState().theme)
.toBe('dark');
});
});