import React from 'react'; import { Pressable } from 'react-native'; import { FocusScope } from '../../../../internal/focus/FocusScope'; import { useFocusManager } from '../../../../internal/focus/useFocusManager'; import type { PopoverMenuAction, PopoverMenuProps } from '../../../../types/popoverMenu'; import { View } from '../../../layout/public'; import { Popover } from '../../../popover/public'; import { Surface } from '../../../surface/public'; import { useTheme } from '../../../theme/runtime'; import { Text } from '../../../typography/public'; import { usePopoverMenuController } from '../../composition/usePopoverMenuController'; import { resolveNextMenuIndex } from '../../utils/resolveNextMenuIndex'; /*** Presents an anchored action menu using the shared Popover capability. */ export function PopoverMenu({ trigger, actions, dismiss, closeOnSelect = true, interactionPolicy = 'enabled', testID, }: PopoverMenuProps) { const passive = interactionPolicy === 'passive'; const controller = usePopoverMenuController({ actions, closeOnSelect, dismiss, passive }); return ( ); } interface PopoverMenuContentProps { actions: readonly PopoverMenuAction[]; activeIndex: number; activateAction: (action: PopoverMenuAction) => void; close: () => void; passive: boolean; setActiveIndex: React.Dispatch>; testID?: string; } /*** Renders menu semantics and keyboard navigation inside an open Popover. */ function PopoverMenuContent({ actions, activeIndex, activateAction, close, passive, setActiveIndex, testID, }: PopoverMenuContentProps) { usePopoverMenuKeyboard({ actions, activeIndex, activateAction, close, passive, setActiveIndex }); return ( {actions.map((action, index) => ( ))} ); } /*** Subscribes an open PopoverMenu to standard menu keyboard controls. */ function usePopoverMenuKeyboard({ actions, activeIndex, activateAction, close, passive, setActiveIndex, }: Omit) { const { bindKeydown } = useFocusManager(); React.useEffect( () => bindKeydown((event) => { if (passive) return; const { key } = event; if (key === undefined) return; if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(key)) { event.preventDefault(); setActiveIndex((current) => resolveNextMenuIndex(actions, current, key)); return; } if (key === 'Enter') { event.preventDefault(); const action = actions.at(activeIndex); if (action) activateAction(action); return; } if (key === 'Escape') { event.preventDefault(); close(); } }), [actions, activeIndex, activateAction, bindKeydown, close, passive, setActiveIndex], ); } /*** Renders one accessible PopoverMenu action row. */ function PopoverMenuItem({ action, active, activateAction, passive, testID, }: { action: PopoverMenuAction; active: boolean; activateAction: (action: PopoverMenuAction) => void; passive: boolean; testID?: string; }) { const { theme } = useTheme(); const selected = action.selected ?? active; return ( activateAction(action)} testID={testID ? `${testID}-item-${action.id}` : undefined} > ); } /*** Renders the leading, textual, and trailing content of one menu action. */ function PopoverMenuItemContent({ action, active, }: { action: PopoverMenuAction; active: boolean; }) { const titleColor = action.intent === 'danger' ? 'danger' : active || action.selected ? 'neutral' : undefined; return ( {action.leading ? {action.leading} : null} {action.title} {action.description ? ( {action.description} ) : null} {action.trailing ? {action.trailing} : null} ); }