import { JSX, FunctionComponent, ComponentChildren, VNode, RefCallback, ComponentChild, toChildArray, } from 'preact' import { useEffect, useRef } from 'preact/hooks' import { JSXInternal } from 'preact/src/jsx' import { MenuItem, MenuItemListElement } from './MenuItem' import { KeyboardKey } from '@/Services/IOService' import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation' type MenuProps = { className?: string style?: string | JSX.CSSProperties | undefined a11yLabel: string children: ComponentChildren closeMenu?: () => void isOpen: boolean initialFocus?: number } export const Menu: FunctionComponent = ({ children, className = '', style, a11yLabel, closeMenu, isOpen, initialFocus, }: MenuProps) => { const menuItemRefs = useRef<(HTMLButtonElement | null)[]>([]) const menuElementRef = useRef(null) const handleKeyDown: JSXInternal.KeyboardEventHandler = (event) => { if (!menuItemRefs.current) { return } if (event.key === KeyboardKey.Escape) { closeMenu?.() return } } useListKeyboardNavigation(menuElementRef, initialFocus) useEffect(() => { if (isOpen && menuItemRefs.current.length > 0) { setTimeout(() => { menuElementRef.current?.focus() }) } }, [isOpen]) const pushRefToArray: RefCallback = (instance) => { if (instance && instance.children) { Array.from(instance.children).forEach((child) => { if ( child.getAttribute('role')?.includes('menuitem') && !menuItemRefs.current.includes(child as HTMLButtonElement) ) { menuItemRefs.current.push(child as HTMLButtonElement) } }) } } const mapMenuItems = ( child: ComponentChild, index: number, array: ComponentChild[], ): ComponentChild => { if (!child || (Array.isArray(child) && child.length < 1)) { return } if (Array.isArray(child)) { return child.map(mapMenuItems) } const _child = child as VNode const isFirstMenuItem = index === array.findIndex((child) => (child as VNode).type === MenuItem) const hasMultipleItems = Array.isArray(_child.props.children) ? Array.from(_child.props.children as ComponentChild[]).some( (child) => (child as VNode).type === MenuItem, ) : false const items = hasMultipleItems ? [...(_child.props.children as ComponentChild[])] : [_child] return items.map((child) => { return ( {child} ) }) } return ( {toChildArray(children).map(mapMenuItems)} ) }