From 3833c759b183a3137a6b14c8f8983a9dfcaaa1bb Mon Sep 17 00:00:00 2001 From: AlasdairSwan Date: Wed, 18 Apr 2018 08:53:48 -0400 Subject: [PATCH] fix(modal): make header close button optional --- .../__snapshots__/Storyshots.test.js.snap | 79 +++++++++++++++---- src/Modal/Modal.scss | 8 +- src/Modal/Modal.stories.jsx | 9 +++ src/Modal/Modal.test.jsx | 10 +++ src/Modal/README.md | 3 + src/Modal/index.jsx | 39 +++++---- 6 files changed, 116 insertions(+), 32 deletions(-) diff --git a/.storybook/__snapshots__/Storyshots.test.js.snap b/.storybook/__snapshots__/Storyshots.test.js.snap index a35b2b0f88..79a35f3a0e 100644 --- a/.storybook/__snapshots__/Storyshots.test.js.snap +++ b/.storybook/__snapshots__/Storyshots.test.js.snap @@ -15688,7 +15688,7 @@ exports[`Storyshots Modal modal with element body 1`] = ` exports[`Storyshots Modal modal with warning variant 1`] = `

Warning Modal

@@ -15750,7 +15750,7 @@ exports[`Storyshots Modal modal with warning variant 1`] = `
@@ -15784,6 +15784,55 @@ exports[`Storyshots Modal modal with warning variant 1`] = ` `; +exports[`Storyshots Modal modal without a close button in the header 1`] = ` +
+
+
+
+

+ Modal title. +

+
+
+

+ Modal body. +

+
+
+ +
+
+
+
+`; + exports[`Storyshots Paragon Welcome 1`] = `
  • @@ -20086,9 +20135,9 @@ exports[`Storyshots Tabs basic usage 1`] = `
    @@ -20097,9 +20146,9 @@ exports[`Storyshots Tabs basic usage 1`] = `
    diff --git a/src/Modal/Modal.scss b/src/Modal/Modal.scss index 10006c1c7f..682802739c 100644 --- a/src/Modal/Modal.scss +++ b/src/Modal/Modal.scss @@ -4,7 +4,13 @@ @import "~bootstrap/scss/_utilities"; .modal-open { - display: block; + display: block; + + &:focus { + .modal-dialog { + box-shadow: $btn-focus-box-shadow; + } + } } .modal-backdrop { diff --git a/src/Modal/Modal.stories.jsx b/src/Modal/Modal.stories.jsx index d81116a996..07c668124f 100644 --- a/src/Modal/Modal.stories.jsx +++ b/src/Modal/Modal.stories.jsx @@ -146,6 +146,15 @@ storiesOf('Modal', module) onClose={() => {}} /> )) + .add('modal without a close button in the header', () => ( + {}} + renderHeaderCloseButton={false} + /> + )) .add('modal with warning variant', () => ( ', () => { expect(modalBody.text()).toEqual(body); expect(wrapper.find('button')).toHaveLength(2); }); + + it('render of the header close button is optional', () => { + wrapper = mount(); + const modalHeader = wrapper.find('.modal-header'); + const modalFooter = wrapper.find('.modal-footer'); + + expect(modalHeader.find('button')).toHaveLength(0); + expect(modalFooter.find('button')).toHaveLength(1); + expect(wrapper.find('button')).toHaveLength(1); + }); }); describe('props received correctly', () => { diff --git a/src/Modal/README.md b/src/Modal/README.md index fb1070fa1c..1ffc23a5cc 100644 --- a/src/Modal/README.md +++ b/src/Modal/README.md @@ -21,3 +21,6 @@ Provides a basic modal component with customizable title, body, and footer butto ### `onClose` (function; required) `onClose` is a function that is called on close. It can be used to perform actions upon closing of the modal, such as restoring focus to the previous logical focusable element. + +### `renderHeaderCloseButton` (boolean; optional) +`renderHeaderCloseButton` specifies whether a close button is rendered in the modal header. It defaults to true. diff --git a/src/Modal/index.jsx b/src/Modal/index.jsx index 75b9b4f3d3..245429fbfa 100644 --- a/src/Modal/index.jsx +++ b/src/Modal/index.jsx @@ -15,7 +15,7 @@ class Modal extends React.Component { this.close = this.close.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); - this.setXButton = this.setXButton.bind(this); + this.setFirstFocusableElement = this.setFirstFocusableElement.bind(this); this.setCloseButton = this.setCloseButton.bind(this); this.headerId = newId(); @@ -26,8 +26,8 @@ class Modal extends React.Component { } componentDidMount() { - if (this.xButton) { - this.xButton.focus(); + if (this.firstFocusableElement) { + this.firstFocusableElement.focus(); } } @@ -39,12 +39,12 @@ class Modal extends React.Component { componentDidUpdate(prevState) { if (this.state.open && !prevState.open) { - this.xButton.focus(); + this.firstFocusableElement.focus(); } } - setXButton(input) { - this.xButton = input; + setFirstFocusableElement(input) { + this.firstFocusableElement = input; } setCloseButton(input) { @@ -105,13 +105,13 @@ class Modal extends React.Component { this.close(); } else if (e.key === 'Tab') { if (e.shiftKey) { - if (e.target === this.xButton) { + if (e.target === this.firstFocusableElement) { e.preventDefault(); this.closeButton.focus(); } } else if (e.target === this.closeButton) { e.preventDefault(); - this.xButton.focus(); + this.firstFocusableElement.focus(); } } } @@ -152,6 +152,7 @@ class Modal extends React.Component { render() { const { open } = this.state; + const { renderHeaderCloseButton } = this.props; return (

    {this.props.title}

    -
    {this.renderBody()} @@ -214,6 +219,7 @@ Modal.propTypes = { variant: PropTypes.shape({ status: PropTypes.string, }), + renderHeaderCloseButton: PropTypes.bool, }; Modal.defaultProps = { @@ -221,6 +227,7 @@ Modal.defaultProps = { buttons: [], closeText: 'Close', variant: {}, + renderHeaderCloseButton: true, };