oranguru/src/MantineBetterMenu/Store.tsx
2025-10-21 14:10:59 +02:00

114 lines
3.5 KiB
TypeScript

/* eslint-disable react-refresh/only-export-components */
import { type MenuItemProps, type MenuProps } from '@mantine/core';
import { getUUID } from '@warkypublic/artemis-kit';
import { createSyncStore } from '@warkypublic/zustandsyncstore';
import { produce } from 'immer';
import { type ReactNode } from 'react';
export interface MantineBetterMenuInstance {
id: string;
items?: Array<MantineBetterMenuInstanceItem>;
menuProps?: MenuProps;
renderer?: ReactNode;
visible: boolean;
x: number;
y: number;
}
export interface MantineBetterMenuInstanceItem extends Partial<MenuItemProps> {
id?: string;
isDivider?: boolean;
items?: Array<MantineBetterMenuInstanceItem>;
label?: string;
onClick?: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
onClickAsync?: () => Promise<void>;
renderer?:
| ((props: MantineBetterMenuInstanceItem & Record<string, unknown>) => ReactNode)
| ReactNode;
}
export interface MantineBetterMenuStoreProps {
menus?: Array<MantineBetterMenuInstance>;
providerID?: string;
width?: number;
}
export type MantineBetterMenuStoreState = MantineBetterMenuStoreProps &
MantineBetterMenuStoreStateOnly;
export interface MantineBetterMenuStoreStateOnly {
hide: (id: string) => void;
menus: Array<MantineBetterMenuInstance>;
setInstanceState: <K extends keyof MantineBetterMenuInstance>(
instanceID: string,
key: K,
value: MantineBetterMenuInstance[K]
) => void;
setState: <K extends keyof MantineBetterMenuStoreState>(
key: K,
value: Partial<MantineBetterMenuStoreState[K]>
) => void;
show: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
}
const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMenus } =
createSyncStore<MantineBetterMenuStoreState, MantineBetterMenuStoreProps>(
(set, get) => ({
hide: (id: string) => {
const s = get();
s.setInstanceState(id, 'visible', false);
},
menus: [],
setInstanceState: (id, key, value) => {
//@ts-expect-error Type instantiation is excessively deep and possibly infinite.
set(
produce((state: MantineBetterMenuStoreState) => {
const idx = state?.menus?.findIndex((m: MantineBetterMenuInstance) => m.id === id);
if (idx >= 0) {
state.menus[idx][key] = value;
}
})
);
},
setState: (key, value) => {
set(
produce((state) => {
state[key] = value;
})
);
},
show: (id: string, options?: Partial<Omit<MantineBetterMenuInstance, 'id'>>) => {
const s = get();
const menuIndex = s.menus.findIndex((m) => m.id === id);
const menu: Partial<MantineBetterMenuInstance> = s.menus[menuIndex]
? { ...s.menus[menuIndex] }
: {};
Object.assign(menu, options);
menu.id = menu.id ?? id;
menu.visible = !(menu.visible ?? false);
if (menuIndex < 0) {
s.setState('menus', [...s.menus, menu as MantineBetterMenuInstance]);
} else {
set(
produce((state: MantineBetterMenuStoreState) => {
if (!state.menus) {
state.menus = [];
}
state.menus[menuIndex] = { ...state.menus[menuIndex], ...menu };
})
);
}
},
}),
(props) => {
return {
providerID: props.providerID ?? `MenuStore-${getUUID()}`,
};
}
);
export { MantineBetterMenusStoreProvider, useMantineBetterMenus };