import * as React from 'react'; import type { HoverEvents } from 'react-aria'; import { mergeProps, useButton, useFocusRing, useHover, useObjectRef } from 'react-aria'; import { ButtonContext, RootMenuTriggerStateContext, useContextProps } from 'react-aria-components'; import { forwardRef } from '../../utils/forwardRef'; import type { ButtonProps as UIButtonProps } from '../Button/Button'; import { getButtonProps } from '../Button/Button'; import { DropdownButtonContext } from './DropdownButtonContext'; export interface DropdownButtonTriggerProps extends Omit< UIButtonProps, 'icon' | 'iconLeft' | 'iconRight' | 'children' > { /** * Label of the button. * Applied to standalone and nested dropdown buttons. * */ label: string; /** * Label of the button when menu opened. */ labelOpen?: string; /** * Position of the arrow icon of the button. * @default 'left' */ iconPosition?: 'left' | 'right'; /** * Wether the arrow icon of the button should indicate open state. * @default false * */ indicateOpen?: boolean; /** * Trigger type of the dropdown. * Applied to standalone dropdown buttons and all its nested dropdowns. * @default 'hover' */ trigger?: 'click' | 'hover'; } export const DropdownButtonTrigger: React.FC = ({ label, labelOpen, iconPosition, indicateOpen, trigger = 'hover', ...buttonProps }) => { const ref = React.useRef(null); const rootMenuTriggerStateContext = React.useContext(RootMenuTriggerStateContext); const isOpen = rootMenuTriggerStateContext?.isOpen ?? false; const { mobileView } = React.useContext(DropdownButtonContext); const closedButton = mobileView ? 'shp-ascending' : 'shp-descending'; const openButton = mobileView ? 'shp-descending' : 'shp-ascending'; const icon = indicateOpen && isOpen ? openButton : closedButton; React.useEffect(() => { if (mobileView || trigger !== 'hover' || !isOpen) { return; } const handler = (e: Event) => { if (e instanceof MouseEvent) { window.requestAnimationFrame(() => { if (document.elementsFromPoint(e.clientX, e.clientY).includes(ref.current!)) { return; } rootMenuTriggerStateContext?.close(); }); } }; // We have no other way to get the underlay element const underlay = document.querySelector("[data-testid='underlay']"); if (underlay) { underlay.addEventListener('mousemove', handler); return () => { underlay.removeEventListener('mousemove', handler); }; } }, [isOpen, mobileView, rootMenuTriggerStateContext, trigger]); const handleHoverStart = React.useCallback(() => { if (mobileView || trigger !== 'hover') { return; } rootMenuTriggerStateContext?.open(); }, [mobileView, rootMenuTriggerStateContext, trigger]); return ( {isOpen ? labelOpen || label : label} ); }; const ConnectedButton = forwardRef( ({ onHoverStart, onHoverChange, onHoverEnd, ...props }, forwardedRef) => { const ref = useObjectRef(forwardedRef); const [contextOptions, contextRef] = useContextProps({}, ref, ButtonContext); const { buttonProps } = useButton(contextOptions, contextRef); const { hoverProps, isHovered } = useHover(mergeProps(contextOptions, { onHoverStart, onHoverChange, onHoverEnd })); const { focusProps, isFocused, isFocusVisible } = useFocusRing(contextOptions); const uiButtonProps = getButtonProps({ ...props, className: 'dropdownButton__trigger' }); return (