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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow content within modals to autofocus #202

Merged
merged 1 commit into from
Apr 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/components/Modal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
import { Modal } from 'reactstrap';
import { Modal as ReactstrapModal } from 'reactstrap';

/**
* TODO
* As of 04/27/2017 the reactstrap Modal class will autofocus itself upon opening, preventing any
* elements within the modal from autofocusing.
*
* There is a PR open with a fix here https://github.com/reactstrap/reactstrap/pull/389 that solves
* the problem by allowing users to pass autofocus={false} to Modals. When the PR has been merged
* we can remove the togglePortal override below.
*/
class Modal extends ReactstrapModal {
togglePortal() {
if (this.props.isOpen) {
// See https://github.com/reactstrap/reactstrap/pull/389/files#diff-3ddc036af206541151e86f03cfc0c0eeR113
if (this.props.autoFocus !== false) {
this._focus = true;
}
this.show();
} else {
this.hide();
}
}
}

Modal.defaultProps = {
...Modal.defaultProps,
Expand Down
24 changes: 21 additions & 3 deletions stories/Modal.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Button, ButtonToolbar, Modal, ModalHeader, ModalBody, ModalFooter } from '../src';
import { Button, ButtonToolbar, Modal, ModalHeader, ModalBody, ModalFooter, Input } from '../src';
import { storiesOf } from '@kadira/storybook';
import { boolean, select } from '@kadira/storybook-addon-knobs';

Expand All @@ -26,5 +26,23 @@ storiesOf('Modal', module)
</ButtonToolbar>
</ModalFooter>
</Modal>
)
);
))
.addWithInfo('Autofocus', () => (
<Modal
isOpen={boolean('isOpen', true)}
backdrop={boolean('backdrop', true)}
size={select('size', [null, 'sm', 'md', 'lg'], null)}
autoFocus={false}
>
<ModalHeader toggle={() => {}}>Modal title</ModalHeader>
<ModalBody>
This input should have focus: <Input autoFocus />
</ModalBody>
<ModalFooter>
<ButtonToolbar>
<Button color="primary">Do Something</Button>
<Button>Cancel</Button>
</ButtonToolbar>
</ModalFooter>
</Modal>
));
80 changes: 78 additions & 2 deletions test/components/Modal.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,88 @@ import 'jsdom-global/register';
import React from 'react';
import assert from 'assert';
import { mount } from 'enzyme';
import { Button } from 'reactstrap';

import { Modal } from '../../src';

describe('<Modal />', () => {
const component = mount(<Modal />);
const noop = () => {};

describe.only('<Modal />', () => {
let component;

afterEach(done => {
if (component && component.prop('isOpen')) {
component.setProps({ isOpen: false });
const interval = setInterval(() => {
if (!document.getElementsByClassName('modal').length) {
clearInterval(interval);
component = null;
done();
}
}, 100);
} else {
done();
}
});

it('should render correctly', () => {
component = mount(<Modal toggle={noop} />);
assert(component);
});

context('when autoFocus="true"', () => {
it('sets focus to the modal content', () => {
const originalActiveElement = document.activeElement;
component = mount(
<Modal isOpen={false} toggle={noop}>
<Button id="button">This button</Button> should not have focus
</Modal>
);

// After mounting the active element should not have changed
assert.equal(document.activeElement, originalActiveElement);

component.setProps({ isOpen: true });

// Now the modal is open the original focused element should still maintain focus
const content = document.getElementsByClassName('modal')[0];
assert.equal(document.activeElement, content);
});
});

context('when autoFocus="false"', () => {
it('leaves focus as-is if elements within the modal are not autofocus', () => {
const originalActiveElement = document.activeElement;
component = mount(
<Modal isOpen={false} autoFocus={false} toggle={noop}>
<Button id="button">This button</Button> should not have focus
</Modal>
);

// After mounting the active element should not have changed
assert.equal(document.activeElement, originalActiveElement);

component.setProps({ isOpen: true });

// Now the modal is open the original focused element should still maintain focus
assert.equal(document.activeElement, originalActiveElement);
});

it('allows elements within the modal to have focus', () => {
const originalActiveElement = document.activeElement;
component = mount(
<Modal isOpen={false} autoFocus={false} toggle={noop}>
<Button id="button" autoFocus>This button</Button> should have focus
</Modal>
);

// After mounting the active element should not have changed
assert.equal(document.activeElement, originalActiveElement);

component.setProps({ isOpen: true });

// Now the modal is open the button should have focus
assert.equal('button', document.activeElement.id);
});
});
});