Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add options to set initial focus within modal #476

Merged
merged 5 commits into from Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion react-responsive-modal/__tests__/index.test.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { Modal } from '../src';

describe('modal', () => {
Expand Down
10 changes: 10 additions & 0 deletions react-responsive-modal/cypress/integration/modal.spec.ts
Expand Up @@ -65,4 +65,14 @@ describe('simple modal', () => {
cy.get('[data-testid=modal]').should('not.exist');
cy.get('body').should('not.have.css', 'overflow', 'hidden');
});

it('should focus first element within modal', () => {
cy.get('button').eq(3).click();
cy.get('[data-testid=modal] input').first().should('have.focus');
});

it('should focus on modal root', () => {
cy.get('button').eq(4).click();
cy.get('[data-testid=modal]').should('have.focus');
});
});
5 changes: 3 additions & 2 deletions react-responsive-modal/package.json
Expand Up @@ -47,14 +47,15 @@
"size-limit": [
{
"path": "dist/react-responsive-modal.cjs.production.min.js",
"limit": "3.8 KB"
"limit": "4.0 KB"
},
{
"path": "dist/react-responsive-modal.esm.js",
"limit": "3.8 KB"
"limit": "4.0 KB"
}
],
"dependencies": {
"@bedrock-layout/use-forwarded-ref": "^1.1.4",
pradel marked this conversation as resolved.
Show resolved Hide resolved
"body-scroll-lock": "^3.1.5",
"classnames": "^2.2.6"
},
Expand Down
27 changes: 22 additions & 5 deletions react-responsive-modal/src/FocusTrap.tsx
Expand Up @@ -6,11 +6,16 @@ import {
getAllTabbingElements,
} from './lib/focusTrapJs';

export interface FocusTrapOptions {
focusOn?: React.RefObject<HTMLElement>;
}

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,20 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
) {
refLastFocus.current = document.activeElement as HTMLElement;
}
allTabbingElements[0].focus();
};

if (options?.focusOn) {
savePreviousFocus();
// We need to schedule focusing on a next frame - this allows to focus on the modal root
requestAnimationFrame(() => {
options.focusOn?.current?.focus();
});
} else {
const allTabbingElements = getAllTabbingElements(container.current);
if (allTabbingElements[0]) {
savePreviousFocus();
allTabbingElements[0].focus();
}
}
}
return () => {
Expand All @@ -48,7 +65,7 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
refLastFocus.current?.focus();
}
};
}, [container]);
}, [container, options?.focusOn]);

return null;
};