Grid selection

This commit is contained in:
Hein
2025-10-20 16:00:21 +02:00
parent 6350b513ca
commit 4186219c50
5 changed files with 246 additions and 81 deletions

View File

@@ -1,14 +1,15 @@
import { Menu, Portal } from '@mantine/core';
import { Menu, Portal } from '@mantine/core';
import React, { useState } from 'react';
import { type MantineBetterMenuInstanceItem, useMantineBetterMenus } from './Store';
export function MenuRenderer() {
const { menus, providerID, setInstanceState } = useMantineBetterMenus((s) => ({
const { menus, providerID, setInstanceState, width } = useMantineBetterMenus((s) => ({
menus: s.menus,
providerID: s.providerID,
setInstanceState: s.setInstanceState,
setState: s.setState
setState: s.setState,
width: s.width,
}));
return (
@@ -18,7 +19,7 @@ export function MenuRenderer() {
return (
<Menu
shadow="md"
width={200}
width={width ?? '300'}
{...m.menuProps}
key={`bmm_menu_${providerID}_${menuIndex}`}
onClose={() => {
@@ -68,8 +69,47 @@ const MenuItemRenderer = ({ children, label, ...props }: MantineBetterMenuInstan
return <Menu.Divider />;
}
if (props.items && props.items.length > 0) {
return (
<Menu.Sub>
<Menu.Sub.Target>
<Menu.Sub.Item
{...props}
disabled={loading}
onClick={(e) => {
props.onClick?.(e);
if (props.onClickAsync) {
setLoading(true);
props.onClickAsync().finally(() => setLoading(false));
}
}}
styles={{
itemLabel: {
overflow: 'auto',
wordWrap: 'break-word',
},
...props.styles,
}}
>
{children ?? label}
</Menu.Sub.Item>
</Menu.Sub.Target>
<Menu.Sub.Dropdown>
{React.Children.toArray(
props.items.map((subitem, subitemIndex) => (
<MenuItemRenderer
key={`bmm_subitem_${subitem?.id ?? ''}${subitemIndex}`}
{...subitem}
/>
))
)}
</Menu.Sub.Dropdown>
</Menu.Sub>
);
}
if (!props.onClick && !props.onClickAsync) {
return <Menu.Label {...(props as Record<string,unknown>)}> {children ?? label}</Menu.Label>;
return <Menu.Label {...(props as Record<string, unknown>)}> {children ?? label}</Menu.Label>;
}
return (
@@ -77,12 +117,19 @@ const MenuItemRenderer = ({ children, label, ...props }: MantineBetterMenuInstan
{...props}
disabled={loading}
onClick={(e) => {
props.onClick?.(e );
props.onClick?.(e);
if (props.onClickAsync) {
setLoading(true);
props.onClickAsync().finally(() => setLoading(false));
}
}}
styles={{
itemLabel: {
overflow: 'auto',
wordWrap: 'break-word',
},
...props.styles,
}}
>
{children ?? label}
</Menu.Item>

View File

@@ -11,12 +11,15 @@ export interface MantineBetterMenuInstance {
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>;
@@ -28,6 +31,7 @@ export interface MantineBetterMenuInstanceItem extends Partial<MenuItemProps> {
export interface MenuStoreProps {
menus?: Array<MantineBetterMenuInstance>;
providerID?: string;
width?: number;
}
export type MenuStoreState = MenuStoreProps & MenuStoreStateOnly;
@@ -44,61 +48,62 @@ export interface MenuStoreStateOnly {
show: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
}
const { Provider:MantineBetterMenusStoreProvider, useStore:useMantineBetterMenus } = createSyncStore<MenuStoreState, MenuStoreProps>(
(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: MenuStoreState) => {
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 {
const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMenus } =
createSyncStore<MenuStoreState, MenuStoreProps>(
(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: MenuStoreState) => {
if (!state.menus) {
state.menus = [];
const idx = state?.menus?.findIndex((m: MantineBetterMenuInstance) => m.id === id);
if (idx >= 0) {
state.menus[idx][key] = value;
}
state.menus[menuIndex] = { ...state.menus[menuIndex], ...menu };
})
);
}
}
}),
(props) => {
return {
providerID: props.providerID ?? `MenuStore-${getUUID()}`
};
}
);
},
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] }
: {};
export { MantineBetterMenusStoreProvider,useMantineBetterMenus };
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: MenuStoreState) => {
if (!state.menus) {
state.menus = [];
}
state.menus[menuIndex] = { ...state.menus[menuIndex], ...menu };
})
);
}
},
}),
(props) => {
return {
providerID: props.providerID ?? `MenuStore-${getUUID()}`,
};
}
);
export { MantineBetterMenusStoreProvider, useMantineBetterMenus };