'use client'; import React, { useCallback, useEffect, useId, useMemo, useRef } from 'react'; import { useButton, useOverlayTrigger } from 'react-aria'; import { useOverlayTriggerState } from 'react-stately'; import { useBreakpoint } from '../../hook/breakpoints.hook.js'; import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js'; import { Button } from '../button/index.js'; import { DropDownIcon, IconProps } from '../icon/index.js'; import { DropdownPanel } from './components/dropdown-panel/dropdown-panel.component.js'; import { styles as dropdownStyles } from './dropdown.styles.js'; import { type DropdownProps } from './dropdown.types.js'; export function Dropdown({ className, portalClassName, dropdownSize = 'medium', iconBefore: IconBefore, open = false, text, children, size, look = 'hero', soft = false, block = false, portalContainer, dropDownIcon: Icon = DropDownIcon, placement = 'bottom start', shouldFlip, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelBy, shouldCloseOnInteractOutside = () => false, }: DropdownProps) { const ref = useRef(null); const panelId = useId(); const breakpoint = useBreakpoint(); const resolvedLook = resolveResponsiveVariant(look, breakpoint); const resolvedSoft = resolveResponsiveVariant(soft, breakpoint); const styles = dropdownStyles({ block: resolveResponsiveVariant(block, breakpoint), dropdownSize: resolveResponsiveVariant(dropdownSize, breakpoint), }); const state = useOverlayTriggerState({ defaultOpen: open }); const { triggerProps, overlayProps } = useOverlayTrigger({ type: 'menu' }, state, ref); const { buttonProps } = useButton(triggerProps, ref); // React Aria does not check for escape key press unless panel is focused so this is needed const keyHandler = useCallback( (event: globalThis.KeyboardEvent) => { if (state.isOpen && event.key === 'Escape') state.close(); }, [state], ); useEffect(() => { window.document.addEventListener('keydown', keyHandler); return () => { window.document.removeEventListener('keydown', keyHandler); }; }, [keyHandler]); const iconColor = useMemo(() => { if (resolvedLook === 'faint') { return 'muted'; } return resolvedSoft ? 'muted-vivid' : 'mono'; }, [resolvedLook, resolvedSoft]); // This is required so branding applies correctly by default due to portal location, can be overridden with portalContainer prop const brandContainer = useMemo(() => { if (typeof window !== 'undefined') { return ( document.querySelector('[data-brand]') || document.querySelector('[class^="theme-"], [class*=" theme-"]') || document.body ); } }, []); return ( <> {state.isOpen && ( {children} )} ); }