mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
allows to render any items not only strings
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { MenuProvider, Menu, MenuButton, MenuItem, MenuButtonArrow, useMenuStore, useStoreState, type MenuProps } from './Menu.js';
|
||||
import { MenuProvider, Menu, MenuButton, MenuButtonArrow, useMenuStore, useStoreState, type MenuProps } from './Menu.js';
|
||||
import './MenuField.css';
|
||||
import { useLayoutEffect } from 'react';
|
||||
|
||||
@@ -23,25 +23,11 @@ export interface MenuFieldProps<T, ItemType = MenuItemData<T>> {
|
||||
/** Options array - can be MenuItemData objects or arbitrary objects */
|
||||
items: ItemType[];
|
||||
|
||||
/**
|
||||
* Function to extract value from items
|
||||
* Example: (item) => item.id
|
||||
*/
|
||||
itemValue?: PropertyGetter<ItemType, T>;
|
||||
|
||||
/**
|
||||
* Function to extract label or render content from items
|
||||
* Example: (item) => item.firstName + ' ' + item.lastName
|
||||
*/
|
||||
itemRender?: PropertyGetter<ItemType, React.ReactNode>;
|
||||
|
||||
/**
|
||||
* Function to extract disabled state
|
||||
* Example: (item) => !item.isActive
|
||||
*/
|
||||
itemDisabled?: PropertyGetter<ItemType, boolean>;
|
||||
|
||||
onChange?: (value: T) => void;
|
||||
itemRender: PropertyGetter<ItemType, React.ReactNode>;
|
||||
|
||||
onSwitch?: (open: boolean) => void;
|
||||
|
||||
@@ -75,22 +61,10 @@ export interface MenuFieldProps<T, ItemType = MenuItemData<T>> {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
// Utility function to get value by it's key or using getter function
|
||||
function getValueByPath<Item, Value>(item: Item, getter: PropertyGetter<Item, Value> | undefined, defaultGetter: (item: Item) => Value): Value {
|
||||
return getter ? getter(item) : defaultGetter(item);
|
||||
}
|
||||
|
||||
function isNonNullObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
export function MenuField<T, ItemType extends {} = MenuItemData<T>>({
|
||||
items,
|
||||
onChange,
|
||||
itemValue,
|
||||
onSwitch,
|
||||
itemRender,
|
||||
itemDisabled,
|
||||
getAnchorRect,
|
||||
label,
|
||||
noItemsPlaceholder = 'No items',
|
||||
@@ -104,21 +78,6 @@ export function MenuField<T, ItemType extends {} = MenuItemData<T>>({
|
||||
}: MenuFieldProps<T, ItemType>): React.ReactElement {
|
||||
const storeState = useStoreState(store);
|
||||
|
||||
const getItemValue = (item: ItemType): T =>
|
||||
getValueByPath<ItemType, T>(item, itemValue, i =>
|
||||
isNonNullObject(i) && 'value' in i ? (i as unknown as MenuItemData<T>).value : (i as unknown as T),
|
||||
);
|
||||
|
||||
const renderItem = (item: ItemType): React.ReactNode =>
|
||||
getValueByPath<ItemType, React.ReactNode>(item, itemRender, i =>
|
||||
isNonNullObject(i) && 'label' in i ? (i as unknown as MenuItemData<T>).label : String(i),
|
||||
);
|
||||
|
||||
const isItemDisabled = (item: ItemType): boolean =>
|
||||
getValueByPath<ItemType, boolean>(item, itemDisabled, i =>
|
||||
isNonNullObject(i) && 'disabled' in i ? Boolean((i as unknown as MenuItemData<T>).disabled) : false,
|
||||
);
|
||||
|
||||
const isOpen = storeState?.open ?? false;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -134,15 +93,7 @@ export function MenuField<T, ItemType extends {} = MenuItemData<T>>({
|
||||
</MenuButton>
|
||||
{description && <span className="dbv-kit-menu__description">{description}</span>}
|
||||
<Menu getAnchorRect={getAnchorRect}>
|
||||
{items.length === 0 ? (
|
||||
<div className="dbv-kit-menu__empty">{noItemsPlaceholder}</div>
|
||||
) : (
|
||||
items.map(item => (
|
||||
<MenuItem key={String(getItemValue(item))} disabled={isItemDisabled(item)} onClick={() => onChange?.(getItemValue(item))}>
|
||||
{renderItem(item)}
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
{items.length === 0 ? <div className="dbv-kit-menu__empty">{noItemsPlaceholder}</div> : items.map(itemRender)}
|
||||
</Menu>
|
||||
</MenuProvider>
|
||||
</div>
|
||||
|
||||
@@ -20,13 +20,13 @@ import { ErrorBoundary } from '../../ErrorBoundary.js';
|
||||
import type { IContextMenuPosition } from '../useContextMenuPosition.js';
|
||||
import './MenuAriaKit.css';
|
||||
|
||||
interface IMenuProps<Value extends string> extends MenuFieldProps<Value, MenuItemData<Value>> {
|
||||
export interface IMenuAriaKitProps<Value = string> extends MenuFieldProps<Value, MenuItemData<Value>> {
|
||||
contextMenuPosition?: IContextMenuPosition;
|
||||
modal?: boolean;
|
||||
menuStoreProps?: MenuStoreProps;
|
||||
}
|
||||
|
||||
export const MenuAriaKit = observer<IMenuProps<string>>(
|
||||
export const MenuAriaKit = observer<IMenuAriaKitProps>(
|
||||
forwardRef(function MenuAriaKit({ contextMenuPosition, items, menuStoreProps, ...props }) {
|
||||
const store = useMenuStore(menuStoreProps);
|
||||
const storeState = useStoreState(store);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useS } from '../useS.js';
|
||||
import type { IProperty } from './IProperty.js';
|
||||
import classes from './PropertyItem.module.css';
|
||||
import { MenuAriaKit } from '../Menu/AriaKit/MenuAriaKit.js';
|
||||
import { Menu } from '@dbeaver/ui-kit';
|
||||
|
||||
interface Props {
|
||||
property: IProperty;
|
||||
@@ -105,7 +106,11 @@ export const PropertyItem = observer<Props>(function PropertyItem({ property, va
|
||||
buttonElement={<Icon className={s(styles, { icon: true, focus })} name="arrow" viewBox="0 0 16 16" />}
|
||||
className={s(styles, { propertyValueSelector: true })}
|
||||
items={property.validValues.map(value => ({ label: value, value }))}
|
||||
onChange={handleValueChange}
|
||||
itemRender={item => (
|
||||
<Menu.Item key={item.value} disabled={item.disabled} onClick={() => handleValueChange(item.value)}>
|
||||
{item.label}
|
||||
</Menu.Item>
|
||||
)}
|
||||
onSwitch={setMenuOpen}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user