Skip to content

Commit

Permalink
Add options for FocusTrap to allow better behavior on modal open
Browse files Browse the repository at this point in the history
  • Loading branch information
megawebmaster committed May 20, 2021
1 parent 23cccc6 commit c450167
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 32 deletions.
2 changes: 1 addition & 1 deletion react-responsive-modal/package.json
@@ -1,6 +1,6 @@
{
"name": "react-responsive-modal",
"version": "6.0.1",
"version": "6.1.0",
"description": "A simple responsive and accessible react modal",
"license": "MIT",
"main": "dist/index.js",
Expand Down
24 changes: 19 additions & 5 deletions react-responsive-modal/src/FocusTrap.tsx
Expand Up @@ -6,11 +6,16 @@ import {
getAllTabbingElements,
} from './lib/focusTrapJs';

export interface FocusTrapOptions {
focusOn?: 'firstFocusableElement' | 'modalRoot';
}

interface FocusTrapProps {
container?: React.RefObject<HTMLElement> | null;
options?: FocusTrapOptions;
}

export const FocusTrap = ({ container }: FocusTrapProps) => {
export const FocusTrap = ({ container, options }: FocusTrapProps) => {
const refLastFocus = useRef<HTMLElement | null>();
/**
* Handle focus lock on the modal
Expand All @@ -27,8 +32,7 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
}
// On mount we focus on the first focusable element in the modal if there is one
if (isBrowser && container?.current) {
const allTabbingElements = getAllTabbingElements(container.current);
if (allTabbingElements[0]) {
const savePreviousFocus = () => {
// First we save the last focused element
// only if it's a focusable element
if (
Expand All @@ -38,7 +42,17 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
) {
refLastFocus.current = document.activeElement as HTMLElement;
}
allTabbingElements[0].focus();
};

if (options?.focusOn === 'firstFocusableElement') {
const allTabbingElements = getAllTabbingElements(container.current);
if (allTabbingElements[0]) {
savePreviousFocus();
allTabbingElements[0].focus();
}
} else if (options?.focusOn === 'modalRoot') {
savePreviousFocus();
container?.current?.focus();
}
}
return () => {
Expand All @@ -48,7 +62,7 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
refLastFocus.current?.focus();
}
};
}, [container]);
}, [container, options?.focusOn]);

return null;
};
18 changes: 15 additions & 3 deletions react-responsive-modal/src/index.tsx
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react';
import ReactDom from 'react-dom';
import cx from 'classnames';
import CloseIcon from './CloseIcon';
import { FocusTrap } from './FocusTrap';
import { FocusTrap, FocusTrapOptions } from './FocusTrap';
import { modalManager, useModalManager } from './modalManager';
import { useScrollLock } from './useScrollLock';
import { isBrowser } from './utils';
Expand Down Expand Up @@ -69,6 +69,12 @@ export interface ModalProps {
* Default to true.
*/
focusTrapped?: boolean;
/**
* Options focus trapping.
*
* Default to { focusOn: 'firstFocusableElement' }.
*/
focusTrapOptions?: FocusTrapOptions;
/**
* You can specify a container prop which should be of type `Element`.
* The portal will be rendered inside that element.
Expand Down Expand Up @@ -104,7 +110,7 @@ export interface ModalProps {
/**
* Animation duration in milliseconds.
*
* Default to 500.
* Default to 300.
*/
animationDuration?: number;
/**
Expand Down Expand Up @@ -157,6 +163,7 @@ export const Modal = ({
closeIconId,
closeIcon,
focusTrapped = true,
focusTrapOptions = { focusOn: 'firstFocusableElement' },
animationDuration = 300,
classNames,
styles,
Expand All @@ -171,6 +178,7 @@ export const Modal = ({
children,
}: ModalProps) => {
const refModal = useRef<HTMLDivElement>(null);
const refDialog = useRef<HTMLDivElement>(null);
const refShouldClose = useRef<boolean | null>(null);
const refContainer = useRef<HTMLDivElement | null>(null);
// Lazily create the ref instance
Expand Down Expand Up @@ -314,6 +322,7 @@ export const Modal = ({
onClick={handleClickOverlay}
>
<div
ref={refDialog}
className={cx(classes.modal, classNames?.modal)}
style={{
animation: `${modalAnimation} ${animationDuration}ms`,
Expand All @@ -329,8 +338,11 @@ export const Modal = ({
aria-labelledby={ariaLabelledby}
aria-describedby={ariaDescribedby}
data-testid="modal"
tabIndex={-1}
>
{focusTrapped && <FocusTrap container={refModal} />}
{focusTrapped && (
<FocusTrap container={refDialog} options={focusTrapOptions} />
)}
{children}
{showCloseIcon && (
<CloseIcon
Expand Down

0 comments on commit c450167

Please sign in to comment.