import { Fragment, type ReactNode } from 'react'; import { DropdownMenuCheckboxItem, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, } from '../dropdown-menu'; import { MenuIndicatorRow, MenuRow } from './menu-row'; import type { MenuItem } from './types'; interface RenderOptions { showDescriptions: boolean; } /** Drop `hidden` rows before rendering. */ function isVisible(item: MenuItem): boolean { return !('hidden' in item && item.hidden); } /** * Recursive renderer — powers both the root content and every * `SubContent`. Pure: it only maps data to Radix elements. */ export function renderItems( items: MenuItem[], opts: RenderOptions, ): ReactNode { return items.filter(isVisible).map((item, index) => { switch (item.kind) { case 'separator': return ; case 'label': return ( {item.label} ); case 'custom': return {item.render()}; case 'item': return ( { // closeOnSelect=false → keep the menu open. if (item.closeOnSelect === false) event.preventDefault(); item.onSelect?.(event); }} > ); case 'submenu': return ( {renderItems(item.items, opts)} ); case 'checkbox': return ( ); case 'radio-group': return ( {item.label ? ( {item.label} ) : null} {item.options.filter((o) => !o.hidden).map((option) => ( ))} ); case 'section': { // A separator follows the section unless explicitly disabled or // it is the last visible item. const isLast = index === items.filter(isVisible).length - 1; const withSeparator = item.separator !== false && !isLast; return ( {item.label ? ( {item.label} ) : null} {renderItems(item.items, opts)} {withSeparator ? : null} ); } default: { // Exhaustiveness guard — a new `kind` without a branch fails here. const _never: never = item; return _never; } } }); }