import { useI18n } from 'domains/i18n/hooks' import { className } from 'lib/css' import { useEffect, useMemo, useRef, useState } from 'preact/hooks' import Icon from 'ui/components/layout/icon' import InOutTransition, { transitionStartStates, } from 'ui/components/widgets/in-out-transition' import { timeout } from 'ui/hooks/focus-helper-hooks' import { useOptionButton, useSeamlyOptions } from 'ui/hooks/seamly-hooks' import { focusElement, getKey, keyNames } from 'ui/utils/general-utils' import Options from './options' const OptionsButton = () => { const { t } = useI18n() const { menuOptions, showOption, panelActive, hideOption } = useSeamlyOptions() const { id } = useOptionButton() const focusOutDelayTimeoutID = useRef | null>( null, ) const [menuIsOpen, setMenuIsOpen] = useState(false) const toggleButton = useRef(null) const optionsLength = menuOptions.length const menuItemButtons = useRef>([]) const prevMenuIsOpen = useRef(false) const multiMenu = optionsLength > 1 const firstOption = menuOptions[0] const firstOptionName = useMemo( () => firstOption?.name?.trim().replace(/\s+/g, ''), [firstOption], ) useEffect(() => { return () => { if (focusOutDelayTimeoutID.current) clearTimeout(focusOutDelayTimeoutID.current) } }, []) useEffect(() => { if (menuIsOpen && !prevMenuIsOpen.current) { requestAnimationFrame(async () => { await timeout(60) // Wait for next frame tick const firstActiveOptionIndex = menuOptions.findIndex( (option) => option.available, ) const focusIndex = firstActiveOptionIndex === -1 ? 0 : firstActiveOptionIndex if (menuItemButtons.current) focusElement(menuItemButtons.current[focusIndex]) }) } prevMenuIsOpen.current = menuIsOpen }, [menuIsOpen, menuOptions]) const onClickHandler = () => { if (panelActive) { hideOption() } if (multiMenu) { setMenuIsOpen((o) => !o) } else if (firstOption.available && !panelActive) { showOption(firstOption.name) } } const onMainKeyDownHandler = (e: KeyboardEvent) => { if (!menuIsOpen) { return } if (getKey(e) === keyNames.Escape) { setMenuIsOpen(false) focusElement(toggleButton.current) } if (getKey(e) === keyNames.Home) { if (menuItemButtons.current) focusElement(menuItemButtons.current[0]) e.preventDefault() } if (getKey(e) === keyNames.End) { if (menuItemButtons.current) focusElement(menuItemButtons.current[optionsLength - 1]) e.preventDefault() } } const onButtonKeyDownHandler = (e: KeyboardEvent) => { if (getKey(e) === keyNames.ArrowDown) { setMenuIsOpen(true) e.preventDefault() } } const onMenuItemKeyDownHandler = (e, index) => { if (getKey(e) === keyNames.ArrowDown) { const newIndex = Math.min(optionsLength - 1, index + 1) if (menuItemButtons.current) focusElement(menuItemButtons.current[index === newIndex ? 0 : newIndex]) e.preventDefault() } if (getKey(e) === keyNames.ArrowUp) { const newIndex = Math.max(0, index - 1) if (menuItemButtons.current) focusElement( menuItemButtons.current[ index === newIndex ? optionsLength - 1 : newIndex ], ) e.preventDefault() } } const onFocusOutHandler = () => { if (multiMenu) { focusOutDelayTimeoutID.current = setTimeout(() => { setMenuIsOpen(false) }, 200) } } const onFocusInHandler = () => { if (focusOutDelayTimeoutID.current) clearTimeout(focusOutDelayTimeoutID.current) } const onKeyPressHandler = (e, index) => { const char = String.fromCharCode(e.charCode) const isPrintableChar = !!(char.length === 1 && char.match(/\S/)) if (!isPrintableChar) { return } let foundIndex = -1 menuOptions.forEach((option, i) => { if ( i > index && option.title.charAt(0).toLowerCase() === char.toLowerCase() && foundIndex === -1 ) { foundIndex = i } }) if (foundIndex !== -1 && menuItemButtons.current) { focusElement(menuItemButtons.current[foundIndex]) } } const onMenuItemClickHandler = (name, available) => { if (available) { setMenuIsOpen(false) showOption(name) } } if (!optionsLength) { return null } return (
{multiMenu && (
    {menuOptions.map(({ name, title, available }, i) => (
  • ))}
)}
) } export default OptionsButton