Skip to content
This repository has been archived by the owner on Oct 19, 2021. It is now read-only.

Commit

Permalink
feat(modal): make modal a focus trap (#2096)
Browse files Browse the repository at this point in the history
* fix(modal): add focus-trap-react package

* fix(modal): make modal a focus trap

* chore(modal): update snapshots

* fix(modal): move focus trap to outer-inner modal

* fix(modal): remove focusButton, onBlur, onTransitionEnd

* fix(modal): add asudoh suggested changes

* fix(modal): reverting changes

* chore(modal): update snapshot

* fix(modal): get tests passing

* fix(modal): add check for open modal

* chore(modal): update snapshot
  • Loading branch information
dakahn authored and joshblack committed Mar 28, 2019
1 parent a7c293c commit b9f5d9e
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 141 deletions.
Binary file added .yarn-offline-mirror/focus-trap-4.0.2.tgz
Binary file not shown.
Binary file added .yarn-offline-mirror/focus-trap-react-6.0.0.tgz
Binary file not shown.
Binary file added .yarn-offline-mirror/tabbable-3.1.2.tgz
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
"classnames": "2.2.6",
"downshift": "^1.31.14",
"flatpickr": "4.5.5",
"focus-trap-react": "^6.0.0",
"invariant": "^2.2.3",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
Expand Down
15 changes: 9 additions & 6 deletions src/components/Modal/Modal-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,34 @@ import ModalWrapper from '../ModalWrapper';
import { shallow, mount } from 'enzyme';
import { componentsX } from '../../internal/FeatureFlags';

// The modal is the 0th child inside the wrapper on account of focus-trap-react
const getModal = wrapper => wrapper.childAt(0);

describe('Modal', () => {
describe('Renders as expected', () => {
const wrapper = shallow(<Modal className="extra-class" />);
const mounted = mount(<Modal className="extra-class" />);

it('has the expected classes', () => {
expect(wrapper.hasClass('bx--modal')).toEqual(true);
expect(getModal(wrapper).hasClass('bx--modal')).toEqual(true);
});

it('should add extra classes that are passed via className', () => {
expect(wrapper.hasClass('extra-class')).toEqual(true);
expect(getModal(wrapper).hasClass('extra-class')).toEqual(true);
});

it('should not be a passive modal by default', () => {
expect(wrapper.hasClass('bx--modal-tall')).toEqual(true);
expect(getModal(wrapper).hasClass('bx--modal-tall')).toEqual(true);
});

it('should be a passive modal when passiveModal is passed', () => {
wrapper.setProps({ passiveModal: true });
expect(wrapper.hasClass('bx--modal-tall')).toEqual(false);
expect(getModal(wrapper).hasClass('bx--modal-tall')).toEqual(false);
});

it('should set id if one is passed via props', () => {
const modal = shallow(<Modal id="modal-1" />);
expect(modal.props().id).toEqual('modal-1');
expect(getModal(modal).props().id).toEqual('modal-1');
});

it('has the expected default iconDescription', () => {
Expand Down Expand Up @@ -215,7 +218,7 @@ describe('Danger Modal', () => {
const wrapper = shallow(<Modal danger />);

it('has the expected classes', () => {
expect(wrapper.hasClass('bx--modal--danger')).toEqual(true);
expect(getModal(wrapper).hasClass('bx--modal--danger')).toEqual(true);
});

it('has correct button combination', () => {
Expand Down
37 changes: 25 additions & 12 deletions src/components/Modal/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Button from '../Button';
import { settings } from 'carbon-components';
import Close20 from '@carbon/icons-react/lib/close/20';
import { breakingChangesX, componentsX } from '../../internal/FeatureFlags';
import FocusTrap from 'focus-trap-react';

const { prefix } = settings;

Expand Down Expand Up @@ -125,6 +126,12 @@ export default class Modal extends Component {
* be focused when the Modal opens
*/
selectorPrimaryFocus: PropTypes.string,

/**
* Specify whether the modal should be a focus trap. NOTE: by default
* this is true
*/
focusTrap: PropTypes.bool,
};

static defaultProps = {
Expand All @@ -137,6 +144,7 @@ export default class Modal extends Component {
modalHeading: '',
modalLabel: '',
selectorPrimaryFocus: '[data-modal-primary-focus]',
focusTrap: true,
};

button = React.createRef();
Expand Down Expand Up @@ -266,6 +274,7 @@ export default class Modal extends Component {
selectorPrimaryFocus, // eslint-disable-line
selectorsFloatingMenus, // eslint-disable-line
shouldSubmitOnEnter, // eslint-disable-line
focusTrap,
...other
} = this.props;

Expand Down Expand Up @@ -339,18 +348,22 @@ export default class Modal extends Component {
);

return (
<div
{...other}
onKeyDown={this.handleKeyDown}
onClick={this.handleClick}
onBlur={this.handleBlur}
className={modalClasses}
role="presentation"
tabIndex={-1}
onTransitionEnd={this.props.open ? this.handleTransitionEnd : undefined}
ref={this.outerModal}>
{modalBody}
</div>
<FocusTrap active={open && focusTrap}>
<div
{...other}
onKeyDown={this.handleKeyDown}
onClick={this.handleClick}
onBlur={this.handleBlur}
className={modalClasses}
role="presentation"
tabIndex={-1}
onTransitionEnd={
this.props.open ? this.handleTransitionEnd : undefined
}
ref={this.outerModal}>
{modalBody}
</div>
</FocusTrap>
);
}
}
Loading

0 comments on commit b9f5d9e

Please sign in to comment.