import { useCallback, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import { supportsPassiveEvents } from 'detect-passive-events'; import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import type { IconProp } from './icon'; import { Icon } from './icon'; const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; export interface SelectItem { value: string; icon?: string; iconComponent?: IconProp; text: string; meta: string; extra?: string; } interface Props { value: string; classNamePrefix: string; style?: React.CSSProperties; items: SelectItem[]; onChange: (value: string) => void; onClose: () => void; } export const DropdownSelector: React.FC = ({ style, items, value, classNamePrefix = 'privacy-dropdown', onClose, onChange, }) => { const nodeRef = useRef(null); const focusedItemRef = useRef(null); const [currentValue, setCurrentValue] = useState(value); const handleDocumentClick = useCallback( (e: MouseEvent | TouchEvent) => { if ( nodeRef.current && e.target instanceof Node && !nodeRef.current.contains(e.target) ) { onClose(); e.stopPropagation(); } }, [nodeRef, onClose], ); const handleClick = useCallback( ( e: React.MouseEvent | React.KeyboardEvent, ) => { const value = e.currentTarget.getAttribute('data-index'); e.preventDefault(); onClose(); if (value) onChange(value); }, [onClose, onChange], ); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { const value = e.currentTarget.getAttribute('data-index'); const index = items.findIndex((item) => item.value === value); let element: Element | null | undefined = null; switch (e.key) { case 'Escape': onClose(); break; case ' ': case 'Enter': handleClick(e); break; case 'ArrowDown': element = nodeRef.current?.children[index + 1] ?? nodeRef.current?.firstElementChild; break; case 'ArrowUp': element = nodeRef.current?.children[index - 1] ?? nodeRef.current?.lastElementChild; break; case 'Tab': if (e.shiftKey) { element = nodeRef.current?.children[index + 1] ?? nodeRef.current?.firstElementChild; } else { element = nodeRef.current?.children[index - 1] ?? nodeRef.current?.lastElementChild; } break; case 'Home': element = nodeRef.current?.firstElementChild; break; case 'End': element = nodeRef.current?.lastElementChild; break; } if (element && element instanceof HTMLElement) { const selectedValue = element.getAttribute('data-index'); element.focus(); if (selectedValue) setCurrentValue(selectedValue); e.preventDefault(); e.stopPropagation(); } }, [nodeRef, items, onClose, handleClick, setCurrentValue], ); useEffect(() => { document.addEventListener('click', handleDocumentClick, { capture: true }); document.addEventListener('touchend', handleDocumentClick, listenerOptions); focusedItemRef.current?.focus({ preventScroll: true }); return () => { document.removeEventListener('click', handleDocumentClick, { capture: true, }); document.removeEventListener( 'touchend', handleDocumentClick, listenerOptions, ); }; }, [handleDocumentClick]); return (
    {items.map((item) => (
  • {item.icon && item.iconComponent && (
    )}
    {item.text} {item.meta}
    {item.extra && (
    )}
  • ))}
); };