From fd194938a632186511e358f5af3924a9354c2a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 16 Aug 2024 15:17:27 +0200 Subject: [PATCH] Dropdown menu mobile variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../dropdown-menu/dropdown-menu.tsx | 124 +++++++++--------- src/components/sidebar-menu.tsx | 2 +- .../ui/components/modals/actions-modal.tsx | 11 +- 3 files changed, 63 insertions(+), 74 deletions(-) diff --git a/src/components/dropdown-menu/dropdown-menu.tsx b/src/components/dropdown-menu/dropdown-menu.tsx index 528044066e..821a3bbf7d 100644 --- a/src/components/dropdown-menu/dropdown-menu.tsx +++ b/src/components/dropdown-menu/dropdown-menu.tsx @@ -2,10 +2,9 @@ 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 { useHistory } from 'react-router-dom'; +import { FormattedMessage } from 'react-intl'; 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'; @@ -43,15 +42,15 @@ const DropdownMenu = (props: IDropdownMenu) => { placement: initialPlacement = 'top', src = require('@tabler/icons/outline/dots.svg'), title = 'Menu', - ...filteredProps } = props; const dispatch = useAppDispatch(); - const history = useHistory(); const [isOpen, setIsOpen] = useState(false); const [isDisplayed, setIsDisplayed] = useState(false); + const touching = userTouching.matches; + const arrowRef = useRef(null); const { x, y, strategy, refs, middlewareData, placement } = useFloating({ @@ -91,18 +90,8 @@ 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(); @@ -112,12 +101,8 @@ const DropdownMenu = (props: IDropdownMenu) => { const handleClose = () => { (refs.reference.current as HTMLButtonElement)?.focus(); - if (userTouching.matches) { - dispatch(closeModal('ACTIONS')); - } else { - closeDropdownMenu(); - setIsOpen(false); - } + closeDropdownMenu(); + setIsOpen(false); if (onClose) { onClose(); @@ -145,26 +130,6 @@ 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(); @@ -174,7 +139,7 @@ const DropdownMenu = (props: IDropdownMenu) => { 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 +170,7 @@ const DropdownMenu = (props: IDropdownMenu) => { } if (element) { - element.focus(); + (element as HTMLAnchorElement).focus(); e.preventDefault(); e.stopPropagation(); } @@ -265,6 +230,28 @@ const DropdownMenu = (props: IDropdownMenu) => { const autoFocus = !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-hidden fixed left-0 right-0 mx-auto w-[calc(100vw-2rem)] max-w-lg rounded-t-xl duration-200': true, + 'bottom-0 opacity-100': isDisplayed && isOpen, + '-bottom-32 opacity-0': !(isDisplayed && isOpen), + }) : clsx({ + 'rounded-md w-56 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 ( <> {children ? ( @@ -291,24 +278,21 @@ const DropdownMenu = (props: IDropdownMenu) => { {isOpen || isDisplayed ? ( + {touching && ( +
+ )}
{ autoFocus={autoFocus} /> ))} + {touching && ( +
  • + +
  • + )} {/* Arrow */} -
    + {!touching && ( +
    + )}
    ) : null} diff --git a/src/components/sidebar-menu.tsx b/src/components/sidebar-menu.tsx index 0fd14418f3..25b401bc13 100644 --- a/src/components/sidebar-menu.tsx +++ b/src/components/sidebar-menu.tsx @@ -183,7 +183,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { >
    void; onClose: () => void; } -const ActionsModal: React.FC = ({ status, actions, onClick, onClose }) => { +const ActionsModal: React.FC = ({ actions, onClick, onClose }) => { const renderAction = (action: MenuItem | null, i: number) => { if (action === null) { return
  • ; @@ -60,11 +57,7 @@ const ActionsModal: React.FC = ({ status, actions, onClick, onClo className='pointer-events-auto relative z-[9999] m-auto flex max-h-[calc(100vh-3rem)] w-full max-w-lg flex-col overflow-hidden rounded-2xl bg-white text-gray-400 shadow-xl black:bg-black dark:bg-gray-900' style={{ top: `${top}%` }} > - {status && ( - - )} - -
      +
        {actions && actions.map(renderAction)}