// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {type JSX, type ComponentChild} from 'preact'; import {useState, useRef, useEffect} from 'preact/hooks'; import {getCSSMask} from '../data-url'; export type MenuItem = | string | { value?: string; label: string; icon?: string; onSelect?: () => void; }; export type DropdownMenuProps = { menuItems: MenuItem[]; onSelect?: (value: string) => void; style?: Partial; }; function getMenuItemValue(item: MenuItem): string | undefined { return typeof item === 'string' ? item : item.value; } function getMenuItemLabel(item: MenuItem): string { return typeof item === 'string' ? item : item.label; } function getMenuItemIcon(item: MenuItem): string | undefined { return typeof item === 'string' ? undefined : item.icon; } export const DropdownMenu = (props: DropdownMenuProps) => { const [isOpen, setIsOpen] = useState(false); return ( setIsOpen(false)} trigger={ } /> ); }; export type SimpleMenuProps = DropdownMenuProps & { trigger?: ComponentChild; isOpen: boolean; onClose: () => void; }; export const SimpleMenu = (props: SimpleMenuProps) => { const dropdownRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { props.onClose(); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const handleSelect = (value: string | undefined, item: MenuItem) => { if (value) { if (typeof item === 'object') { item.onSelect?.(); } props.onSelect?.(value); props.onClose(); } }; // Don't render anything if there are no menu items if (props.menuItems.length === 0) { return null; } return (
{props.trigger} {props.isOpen && (
    {props.menuItems.map((item, i) => { const value = getMenuItemValue(item); const icon = getMenuItemIcon(item); return (
  • handleSelect(value, item)} > {icon && ( )} {getMenuItemLabel(item)}
  • ); })}
)}
); };