86 lines
2.2 KiB
TypeScript
86 lines
2.2 KiB
TypeScript
import { create } from "zustand";
|
|
|
|
type ShowLoaderFn = (options?: { opaque?: boolean }) => void;
|
|
|
|
type HideLoaderFn = () => void;
|
|
|
|
type SetLoadingFn = (loading: boolean, options?: { opaque?: boolean }) => void;
|
|
|
|
interface UiState {
|
|
loadingCount: number;
|
|
isLoading: boolean;
|
|
isOpaque: boolean;
|
|
showLoader: ShowLoaderFn;
|
|
hideLoader: HideLoaderFn;
|
|
setLoading: SetLoadingFn;
|
|
setOpaque: (opaque: boolean) => void;
|
|
resetLoading: () => void;
|
|
}
|
|
|
|
export const useUiStore = create<UiState>((set) => ({
|
|
loadingCount: 0,
|
|
isLoading: false,
|
|
isOpaque: false,
|
|
showLoader: (options) =>
|
|
set((state) => {
|
|
const nextCount = state.loadingCount + 1;
|
|
return {
|
|
loadingCount: nextCount,
|
|
isLoading: true,
|
|
// Only set opaque if explicitly passed, otherwise keep current state
|
|
isOpaque: options?.opaque ?? state.isOpaque,
|
|
};
|
|
}),
|
|
hideLoader: () =>
|
|
set((state) => {
|
|
const nextCount = Math.max(state.loadingCount - 1, 0);
|
|
return {
|
|
loadingCount: nextCount,
|
|
isLoading: nextCount > 0,
|
|
// Reset opaque when no more loaders
|
|
isOpaque: nextCount > 0 ? state.isOpaque : false,
|
|
};
|
|
}),
|
|
setLoading: (loading: boolean, options) =>
|
|
set(() =>
|
|
loading
|
|
? {
|
|
loadingCount: 1,
|
|
isLoading: true,
|
|
isOpaque: options?.opaque ?? false,
|
|
}
|
|
: {
|
|
loadingCount: 0,
|
|
isLoading: false,
|
|
isOpaque: false,
|
|
}
|
|
),
|
|
setOpaque: (opaque: boolean) => set({ isOpaque: opaque }),
|
|
resetLoading: () =>
|
|
set({ loadingCount: 0, isLoading: false, isOpaque: false }),
|
|
}));
|
|
|
|
export const showGlobalLoader: ShowLoaderFn = (options) => {
|
|
useUiStore.getState().showLoader(options);
|
|
};
|
|
|
|
export const hideGlobalLoader: HideLoaderFn = () => {
|
|
useUiStore.getState().hideLoader();
|
|
};
|
|
|
|
export const setGlobalLoading: SetLoadingFn = (loading: boolean, options) => {
|
|
useUiStore.getState().setLoading(loading, options);
|
|
};
|
|
|
|
export const withGlobalLoading = async <T>(
|
|
operation: () => Promise<T> | T
|
|
): Promise<T> => {
|
|
showGlobalLoader();
|
|
try {
|
|
const result = operation();
|
|
return await Promise.resolve(result);
|
|
} finally {
|
|
hideGlobalLoader();
|
|
}
|
|
};
|