fix: focustrap activates on mount to prevent throw

pull/3074/head
Audric Ackermann 1 month ago
parent 85c247eaf0
commit 53aeb63894

@ -1,27 +1,21 @@
import FocusTrap from 'focus-trap-react';
import React from 'react';
import styled from 'styled-components';
const DefaultFocusButton = styled.button`
opacity: 0;
width: 0;
height: 0;
`;
import React, { useState } from 'react';
import { useMount } from 'react-use';
/**
* The FocusTrap always require at least one element to be tabbable.
* On some dialogs, we don't have any until the content is loaded, and depending on what is loaded, we might not have any tabbable elements in it.
* This component renders the children inside a FocusTrap and always adds an invisible button to make FocusTrap happy.
* Focus trap which activates on mount.
*/
export function SessionDialogFocusTrap(props: { children: React.ReactNode }) {
// FocusTrap needs a single child so props.children and the default button needs to be wrapped in a container.
// This might cause some styling issues, but I didn't find any with our current dialogs
export function SessionFocusTrap(props: { children: React.ReactNode }) {
const [active, setActive] = useState(false);
// Activate the trap on mount so we **should** have a button to tab through. focus-trap-react will throw if we don't have a button when the trap becomes active.
// We might still have an issue for dialogs which have buttons added with a useEffect, or asynchronously but have no buttons on mount.
// If that happens we will need to force those dialogs to have at least one button so focus-trap-react does not throw.
useMount(() => setActive(true));
return (
<FocusTrap focusTrapOptions={{ initialFocus: false, allowOutsideClick: true }}>
<div style={{ position: 'absolute' }}>
<DefaultFocusButton />
{props.children}
</div>
<FocusTrap active={active} focusTrapOptions={{ initialFocus: false, allowOutsideClick: true }}>
{props.children}
</FocusTrap>
);
}

@ -5,7 +5,7 @@ import useKey from 'react-use/lib/useKey';
import { SessionIconButton } from './icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
import { SessionDialogFocusTrap } from './SessionFocusTrap';
import { SessionFocusTrap } from './SessionFocusTrap';
export type SessionWrapperModalType = {
title?: string;
@ -64,7 +64,7 @@ export const SessionWrapperModal = (props: SessionWrapperModalType) => {
};
return (
<SessionDialogFocusTrap>
<SessionFocusTrap>
<div
className={classNames('loki-dialog modal', additionalClassName || null)}
onClick={handleClick}
@ -128,6 +128,6 @@ export const SessionWrapperModal = (props: SessionWrapperModalType) => {
</div>
</div>
</div>
</SessionDialogFocusTrap>
</SessionFocusTrap>
);
};

@ -1,4 +1,3 @@
import FocusTrap from 'focus-trap-react';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
@ -18,6 +17,7 @@ import {
SessionButtonType,
} from '../../basic/SessionButton';
import { SessionIconButton } from '../../icon';
import { SessionFocusTrap } from '../../SessionFocusTrap';
export const SelectionOverlay = () => {
const selectedMessageIds = useSelector(getSelectedMessageIds);
@ -70,7 +70,7 @@ export const SelectionOverlay = () => {
const classNameAndId = 'message-selection-overlay';
return (
<FocusTrap focusTrapOptions={{ initialFocus: false, allowOutsideClick: true }}>
<SessionFocusTrap>
<div className={classNameAndId} id={classNameAndId}>
<div className="close-button">
<SessionIconButton iconType="exit" iconSize="medium" onClick={onCloseOverlay} />
@ -94,6 +94,6 @@ export const SelectionOverlay = () => {
/>
</div>
</div>
</FocusTrap>
</SessionFocusTrap>
);
};

Loading…
Cancel
Save