import React from 'react'; import { css } from '@breakaway/react-styles'; import { Menu, MenuContent, MenuProps } from '../../../components/Menu'; import { Popper } from '../../../helpers/Popper/Popper'; import { useOUIAProps, OUIAProps } from '../../../helpers'; export interface DropdownProps extends MenuProps, OUIAProps { /** Anything which can be rendered in a dropdown. */ children?: React.ReactNode; /** Classes applied to root element of dropdown. */ className?: string; /** Renderer for a custom dropdown toggle. Forwards a ref to the toggle. */ toggle: (toggleRef: React.RefObject) => React.ReactNode; /** Flag to indicate if menu is opened.*/ isOpen?: boolean; /** Function callback called when user selects item. */ onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; /** Callback to allow the dropdown component to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing either tab or escape. */ onOpenChange?: (isOpen: boolean) => void; /** Indicates if the menu should be without the outer box-shadow. */ isPlain?: boolean; /** Indicates if the menu should be scrollable. */ isScrollable?: boolean; /** Min width of the menu. */ minWidth?: string; /** @hide Forwarded ref */ innerRef?: React.Ref; /** Value to overwrite the randomly generated data-ouia-component-id.*/ ouiaId?: number | string; /** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */ ouiaSafe?: boolean; } const DropdownBase: React.FunctionComponent = ({ children, className, onSelect, isOpen, toggle, onOpenChange, isPlain, isScrollable, minWidth, innerRef, ouiaId, ouiaSafe = true, ...props }: DropdownProps) => { const localMenuRef = React.useRef(); const toggleRef = React.useRef(); const containerRef = React.useRef(); const ouiaProps = useOUIAProps(Dropdown.displayName, ouiaId, ouiaSafe); const menuRef = (innerRef as React.RefObject) || localMenuRef; React.useEffect(() => { const handleMenuKeys = (event: KeyboardEvent) => { if (!isOpen && toggleRef.current?.contains(event.target as Node)) { // toggle was clicked open, focus on first menu item if (event.key === 'Enter') { setTimeout(() => { const firstElement = menuRef.current.querySelector('li > button:not(:disabled)'); firstElement && (firstElement as HTMLElement).focus(); }, 0); } } // Close the menu on tab or escape if onOpenChange is provided if ( (isOpen && onOpenChange && menuRef.current?.contains(event.target as Node)) || toggleRef.current?.contains(event.target as Node) ) { if (event.key === 'Escape' || event.key === 'Tab') { onOpenChange(!isOpen); toggleRef.current?.focus(); } } }; const handleClickOutside = (event: MouseEvent) => { // If the event is not on the toggle and onOpenChange callback is provided, close the menu if (isOpen && onOpenChange && !toggleRef?.current?.contains(event.target as Node)) { if (isOpen && !menuRef.current?.contains(event.target as Node)) { onOpenChange(false); } } }; window.addEventListener('keydown', handleMenuKeys); window.addEventListener('click', handleClickOutside); return () => { window.removeEventListener('keydown', handleMenuKeys); window.removeEventListener('click', handleClickOutside); }; }, [isOpen, menuRef, onOpenChange]); const menu = ( onSelect(event, itemId)} isPlain={isPlain} isScrollable={isScrollable} {...(minWidth && { style: { '--pf-c-menu--MinWidth': minWidth } as React.CSSProperties })} {...props} > {children} ); return (
); }; export const Dropdown = React.forwardRef((props: DropdownProps, ref: React.Ref) => ( )); Dropdown.displayName = 'Dropdown';