-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TAXTOOLS-556] Add Help Drawer component (#299)
* Add simplified HelpDrawer and Toggle based on App3 * Add tests * Add aria-label to close button * Deal with focus in render, properly nest HTML in examples * Remove id generation/handling from toggle * No longer dangerously set HTML in footer - allow user to pass a renderable node instead * Add footer title and content to React example
- Loading branch information
1 parent
eb61878
commit 5bf3453
Showing
11 changed files
with
480 additions
and
1 deletion.
There are no files selected for viewing
76 changes: 76 additions & 0 deletions
76
packages/core/src/components/HelpDrawer/HelpDrawer.example.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import HelpDrawer from './HelpDrawer'; | ||
import HelpDrawerToggle from './HelpDrawerToggle'; | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
|
||
class HelpDrawerExample extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
|
||
this.state = { | ||
showHelp: false | ||
}; | ||
} | ||
|
||
handleDrawerClose() { | ||
this.setState({ showHelp: false }); | ||
} | ||
|
||
handleDrawerOpen() { | ||
this.setState({ showHelp: true }); | ||
} | ||
|
||
render() { | ||
return ( | ||
<div> | ||
<p> | ||
<strong>Click the link below to open the help drawer.</strong> | ||
</p> | ||
<p> | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do | ||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad | ||
minim veniam, quis nostrud exercitation ullamco laboris nisi ut | ||
aliquip ex ea commodo consequat. | ||
</p> | ||
<HelpDrawerToggle | ||
helpDrawerOpen={this.state.showHelp} | ||
showDrawer={() => this.handleDrawerOpen()} | ||
> | ||
Toggle the help drawer. | ||
</HelpDrawerToggle> | ||
{this.state.showHelp && ( | ||
<HelpDrawer | ||
footerTitle="Footer Title" | ||
footerBody={ | ||
<p className="ds-text ds-u-margin--0">Footer content</p> | ||
} | ||
title="Help Drawer Title" | ||
onCloseClick={() => this.handleDrawerClose()} | ||
> | ||
<strong>An Explanation</strong> | ||
<p> | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do | ||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut | ||
enim ad minim veniam, quis nostrud exercitation ullamco laboris | ||
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in | ||
reprehenderit in voluptate velit esse cillum dolore eu fugiat | ||
nulla pariatur. Excepteur sint occaecat cupidatat non proident, | ||
sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
</p> | ||
</HelpDrawer> | ||
)} | ||
<p> | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do | ||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad | ||
minim veniam, quis nostrud exercitation ullamco laboris nisi ut | ||
aliquip ex ea commodo consequat. Duis aute irure dolor in | ||
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla | ||
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in | ||
culpa qui officia deserunt mollit anim id est laborum. | ||
</p> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
ReactDOM.render(<HelpDrawerExample />, document.getElementById('js-example')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import Button from '../Button/Button'; | ||
import PropTypes from 'prop-types'; | ||
import React from 'react'; | ||
|
||
export class HelpDrawer extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.titleRef = null; | ||
} | ||
|
||
componentDidMount() { | ||
if (this.titleRef) this.titleRef.focus(); | ||
} | ||
|
||
render() { | ||
const { | ||
ariaLabel, | ||
title, | ||
children, | ||
onCloseClick, | ||
footerBody, | ||
footerTitle | ||
} = this.props; | ||
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, react/no-danger */ | ||
return ( | ||
<div className="ds-c-help-drawer"> | ||
<div className="ds-c-help-drawer__header"> | ||
{/* The nested div below might seem redundant, but we need a | ||
* separation between our sticky header, and the flex container | ||
* so things display as expected when the body content overflows | ||
*/} | ||
<div className="ds-u-fill--gray-lightest ds-u-padding--2 ds-u-display--flex ds-u-align-items--start"> | ||
<h3 | ||
ref={el => (this.titleRef = el)} | ||
tabIndex="0" | ||
className="ds-u-text--lead ds-u-margin-y--0 ds-u-margin-right--2" | ||
> | ||
{title} | ||
</h3> | ||
<Button | ||
aria-label={ariaLabel} | ||
className="ds-u-margin-left--auto" | ||
size="small" | ||
onClick={onCloseClick} | ||
variation="secondary" | ||
> | ||
Close | ||
</Button> | ||
</div> | ||
</div> | ||
<div className="ds-c-help-drawer__body ds-u-md-font-size--small ds-u-lg-font-size--base ds-u-padding--2"> | ||
{children} | ||
</div> | ||
<div className="ds-c-help-drawer__footer ds-u-fill--primary-alt-lightest ds-u-md-font-size--small ds-u-lg-font-size--base ds-u-padding--2"> | ||
<h4 className="ds-text ds-u-margin--0">{footerTitle}</h4> | ||
<div className="ds-text ds-u-margin--0">{footerBody}</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
HelpDrawer.defaultProps = { ariaLabel: 'Close help drawer' }; | ||
HelpDrawer.propTypes = { | ||
/** Helps give more context to screen readers on the button that closes the Help Drawer */ | ||
ariaLabel: PropTypes.string, | ||
children: PropTypes.node.isRequired, | ||
footerBody: PropTypes.node, | ||
footerTitle: PropTypes.string, | ||
onCloseClick: PropTypes.func.isRequired, | ||
/** Required because the title is what gets focused on mount */ | ||
title: PropTypes.string.isRequired | ||
}; | ||
|
||
export default HelpDrawer; |
112 changes: 112 additions & 0 deletions
112
packages/core/src/components/HelpDrawer/HelpDrawer.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
@import '@cmsgov/design-system-support/src/settings/index'; | ||
|
||
/* | ||
Help Drawer | ||
A help drawer provides a space for medium to long-form help | ||
content — content that's too long or not common enough to warrant | ||
being on the page by default. | ||
On large screens it's fixed to the side of the screen, and on | ||
smaller screens it overlays the entire screen. | ||
Render the drawer below the toggle bottom that triggers it. | ||
This way the markup remains semantically sound and screen reader friendly. | ||
Markup: | ||
<div> | ||
<p><strong>Note: This is just an example of the HTML markup. See the React example for a functioning example.</strong></p> | ||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> | ||
<span class="ds-u-display--block"> | ||
<a href="javascript:void(0);">Toggle the help drawer.</a> | ||
</span> | ||
<div class="ds-c-help-drawer"> | ||
<div class="ds-c-help-drawer__header"> | ||
<div class="ds-u-fill--gray-lightest ds-u-padding--2 ds-u-display--flex ds-u-align-items--start"> | ||
<h3 tabindex="0" class="ds-u-text--lead ds-u-margin-y--0 ds-u-margin-right--2">Help Drawer Title</h3> | ||
<button class="ds-c-button ds-c-button--secondary ds-c-button--small ds-u-margin-left--auto" type="button">Close</button> | ||
</div> | ||
</div> | ||
<div class="ds-c-help-drawer__body ds-u-md-font-size--small ds-u-lg-font-size--base ds-u-padding--2"> | ||
<strong>An Explanation</strong> | ||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> | ||
</div> | ||
<div class="ds-c-help-drawer__footer ds-u-fill--primary-alt-lightest ds-u-md-font-size--small ds-u-lg-font-size--base ds-u-padding--2"> | ||
<h4 class="ds-text ds-u-margin--0">Footer title</h4> | ||
<p class="ds-text ds-u-margin--0">Footer content</p> | ||
</div> | ||
</div> | ||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> | ||
</div> | ||
Style guide: components.help-drawer | ||
*/ | ||
|
||
/* | ||
`<HelpDrawer>` | ||
@react-component HelpDrawer | ||
Style guide: components.help-drawer.react-help-drawer | ||
*/ | ||
|
||
/* | ||
`<HelpDrawerToggle>` | ||
@hide-example | ||
@react-component HelpDrawerToggle | ||
Style guide: components.help-drawer.react-help-drawer-toggle | ||
*/ | ||
|
||
@keyframes slideInHelpDrawer { | ||
from { | ||
opacity: 0; | ||
transform: translate3d(200px, 0, 0); | ||
} | ||
|
||
to { | ||
opacity: 1; | ||
transform: translate3d(0, 0, 0); | ||
} | ||
} | ||
|
||
.ds-c-help-drawer { | ||
background: $color-background; | ||
bottom: 0; | ||
box-shadow: -2px 0 0 $border-color; | ||
display: flex; // flex layout helps stick the footer to the bottom | ||
flex-direction: column; | ||
overflow: auto; | ||
position: fixed; | ||
right: 0; | ||
top: 0; | ||
width: 100%; | ||
z-index: 10; | ||
|
||
@media (min-width: $width-md) { | ||
animation: slideInHelpDrawer $animation-speed-2 ease-in-out both; // slide in from the right | ||
max-width: 33%; // this equates to 4 grid columns | ||
} | ||
|
||
@media (min-width: $width-xl) { | ||
max-width: $measure-base; | ||
} | ||
} | ||
|
||
.ds-c-help-drawer__header { | ||
position: sticky; | ||
top: 0; | ||
} | ||
|
||
.ds-c-help-drawer__body { | ||
// Stretch the body so that the footer sticks to the | ||
// bottom of the screen | ||
flex-grow: 1; | ||
|
||
p:first-child { | ||
// Prevent too much space at the top of the body area | ||
margin-top: 0; | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
packages/core/src/components/HelpDrawer/HelpDrawer.test.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import HelpDrawer from './HelpDrawer.jsx'; | ||
import React from 'react'; | ||
import renderer from 'react-test-renderer'; | ||
import { shallow } from 'enzyme'; | ||
|
||
const defaultProps = { | ||
footerBody: ( | ||
<div> | ||
<p>Some footer content</p> | ||
</div> | ||
), | ||
footerTitle: 'Footer title', | ||
onCloseClick: () => {}, | ||
title: 'HelpDrawer title' | ||
}; | ||
|
||
function renderHelpDrawer(props) { | ||
props = Object.assign({}, defaultProps, props); | ||
const wrapper = shallow( | ||
<HelpDrawer {...props}> | ||
<p>content</p> | ||
</HelpDrawer> | ||
); | ||
return { props, wrapper }; | ||
} | ||
|
||
describe('HelpDrawer', () => { | ||
it('calls props.onCloseClick on close button click', () => { | ||
const onCloseClick = jest.fn(); | ||
const { wrapper } = renderHelpDrawer({ onCloseClick }); | ||
const closeBtn = wrapper.find('Button'); | ||
closeBtn.simulate('click'); | ||
expect(onCloseClick).toHaveBeenCalled(); | ||
}); | ||
|
||
it('renders a snapshot', () => { | ||
const tree = renderer | ||
.create( | ||
<HelpDrawer {...defaultProps}> | ||
<p>content</p> | ||
</HelpDrawer> | ||
) | ||
.toJSON(); | ||
|
||
expect(tree).toMatchSnapshot(); | ||
}); | ||
}); |
46 changes: 46 additions & 0 deletions
46
packages/core/src/components/HelpDrawer/HelpDrawerToggle.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import PropTypes from 'prop-types'; | ||
import React from 'react'; | ||
|
||
/** | ||
* A link that triggers the visibility of a help drawer | ||
*/ | ||
export class HelpDrawerToggle extends React.PureComponent { | ||
render() { | ||
if (!this.props.helpDrawerOpen && this.buttonRef) { | ||
this.buttonRef.focus(); | ||
} | ||
const blockInlineClass = `ds-u-display--${ | ||
this.props.inline ? 'inline-block' : 'block' | ||
}`; | ||
/* eslint-disable jsx-a11y/anchor-is-valid */ | ||
return ( | ||
// Use a <span> since a <div> may be invalid depending where this link is nested | ||
<span className={blockInlineClass}> | ||
<a | ||
href="javascript:void(0);" | ||
className={this.props.className} | ||
ref={el => (this.buttonRef = el)} | ||
onClick={() => this.props.showDrawer()} | ||
> | ||
{this.props.children} | ||
</a> | ||
</span> | ||
); | ||
} | ||
} | ||
|
||
/* eslint-disable react/no-unused-prop-types */ | ||
HelpDrawerToggle.propTypes = { | ||
/** Whether or not the Help Drawer controlled by this toggle is open or closed. This value is used to re-focus the toggle that opened the drawer when the drawer closes. */ | ||
helpDrawerOpen: PropTypes.bool.isRequired, | ||
children: PropTypes.node.isRequired, | ||
/** Additional classes for the toggle button anchor element */ | ||
className: PropTypes.string, | ||
/** Add display inline or block to parent span */ | ||
inline: PropTypes.bool, | ||
/** This function is called with an id that the toggle generates. It can | ||
be used in implementing the help drawer for keeping track of the drawer the toggle controls */ | ||
showDrawer: PropTypes.func.isRequired | ||
}; | ||
|
||
export default HelpDrawerToggle; |
Oops, something went wrong.