Fix navigation in modal + dropdown combination

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-08-17 00:26:29 +02:00
parent 6ea766d93b
commit f48dc1f0b8
4 changed files with 23 additions and 27 deletions

View file

@ -24,9 +24,14 @@ const closeModal = (type?: ModalType) => ({
modalType: type,
});
type ModalsAction =
ReturnType<typeof openModalSuccess>
| ReturnType<typeof closeModal>;
export {
MODAL_OPEN,
MODAL_CLOSE,
openModal,
closeModal,
type ModalsAction,
};

View file

@ -111,6 +111,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
const { state } = history.location;
if (goBack && state && (state as any).soapboxDropdownKey === dropdownHistoryKey.current) {
history.goBack();
(history.location.state as any).soapboxDropdownKey = true;
}
closeDropdownMenu();
@ -145,6 +146,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
const handleDocumentClick = (event: Event) => {
if (refs.floating.current && !refs.floating.current.contains(event.target as Node)) {
handleClose();
event.stopPropagation();
}
};
@ -244,7 +246,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
unlistenHistory.current = history.listen(({ state }, action) => {
if (!(state as any)?.soapboxDropdownKey) {
handleClose();
handleClose(false);
} else if (action === 'POP') {
handleClose(false);
}

View file

@ -8,7 +8,7 @@ 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 type { ModalType } from 'soapbox/features/ui/components/modal-root';
import type { ReducerCompose } from 'soapbox/reducers/compose';
@ -49,6 +49,8 @@ const ModalRoot: React.FC<IModalRoot> = ({ 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<HTMLDivElement>(null);
const activeElement = useRef<HTMLDivElement | null>(revealed ? document.activeElement as HTMLDivElement | null : null);
@ -56,7 +58,6 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
const unlistenHistory = useRef<ReturnType<typeof history.listen>>();
const prevChildren = usePrevious(children);
const prevType = usePrevious(type);
const visible = !!children;
@ -158,7 +159,7 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
});
};
const handleModalClose = (type: string) => {
const handleModalClose = () => {
if (unlistenHistory.current) {
unlistenHistory.current();
}
@ -206,7 +207,7 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
activeElement.current = null;
getSiblings().forEach(sibling => (sibling as HTMLDivElement).removeAttribute('inert'));
handleModalClose(prevType!);
handleModalClose();
}
if (children) {
@ -218,6 +219,15 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
}
}, [children]);
useEffect(() => {
if (isDropdownOpen && unlistenHistory.current) {
unlistenHistory.current();
} else if (!isDropdownOpen && wasDropdownOpen) {
// TODO find a better solution
setTimeout(() => handleModalOpen(), 100);
}
}, [isDropdownOpen]);
if (!visible) {
return (
<div className='z-50 transition-all' ref={ref} style={{ opacity: 0 }} />

View file

@ -1,6 +1,5 @@
import clsx from 'clsx';
import { supportsPassiveEvents } from 'detect-passive-events';
import React, { useRef, useEffect } from 'react';
import React, { useRef } from 'react';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { changeComposeFederated, changeComposeVisibility } from 'soapbox/actions/compose';
@ -28,8 +27,6 @@ const messages = defineMessages({
local: { id: 'privacy.local', defaultMessage: '{privacy} (local-only)' },
});
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
interface Option {
icon: string;
value: string;
@ -54,12 +51,6 @@ const PrivacyDropdownMenu: React.FC<IPrivacyDropdownMenu> = ({
const node = useRef<HTMLUListElement>(null);
const focusedItem = useRef<HTMLLIElement>(null);
const handleDocumentClick = (e: MouseEvent | TouchEvent) => {
if (node.current && !node.current.contains(e.target as HTMLElement)) {
onClose();
}
};
const handleKeyDown: React.KeyboardEventHandler = e => {
const index = [...e.currentTarget.parentElement!.children].indexOf(e.currentTarget);
let element: ChildNode | null | undefined = null;
@ -113,18 +104,6 @@ const PrivacyDropdownMenu: React.FC<IPrivacyDropdownMenu> = ({
}
};
useEffect(() => {
document.addEventListener('click', handleDocumentClick, false);
document.addEventListener('touchend', handleDocumentClick, listenerOptions);
focusedItem.current?.focus({ preventScroll: true });
return () => {
document.removeEventListener('click', handleDocumentClick, false);
document.removeEventListener('touchend', handleDocumentClick);
};
}, []);
return (
<ul ref={node}>
{items.map(item => {