diff --git a/src/actions/modals.ts b/src/actions/modals.ts index 5bebc746e..877e9bd86 100644 --- a/src/actions/modals.ts +++ b/src/actions/modals.ts @@ -24,9 +24,14 @@ const closeModal = (type?: ModalType) => ({ modalType: type, }); +type ModalsAction = + ReturnType + | ReturnType; + export { MODAL_OPEN, MODAL_CLOSE, openModal, closeModal, + type ModalsAction, }; diff --git a/src/components/dropdown-menu/dropdown-menu.tsx b/src/components/dropdown-menu/dropdown-menu.tsx index 227e86b7e..78ef10f66 100644 --- a/src/components/dropdown-menu/dropdown-menu.tsx +++ b/src/components/dropdown-menu/dropdown-menu.tsx @@ -2,10 +2,10 @@ import { offset, Placement, useFloating, flip, arrow, shift } from '@floating-ui import clsx from 'clsx'; import { supportsPassiveEvents } from 'detect-passive-events'; import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; import { closeDropdownMenu as closeDropdownMenuRedux, openDropdownMenu } from 'soapbox/actions/dropdown-menu'; -import { closeModal, openModal } from 'soapbox/actions/modals'; import { useAppDispatch } from 'soapbox/hooks'; import { userTouching } from 'soapbox/is-mobile'; @@ -20,7 +20,8 @@ type Menu = Array; interface IDropdownMenu { children?: React.ReactElement; disabled?: boolean; - items: Menu; + items?: Menu; + component?: React.FC<{ handleClose: () => any }>; onClose?: () => void; onOpen?: () => void; onShiftClick?: React.EventHandler; @@ -37,13 +38,13 @@ const DropdownMenu = (props: IDropdownMenu) => { children, disabled, items, + component: Component, onClose, onOpen, onShiftClick, placement: initialPlacement = 'top', src = require('@tabler/icons/outline/dots.svg'), title = 'Menu', - ...filteredProps } = props; const dispatch = useAppDispatch(); @@ -52,7 +53,11 @@ const DropdownMenu = (props: IDropdownMenu) => { const [isOpen, setIsOpen] = useState(false); const [isDisplayed, setIsDisplayed] = useState(false); + const touching = userTouching.matches; + const arrowRef = useRef(null); + const dropdownHistoryKey = useRef(); + const unlistenHistory = useRef>(); const { x, y, strategy, refs, middlewareData, placement } = useFloating({ placement: initialPlacement, @@ -87,37 +92,30 @@ const DropdownMenu = (props: IDropdownMenu) => { } }; - /** - * On mobile screens, let's replace the Popper dropdown with a Modal. - */ const handleOpen = () => { - if (userTouching.matches) { - dispatch( - openModal('ACTIONS', { - status: filteredProps.status, - actions: items, - onClick: handleItemClick, - }), - ); - } else { - dispatch(openDropdownMenu()); - setIsOpen(true); - } + dispatch(openDropdownMenu()); + setIsOpen(true); if (onOpen) { onOpen(); } }; - const handleClose = () => { + const handleClose = (goBack: any = true) => { (refs.reference.current as HTMLButtonElement)?.focus(); - if (userTouching.matches) { - dispatch(closeModal('ACTIONS')); - } else { - closeDropdownMenu(); - setIsOpen(false); + if (unlistenHistory.current) { + unlistenHistory.current(); + unlistenHistory.current = undefined; } + const { state } = history.location; + if (goBack && state && (state as any).soapboxDropdownKey === dropdownHistoryKey.current) { + history.goBack(); + (history.location.state as any).soapboxDropdownKey = true; + } + + closeDropdownMenu(); + setIsOpen(false); if (onClose) { onClose(); @@ -145,36 +143,17 @@ const DropdownMenu = (props: IDropdownMenu) => { } }; - const handleItemClick: React.EventHandler = (event) => { - event.preventDefault(); - event.stopPropagation(); - - const i = Number(event.currentTarget.getAttribute('data-index')); - const item = items[i]; - if (!item) return; - - const { action, to } = item; - - handleClose(); - - if (typeof action === 'function') { - action(event); - } else if (to) { - dispatch(closeModal('MEDIA')); - history.push(to); - } - }; - const handleDocumentClick = (event: Event) => { if (refs.floating.current && !refs.floating.current.contains(event.target as Node)) { handleClose(); + event.stopPropagation(); } }; const handleKeyDown = (e: KeyboardEvent) => { if (!refs.floating.current) return; - const items = Array.from(refs.floating.current.getElementsByTagName('a')); + const items = Array.from(refs.floating.current.querySelectorAll('a, button')); const index = items.indexOf(document.activeElement as any); let element = null; @@ -205,7 +184,7 @@ const DropdownMenu = (props: IDropdownMenu) => { } if (element) { - element.focus(); + (element as HTMLAnchorElement).focus(); e.preventDefault(); e.stopPropagation(); } @@ -257,13 +236,51 @@ const DropdownMenu = (props: IDropdownMenu) => { useEffect(() => { setTimeout(() => setIsDisplayed(isOpen), isOpen ? 0 : 150); + + if (isOpen && touching) { + const { pathname, state } = history.location; + + dropdownHistoryKey.current = Date.now(); + + history.push(pathname, { ...(state as any), soapboxDropdownKey: dropdownHistoryKey.current }); + + unlistenHistory.current = history.listen(({ state }, action) => { + if (!(state as any)?.soapboxDropdownKey) { + handleClose(false); + } else if (action === 'POP') { + handleClose(false); + } + }); + } }, [isOpen]); - if (items.length === 0) { + if (items?.length === 0 && !Component) { return null; } - const autoFocus = !items.some((item) => item?.active); + const autoFocus = items && !items.some((item) => item?.active); + + const getClassName = () => { + const className = clsx('z-[1001] bg-white py-1 shadow-lg ease-in-out focus:outline-none black:bg-black no-reduce-motion:transition-all dark:bg-gray-900 dark:ring-2 dark:ring-primary-700', touching ? clsx({ + 'overflow-auto fixed left-0 right-0 mx-auto w-[calc(100vw-2rem)] max-w-lg max-h-[calc(100dvh-1rem)] rounded-t-xl duration-200': true, + 'bottom-0 opacity-100': isDisplayed && isOpen, + '-bottom-32 opacity-0': !(isDisplayed && isOpen), + }) : clsx({ + 'rounded-md min-w-56 max-w-sm duration-100': true, + 'scale-0': !(isDisplayed && isOpen), + 'scale-100': isDisplayed && isOpen, + 'origin-bottom': placement === 'top', + 'origin-left': placement === 'right', + 'origin-top': placement === 'bottom', + 'origin-right': placement === 'left', + 'origin-bottom-left': placement === 'top-start', + 'origin-bottom-right': placement === 'top-end', + 'origin-top-left': placement === 'bottom-start', + 'origin-top-right': placement === 'bottom-end', + })); + + return className; + }; return ( <> @@ -291,31 +308,28 @@ const DropdownMenu = (props: IDropdownMenu) => { {isOpen || isDisplayed ? ( + {touching && ( +
+ )}
+ {Component && }
    - {items.map((item, idx) => ( + {items?.map((item, idx) => ( { autoFocus={autoFocus} /> ))} + {touching && ( +
  • + +
  • + )}
{/* Arrow */} -
+ {!touching && ( +
+ )}
) : null} diff --git a/src/components/modal-root.tsx b/src/components/modal-root.tsx index d04f731c8..1a4ab2572 100644 --- a/src/components/modal-root.tsx +++ b/src/components/modal-root.tsx @@ -8,7 +8,8 @@ import { cancelReplyCompose } from 'soapbox/actions/compose'; import { saveDraftStatus } from 'soapbox/actions/draft-statuses'; import { cancelEventCompose } from 'soapbox/actions/events'; import { openModal, closeModal } from 'soapbox/actions/modals'; -import { useAppDispatch, usePrevious } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, usePrevious } from 'soapbox/hooks'; +import { userTouching } from 'soapbox/is-mobile'; import type { ModalType } from 'soapbox/features/ui/components/modal-root'; import type { ReducerCompose } from 'soapbox/reducers/compose'; @@ -49,6 +50,8 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) const dispatch = useAppDispatch(); const [revealed, setRevealed] = useState(!!children); + const isDropdownOpen = useAppSelector(state => state.dropdown_menu.isOpen); + const wasDropdownOpen = usePrevious(isDropdownOpen); const ref = useRef(null); const activeElement = useRef(revealed ? document.activeElement as HTMLDivElement | null : null); @@ -56,7 +59,6 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) const unlistenHistory = useRef>(); const prevChildren = usePrevious(children); - const prevType = usePrevious(type); const visible = !!children; @@ -158,7 +160,7 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) }); }; - const handleModalClose = (type: string) => { + const handleModalClose = () => { if (unlistenHistory.current) { unlistenHistory.current(); } @@ -206,7 +208,7 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) activeElement.current = null; getSiblings().forEach(sibling => (sibling as HTMLDivElement).removeAttribute('inert')); - handleModalClose(prevType!); + handleModalClose(); } if (children) { @@ -218,6 +220,17 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) } }, [children]); + useEffect(() => { + if (!userTouching.matches) return; + + if (isDropdownOpen && unlistenHistory.current) { + unlistenHistory.current(); + } else if (!isDropdownOpen && wasDropdownOpen) { + // TODO find a better solution + setTimeout(() => handleModalOpen(), 100); + } + }, [isDropdownOpen]); + if (!visible) { return (
@@ -227,8 +240,7 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) return (
{ const handleSwitchAccount = (account: AccountEntity): React.MouseEventHandler => (e) => { e.preventDefault(); + e.stopPropagation(); + dispatch(switchAccount(account.id)); }; const onClickLogOut: React.MouseEventHandler = (e) => { e.preventDefault(); + e.stopPropagation(); + dispatch(logOut()); }; const handleSwitcherClick: React.MouseEventHandler = (e) => { e.preventDefault(); + e.stopPropagation(); setSwitcher((prevState) => (!prevState)); }; @@ -183,7 +188,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { >
(({ }, ref) => { const intl = useIntl(); const buttonRef = React.useRef(null); + const firstRender = React.useRef(true); + + React.useEffect(() => { + if (firstRender.current) { + firstRender.current = false; + } + }, []); React.useEffect(() => { if (buttonRef?.current && !skipFocus) { @@ -95,7 +102,10 @@ const Modal = React.forwardRef(({
diff --git a/src/features/compose/components/language-dropdown.tsx b/src/features/compose/components/language-dropdown.tsx index 2e650fa92..5192f30a4 100644 --- a/src/features/compose/components/language-dropdown.tsx +++ b/src/features/compose/components/language-dropdown.tsx @@ -1,6 +1,4 @@ -import { offset, useFloating, flip, arrow, shift } from '@floating-ui/react'; import clsx from 'clsx'; -import { supportsPassiveEvents } from 'detect-passive-events'; import fuzzysort from 'fuzzysort'; import { Map as ImmutableMap } from 'immutable'; import React, { useEffect, useMemo, useRef, useState } from 'react'; @@ -8,7 +6,8 @@ import { defineMessages, useIntl } from 'react-intl'; import { createSelector } from 'reselect'; import { addComposeLanguage, changeComposeLanguage, changeComposeModifiedLanguage, deleteComposeLanguage } from 'soapbox/actions/compose'; -import { Button, Icon, Input, Portal } from 'soapbox/components/ui'; +import DropdownMenu from 'soapbox/components/dropdown-menu'; +import { Button, Icon, Input } from 'soapbox/components/ui'; import { type Language, languages as languagesObject } from 'soapbox/features/preferences'; import { useAppDispatch, useAppSelector, useCompose, useFeatures } from 'soapbox/hooks'; @@ -21,8 +20,6 @@ const getFrequentlyUsedLanguages = createSelector([ .toArray() )); -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; - const languages = Object.entries(languagesObject) as Array<[Language, string]>; const messages = defineMessages({ @@ -35,95 +32,26 @@ const messages = defineMessages({ }); interface ILanguageDropdown { - composeId: string; + handleClose: () => any; } -const LanguageDropdown: React.FC = ({ composeId }) => { +const getLanguageDropdown = (composeId: string): React.FC => ({ handleClose: handleMenuClose }) => { const intl = useIntl(); const features = useFeatures(); const dispatch = useAppDispatch(); const frequentlyUsedLanguages = useAppSelector(getFrequentlyUsedLanguages); const node = useRef(null); - const focusedItem = useRef(null); - const arrowRef = useRef(null); + const focusedItem = useRef(null); - const [isOpen, setIsOpen] = useState(false); const [searchValue, setSearchValue] = useState(''); - const { x, y, strategy, refs, middlewareData, placement } = useFloating({ - placement: 'top', - middleware: [ - offset(12), - flip(), - shift({ - padding: 8, - }), - arrow({ - element: arrowRef, - }), - ], - }); - const { language, modified_language: modifiedLanguage, - suggested_language: suggestedLanguage, textMap, } = useCompose(composeId); - const handleClick: React.EventHandler< - React.MouseEvent | React.KeyboardEvent - > = (event) => { - event.stopPropagation(); - - setIsOpen(!isOpen); - }; - - const handleKeyPress: React.EventHandler> = (event) => { - switch (event.key) { - case ' ': - case 'Enter': - event.stopPropagation(); - event.preventDefault(); - handleClick(event); - break; - } - }; - - const handleOptionKeyDown: React.KeyboardEventHandler = e => { - const value = e.currentTarget.getAttribute('data-index'); - const index = results.findIndex(([key]) => key === value); - let element: ChildNode | null | undefined = null; - - switch (e.key) { - case 'Escape': - handleClose(); - break; - case 'Enter': - handleOptionClick(e); - break; - case 'ArrowDown': - element = node.current?.childNodes[index + 1] || node.current?.firstChild; - break; - case 'ArrowUp': - element = node.current?.childNodes[index - 1] || node.current?.lastChild; - break; - case 'Home': - element = node.current?.firstChild; - break; - case 'End': - element = node.current?.lastChild; - break; - } - - if (element) { - (element as HTMLElement).focus(); - e.preventDefault(); - e.stopPropagation(); - } - }; - const handleOptionClick: React.EventHandler = (e: MouseEvent | KeyboardEvent) => { const value = (e.currentTarget as HTMLElement)?.getAttribute('data-index') as Language; @@ -146,7 +74,6 @@ const LanguageDropdown: React.FC = ({ composeId }) => { e.preventDefault(); e.stopPropagation(); - handleClose(); dispatch(addComposeLanguage(composeId, value)); }; @@ -156,7 +83,6 @@ const LanguageDropdown: React.FC = ({ composeId }) => { e.preventDefault(); e.stopPropagation(); - handleClose(); dispatch(deleteComposeLanguage(composeId, value)); }; @@ -202,104 +128,105 @@ const LanguageDropdown: React.FC = ({ composeId }) => { }).map(result => result.obj); }; - const handleDocumentClick = (event: Event) => { - if (refs.floating.current && !refs.floating.current.contains(event.target as Node)) { - handleClose(); - } - }; - - const handleKeyDown = (e: KeyboardEvent) => { - if (!refs.floating.current) return; - - const items = Array.from(refs.floating.current.getElementsByTagName('a')); - const index = items.indexOf(document.activeElement as any); - - let element = null; - - switch (e.key) { - case 'ArrowDown': - element = items[index + 1] || items[0]; - break; - case 'ArrowUp': - element = items[index - 1] || items[items.length - 1]; - break; - case 'Tab': - if (e.shiftKey) { - element = items[index - 1] || items[items.length - 1]; - } else { - element = items[index + 1] || items[0]; - } - break; - case 'Home': - element = items[0]; - break; - case 'End': - element = items[items.length - 1]; - break; - case 'Escape': - handleClose(); - break; - } - - if (element) { - element.focus(); - e.preventDefault(); - e.stopPropagation(); - } - }; - const handleClose = () => { setSearchValue(''); - setIsOpen(false); + handleMenuClose(); }; - const arrowProps: React.CSSProperties = useMemo(() => { - if (middlewareData.arrow) { - const { x, y } = middlewareData.arrow; - - const staticPlacement = { - top: 'bottom', - right: 'left', - bottom: 'top', - left: 'right', - }[placement.split('-')[0]]; - - return { - left: x !== null ? `${x}px` : '', - top: y !== null ? `${y}px` : '', - // Ensure the static side gets unset when - // flipping to other placements' axes. - right: '', - bottom: '', - [staticPlacement as string]: `${(-(arrowRef.current?.offsetWidth || 0)) / 2}px`, - transform: 'rotate(45deg)', - }; - } - - return {}; - }, [middlewareData.arrow, placement]); - useEffect(() => { - if (isOpen) { - if (refs.floating.current) { - (refs.floating.current?.querySelector('div[aria-selected=true]') as HTMLDivElement)?.focus(); - } - - document.addEventListener('click', handleDocumentClick, false); - document.addEventListener('keydown', handleKeyDown, false); - document.addEventListener('touchend', handleDocumentClick, listenerOptions); - - return () => { - document.removeEventListener('click', handleDocumentClick); - document.removeEventListener('keydown', handleKeyDown); - document.removeEventListener('touchend', handleDocumentClick); - }; + if (node.current) { + (node.current?.querySelector('div[aria-selected=true]') as HTMLDivElement)?.focus(); } - }, [isOpen, refs.floating.current]); + }, [node.current]); const isSearching = searchValue !== ''; const results = search(); + return ( + <> + +
+ {results.map(([code, name]) => { + const active = code === language; + const modified = code === modifiedLanguage; + + return ( + + ) : ( + + ) + )} + + ); + })} +
+ + ); +}; + +interface ILanguageDropdownButton { + composeId: string; +} + +const LanguageDropdownButton: React.FC = ({ composeId }) => { + const intl = useIntl(); + + const { + language, + modified_language: modifiedLanguage, + suggested_language: suggestedLanguage, + textMap, + } = useCompose(composeId); + let buttonLabel = intl.formatMessage(messages.languagePrompt); if (language) { const list: string[] = [languagesObject[modifiedLanguage || language]]; @@ -311,8 +238,12 @@ const LanguageDropdown: React.FC = ({ composeId }) => { language: languagesObject[suggestedLanguage as Language] || suggestedLanguage, }); + const LanguageDropdown = useMemo(() => getLanguageDropdown(composeId), [composeId]); + return ( - <> + - ) : ( - - ) - )} -
- ); - })} -
-
-
- - ) : null} - + ); }; -export { LanguageDropdown as default }; +export { LanguageDropdownButton as default }; diff --git a/src/features/compose/components/privacy-dropdown.tsx b/src/features/compose/components/privacy-dropdown.tsx index 1b544b6e5..5473d0607 100644 --- a/src/features/compose/components/privacy-dropdown.tsx +++ b/src/features/compose/components/privacy-dropdown.tsx @@ -1,19 +1,12 @@ import clsx from 'clsx'; -import { supportsPassiveEvents } from 'detect-passive-events'; -import React, { useState, useRef, useEffect } from 'react'; +import React, { useRef } from 'react'; import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; -import { spring } from 'react-motion'; -// @ts-ignore -import Overlay from 'react-overlays/lib/Overlay'; import { changeComposeFederated, changeComposeVisibility } from 'soapbox/actions/compose'; -import { closeModal, openModal } from 'soapbox/actions/modals'; +import DropdownMenu from 'soapbox/components/dropdown-menu'; import Icon from 'soapbox/components/icon'; import { Button, Toggle } from 'soapbox/components/ui'; import { useAppDispatch, useCompose, useFeatures } from 'soapbox/hooks'; -import { userTouching } from 'soapbox/is-mobile'; - -import Motion from '../../ui/util/optional-motion'; const messages = defineMessages({ public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, @@ -33,8 +26,6 @@ const messages = defineMessages({ local: { id: 'privacy.local', defaultMessage: '{privacy} (local-only)' }, }); -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; - interface Option { icon: string; value: string; @@ -43,10 +34,8 @@ interface Option { } interface IPrivacyDropdownMenu { - style?: React.CSSProperties; items: any[]; value: string; - placement: string; onClose: () => void; onChange: (value: string | null) => void; unavailable?: boolean; @@ -56,21 +45,14 @@ interface IPrivacyDropdownMenu { } const PrivacyDropdownMenu: React.FC = ({ - style, items, placement, value, onClose, onChange, showFederated, federated, onChangeFederated, + items, value, onClose, onChange, showFederated, federated, onChangeFederated, }) => { - const node = useRef(null); - const focusedItem = useRef(null); - - const [mounted, setMounted] = useState(false); - - const handleDocumentClick = (e: MouseEvent | TouchEvent) => { - if (node.current && !node.current.contains(e.target as HTMLElement)) { - onClose(); - } - }; + const node = useRef(null); + const focusedItem = useRef(null); const handleKeyDown: React.KeyboardEventHandler = e => { - const index = [...e.currentTarget.parentElement!.children].indexOf(e.currentTarget); let element: ChildNode | null | undefined = null; + const index = [...e.currentTarget.parentElement!.children].indexOf(e.currentTarget); + let element: ChildNode | null | undefined = null; switch (e.key) { case 'Escape': @@ -121,96 +103,67 @@ const PrivacyDropdownMenu: React.FC = ({ } }; - useEffect(() => { - document.addEventListener('click', handleDocumentClick, false); - document.addEventListener('touchend', handleDocumentClick, listenerOptions); - - focusedItem.current?.focus({ preventScroll: true }); - setMounted(true); - - return () => { - document.removeEventListener('click', handleDocumentClick, false); - document.removeEventListener('touchend', handleDocumentClick); - }; - }, []); - return ( - - {({ opacity, scaleX, scaleY }) => ( - // It should not be transformed when mounting because the resulting - // size will be used to determine the coordinate of the menu by - // react-overlays -
- {items.map(item => { - const active = item.value === value; - return ( -
-
- -
- -
- {item.text} - {item.meta} -
-
- ); - })} - {showFederated && ( -
-
- -
- -
- - - - -
- - +
    + {items.map(item => { + const active = item.value === value; + return ( +
  • +
    +
    - )} -
+ +
+ {item.text} + {item.meta} +
+ + ); + })} + {showFederated && ( +
  • +
    + +
    + +
    + + + + +
    + + +
  • )} - + + ); }; @@ -223,8 +176,6 @@ const PrivacyDropdown: React.FC = ({ }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const node = useRef(null); - const activeElement = useRef(null); const features = useFeatures(); const compose = useCompose(composeId); @@ -232,9 +183,6 @@ const PrivacyDropdown: React.FC = ({ const value = compose.privacy; const unavailable = compose.id; - const [open, setOpen] = useState(false); - const [placement, setPlacement] = useState('bottom'); - const options = [ { icon: require('@tabler/icons/outline/world.svg'), value: 'public', text: intl.formatMessage(messages.public_short), meta: intl.formatMessage(messages.public_long) }, { icon: require('@tabler/icons/outline/lock-open.svg'), value: 'unlisted', text: intl.formatMessage(messages.unlisted_short), meta: intl.formatMessage(messages.unlisted_long) }, @@ -248,70 +196,6 @@ const PrivacyDropdown: React.FC = ({ const onChangeFederated = () => dispatch(changeComposeFederated(composeId)); - const onModalOpen = (props: Record) => dispatch(openModal('ACTIONS', props)); - - const onModalClose = () => dispatch(closeModal('ACTIONS')); - - const handleToggle: React.MouseEventHandler = (e) => { - if (userTouching.matches) { - if (open) { - onModalClose(); - } else { - onModalOpen({ - actions: options.map(option => ({ ...option, active: option.value === value })), - onClick: handleModalActionClick, - }); - } - } else { - const { top } = e.currentTarget.getBoundingClientRect(); - if (open) { - activeElement.current?.focus(); - } - setPlacement(top * 2 < innerHeight ? 'bottom' : 'top'); - setOpen(!open); - } - e.stopPropagation(); - }; - - const handleModalActionClick: React.MouseEventHandler = (e) => { - e.preventDefault(); - - const { value } = options[e.currentTarget.getAttribute('data-index') as any]; - - onModalClose(); - onChange(value); - }; - - const handleKeyDown: React.KeyboardEventHandler = e => { - switch (e.key) { - case 'Escape': - handleClose(); - break; - } - }; - - const handleMouseDown = () => { - if (!open) { - activeElement.current = document.activeElement as HTMLElement | null; - } - }; - - const handleButtonKeyDown: React.KeyboardEventHandler = (e) => { - switch (e.key) { - case ' ': - case 'Enter': - handleMouseDown(); - break; - } - }; - - const handleClose = () => { - if (open) { - activeElement.current?.focus(); - } - setOpen(false); - }; - if (unavailable) { return null; } @@ -319,41 +203,30 @@ const PrivacyDropdown: React.FC = ({ const valueOption = options.find(item => item.value === value); return ( -
    -
    -
    - - + ( - -
    + )} + > +