Commit 666cd2de by Farhanah Sheets

feat(modal-icon): Add theme capability to Modal & create Icon

parent f9221d99
...@@ -221,6 +221,33 @@ exports[`Storyshots HyperLink with onClick 1`] = ` ...@@ -221,6 +221,33 @@ exports[`Storyshots HyperLink with onClick 1`] = `
</a> </a>
`; `;
exports[`Storyshots Icon basic usage 1`] = `
<div>
<span
aria-describedby={undefined}
aria-hidden={true}
className="fa fa-birthday-cake fa-4x"
id="SampleIcon"
/>
</div>
`;
exports[`Storyshots Icon with screenreader text 1`] = `
<div>
<span
aria-describedby={undefined}
aria-hidden={true}
className="fa fa-smile-o fa-4x"
id="SampleIcon"
/>
<span
className="sr-only"
>
Happy Icon
</span>
</div>
`;
exports[`Storyshots InputSelect basic usage 1`] = ` exports[`Storyshots InputSelect basic usage 1`] = `
<div <div
className="form-group" className="form-group"
...@@ -707,12 +734,12 @@ exports[`Storyshots Modal basic usage 1`] = ` ...@@ -707,12 +734,12 @@ exports[`Storyshots Modal basic usage 1`] = `
<div <div
className="modal-header" className="modal-header"
> >
<h5 <h2
className="modal-title" className="modal-title"
id="id2" id="id2"
> >
Modal title. Modal title.
</h5> </h2>
<button <button
aria-label="Close" aria-label="Close"
className="btn btn-light" className="btn btn-light"
...@@ -769,12 +796,12 @@ exports[`Storyshots Modal configurable buttons 1`] = ` ...@@ -769,12 +796,12 @@ exports[`Storyshots Modal configurable buttons 1`] = `
<div <div
className="modal-header" className="modal-header"
> >
<h5 <h2
className="modal-title" className="modal-title"
id="id3" id="id3"
> >
Modal title. Modal title.
</h5> </h2>
<button <button
aria-label="Close" aria-label="Close"
className="btn btn-light" className="btn btn-light"
...@@ -858,12 +885,12 @@ exports[`Storyshots Modal configurable buttons that perform actions 1`] = ` ...@@ -858,12 +885,12 @@ exports[`Storyshots Modal configurable buttons that perform actions 1`] = `
<div <div
className="modal-header" className="modal-header"
> >
<h5 <h2
className="modal-title" className="modal-title"
id="id5" id="id5"
> >
Modal title. Modal title.
</h5> </h2>
<button <button
aria-label="Close" aria-label="Close"
className="btn btn-light" className="btn btn-light"
...@@ -929,12 +956,12 @@ exports[`Storyshots Modal configurable close button 1`] = ` ...@@ -929,12 +956,12 @@ exports[`Storyshots Modal configurable close button 1`] = `
<div <div
className="modal-header" className="modal-header"
> >
<h5 <h2
className="modal-title" className="modal-title"
id="id6" id="id6"
> >
Modal title. Modal title.
</h5> </h2>
<button <button
aria-label="SHOO!" aria-label="SHOO!"
className="btn btn-light" className="btn btn-light"
...@@ -991,12 +1018,12 @@ exports[`Storyshots Modal configurable title and body 1`] = ` ...@@ -991,12 +1018,12 @@ exports[`Storyshots Modal configurable title and body 1`] = `
<div <div
className="modal-header" className="modal-header"
> >
<h5 <h2
className="modal-title" className="modal-title"
id="id4" id="id4"
> >
Custom title! Custom title!
</h5> </h2>
<button <button
aria-label="Close" aria-label="Close"
className="btn btn-light" className="btn btn-light"
...@@ -1063,12 +1090,12 @@ exports[`Storyshots Modal modal invoked via a button 1`] = ` ...@@ -1063,12 +1090,12 @@ exports[`Storyshots Modal modal invoked via a button 1`] = `
<div <div
className="modal-header" className="modal-header"
> >
<h5 <h2
className="modal-title" className="modal-title"
id="id7" id="id7"
> >
I am the modal! I am the modal!
</h5> </h2>
<button <button
aria-label="Close" aria-label="Close"
className="btn btn-light" className="btn btn-light"
...@@ -1135,12 +1162,12 @@ exports[`Storyshots Modal modal with element body 1`] = ` ...@@ -1135,12 +1162,12 @@ exports[`Storyshots Modal modal with element body 1`] = `
<div <div
className="modal-header" className="modal-header"
> >
<h5 <h2
className="modal-title" className="modal-title"
id="id8" id="id8"
> >
Modal title. Modal title.
</h5> </h2>
<button <button
aria-label="Close" aria-label="Close"
className="btn btn-light" className="btn btn-light"
...@@ -1224,6 +1251,103 @@ exports[`Storyshots Modal modal with element body 1`] = ` ...@@ -1224,6 +1251,103 @@ exports[`Storyshots Modal modal with element body 1`] = `
</div> </div>
`; `;
exports[`Storyshots Modal modal with warning theme 1`] = `
<div
aria-labelledby="id9"
aria-modal={true}
className="modal modal-open modal-backdrop show"
role="dialog"
>
<div
className="modal-dialog"
>
<div
className="modal-content"
>
<div
className="modal-header"
>
<h2
className="modal-title"
id="id9"
>
Warning Modal
</h2>
<button
aria-label="Run Away!"
className="btn btn-light"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
</div>
<div
className="modal-body"
>
<div
className="container-fluid"
>
<div
className="row"
>
<div
className="col-md-10"
>
<div>
<p>
Be careful! It is dangerous ahead.
</p>
</div>
</div>
<div
className="col"
>
<div>
<span
aria-describedby={undefined}
aria-hidden={true}
className="fa fa-exclamation-triangle fa-3x text-warning"
id="Modal-WARNING10"
/>
</div>
</div>
</div>
</div>
</div>
<div
className="modal-footer"
>
<button
className="btn btn-light"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
Continue anyway...
</button>
<button
className="btn btn-secondary"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
Run Away!
</button>
</div>
</div>
</div>
</div>
`;
exports[`Storyshots Paragon Welcome 1`] = ` exports[`Storyshots Paragon Welcome 1`] = `
<div <div
className="css-jail" className="css-jail"
...@@ -2072,10 +2196,10 @@ exports[`Storyshots Tabs basic usage 1`] = ` ...@@ -2072,10 +2196,10 @@ exports[`Storyshots Tabs basic usage 1`] = `
> >
<li <li
className="nav-item" className="nav-item"
id="tab-label-tabInterface9-0" id="tab-label-tabInterface11-0"
> >
<a <a
aria-controls="tab-panel-tabInterface9-0" aria-controls="tab-panel-tabInterface11-0"
aria-selected={true} aria-selected={true}
className="nav-link active" className="nav-link active"
onClick={[Function]} onClick={[Function]}
...@@ -2087,10 +2211,10 @@ exports[`Storyshots Tabs basic usage 1`] = ` ...@@ -2087,10 +2211,10 @@ exports[`Storyshots Tabs basic usage 1`] = `
</li> </li>
<li <li
className="nav-item" className="nav-item"
id="tab-label-tabInterface9-1" id="tab-label-tabInterface11-1"
> >
<a <a
aria-controls="tab-panel-tabInterface9-1" aria-controls="tab-panel-tabInterface11-1"
aria-selected={false} aria-selected={false}
className="nav-link" className="nav-link"
onClick={[Function]} onClick={[Function]}
...@@ -2102,10 +2226,10 @@ exports[`Storyshots Tabs basic usage 1`] = ` ...@@ -2102,10 +2226,10 @@ exports[`Storyshots Tabs basic usage 1`] = `
</li> </li>
<li <li
className="nav-item" className="nav-item"
id="tab-label-tabInterface9-2" id="tab-label-tabInterface11-2"
> >
<a <a
aria-controls="tab-panel-tabInterface9-2" aria-controls="tab-panel-tabInterface11-2"
aria-selected={false} aria-selected={false}
className="nav-link" className="nav-link"
onClick={[Function]} onClick={[Function]}
...@@ -2121,9 +2245,9 @@ exports[`Storyshots Tabs basic usage 1`] = ` ...@@ -2121,9 +2245,9 @@ exports[`Storyshots Tabs basic usage 1`] = `
> >
<div <div
aria-hidden={false} aria-hidden={false}
aria-labelledby="tab-label-tabInterface9-0" aria-labelledby="tab-label-tabInterface11-0"
className="tab-pane active" className="tab-pane active"
id="tab-panel-tabInterface9-0" id="tab-panel-tabInterface11-0"
role="tabpanel" role="tabpanel"
> >
<div> <div>
...@@ -2132,9 +2256,9 @@ exports[`Storyshots Tabs basic usage 1`] = ` ...@@ -2132,9 +2256,9 @@ exports[`Storyshots Tabs basic usage 1`] = `
</div> </div>
<div <div
aria-hidden={true} aria-hidden={true}
aria-labelledby="tab-label-tabInterface9-1" aria-labelledby="tab-label-tabInterface11-1"
className="tab-pane" className="tab-pane"
id="tab-panel-tabInterface9-1" id="tab-panel-tabInterface11-1"
role="tabpanel" role="tabpanel"
> >
<div> <div>
...@@ -2143,9 +2267,9 @@ exports[`Storyshots Tabs basic usage 1`] = ` ...@@ -2143,9 +2267,9 @@ exports[`Storyshots Tabs basic usage 1`] = `
</div> </div>
<div <div
aria-hidden={true} aria-hidden={true}
aria-labelledby="tab-label-tabInterface9-2" aria-labelledby="tab-label-tabInterface11-2"
className="tab-pane" className="tab-pane"
id="tab-panel-tabInterface9-2" id="tab-panel-tabInterface11-2"
role="tabpanel" role="tabpanel"
> >
<div> <div>
......
@import "~bootstrap/scss/utilities/_screenreaders.scss";
import React from 'react';
import { storiesOf } from '@storybook/react';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import Icon from './index';
storiesOf('Icon', module)
.add('basic usage', () => (
<Icon
id="SampleIcon"
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-birthday-cake'],
FontAwesomeStyles['fa-4x'],
]}
/>
))
.add('with screenreader text', () => (
<Icon
id="SampleIcon"
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-smile-o'],
FontAwesomeStyles['fa-4x'],
]}
srText="Happy Icon"
/>
));
import React from 'react';
import { mount } from 'enzyme';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import Icon from './index';
const testId = 'testId';
const classNames = [
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-check'],
];
const srTest = 'srTest';
let wrapper;
describe('<Icon />', () => {
describe('props received correctly', () => {
it('receives required props', () => {
wrapper = mount(<Icon id={testId} className={classNames} />);
const iconSpan = wrapper.find('span');
expect(iconSpan.prop('id')).toEqual(testId);
expect(iconSpan.hasClass(classNames[0])).toEqual(true);
expect(iconSpan.hasClass(classNames[1])).toEqual(true);
});
it('handles srText correctly', () => {
wrapper = mount(<Icon id={testId} className={classNames} srText={srTest} />);
const iconSpans = wrapper.find('span');
expect(iconSpans.length).toEqual(2);
expect(iconSpans.at(1).hasClass('sr-only')).toEqual(true);
});
});
});
# Icon
Provides the ability to have a basic accessibility friendly Icon. The Icon has `aria-hidden=true` set as a default.
## API
### `id` (string; required)
`id` will be the `id` propery of the Icon element
### `className` (array of strings; required)
`className` is array of class names that will define what the Icon looks like. For example, a list of FontAwesome style names.
### `describedBy` (string; optional)
`describedBy` is a string that can add a value to an `aria-describedBy` attribute on the Icon span, this value is `undefined` by default.
### `hidden` (boolean; optional)
`hidden` is a boolean that determines the value of `aria-hidden` attribute on the Icon span, this value is `true` by default.
### `srText` (string; optional)
`srText` is a string that will be used on a secondary span leveraging the `sr-only` style for screenreader only text, this value is `undefined` by default.
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import styles from './Icon.scss';
function Icon(props) {
return (
<div>
<span
id={props.id}
className={classNames(props.className)}
aria-describedby={props.describedBy}
aria-hidden={props.hidden}
/>
{ props.srText &&
<span className={classNames(styles['sr-only'])}>
{props.srText}
</span>
}
</div>
);
}
Icon.propTypes = {
id: PropTypes.string.isRequired,
className: PropTypes.arrayOf(PropTypes.string).isRequired,
describedBy: PropTypes.string,
hidden: PropTypes.bool,
srText: PropTypes.string,
};
Icon.defaultProps = {
describedBy: undefined,
hidden: true,
srText: undefined,
};
export default Icon;
@import "~bootstrap/scss/_variables"; @import "~bootstrap/scss/_variables";
@import "~bootstrap/scss/_modal"; @import "~bootstrap/scss/_modal";
@import "~bootstrap/scss/_grid";
@import "~bootstrap/scss/_utilities";
.modal-open { .modal-open {
display: block; display: block;
......
...@@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; ...@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import Modal from './index'; import Modal from './index';
import Button from '../Button'; import Button from '../Button';
import InputText from '../InputText'; import InputText from '../InputText';
import Theme from '../utils/constants';
class ModalWrapper extends React.Component { class ModalWrapper extends React.Component {
constructor(props) { constructor(props) {
...@@ -144,4 +145,22 @@ storiesOf('Modal', module) ...@@ -144,4 +145,22 @@ storiesOf('Modal', module)
)} )}
onClose={() => {}} onClose={() => {}}
/> />
))
.add('modal with warning theme', () => (
<Modal
open
title="Warning Modal"
body={(
<p>Be careful! It is dangerous ahead.</p>
)}
closeText="Run Away!"
buttons={[
<Button
label="Continue anyway..."
buttonType="light"
/>,
]}
onClose={() => {}}
themes={{ status: Theme.status.WARNING }}
/>
)); ));
...@@ -2,9 +2,12 @@ import React from 'react'; ...@@ -2,9 +2,12 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import styles from './Modal.scss'; import styles from './Modal.scss';
import Button, { buttonPropTypes } from '../Button'; import Button, { buttonPropTypes } from '../Button';
import Icon from '../Icon';
import newId from '../utils/newId'; import newId from '../utils/newId';
import Theme from '../utils/constants';
class Modal extends React.Component { class Modal extends React.Component {
constructor(props) { constructor(props) {
...@@ -48,6 +51,60 @@ class Modal extends React.Component { ...@@ -48,6 +51,60 @@ class Modal extends React.Component {
this.closeButton = input; this.closeButton = input;
} }
getThemeIcon() {
const { themes } = this.props;
let themeIconClassName;
if (themes.status) {
switch (themes.status) {
case Theme.status.WARNING:
themeIconClassName = classNames(
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-exclamation-triangle'],
FontAwesomeStyles['fa-3x'],
styles[`text-${themes.status.toLowerCase()}`],
);
break;
default:
break;
}
}
return themeIconClassName;
}
// TODO: revisit this method here -- I think it has possible uses
// for future styles/themes but wondering best use for Warning Modal
// Perhaps with the grid styles - since that's currently very prescriptive?
// getThemeClassName() {
// const { themes } = this.props;
// let themeClassName;
// return themeClassName;
// }
getGridBody(body) {
return (
<div className={styles['container-fluid']}>
<div className={styles.row}>
<div className={styles['col-md-10']}>
<div>
{body}
</div>
</div>
<div className={styles.col}>
<Icon
id={newId(`Modal-${this.props.themes.status}`)}
className={[
this.getThemeIcon(),
]}
/>
</div>
</div>
</div>
);
}
close() { close() {
this.setState({ open: false }); this.setState({ open: false });
this.props.onClose(); this.props.onClose();
...@@ -89,12 +146,17 @@ class Modal extends React.Component { ...@@ -89,12 +146,17 @@ class Modal extends React.Component {
} }
renderBody() { renderBody() {
const { themes } = this.props;
let { body } = this.props; let { body } = this.props;
if (typeof body === 'string') { if (typeof body === 'string') {
body = <p>{body}</p>; body = <p>{body}</p>;
} }
if (themes.status) {
body = this.getGridBody(body);
}
return body; return body;
} }
...@@ -119,7 +181,7 @@ class Modal extends React.Component { ...@@ -119,7 +181,7 @@ class Modal extends React.Component {
<div className={styles['modal-dialog']}> <div className={styles['modal-dialog']}>
<div className={styles['modal-content']}> <div className={styles['modal-content']}>
<div className={styles['modal-header']}> <div className={styles['modal-header']}>
<h5 className={styles['modal-title']} id={this.headerId}>{this.props.title}</h5> <h2 className={styles['modal-title']} id={this.headerId}>{this.props.title}</h2>
<Button <Button
label={<span aria-hidden="true">&times;</span>} label={<span aria-hidden="true">&times;</span>}
aria-label={this.props.closeText} aria-label={this.props.closeText}
...@@ -159,12 +221,16 @@ Modal.propTypes = { ...@@ -159,12 +221,16 @@ Modal.propTypes = {
])), ])),
closeText: PropTypes.string, closeText: PropTypes.string,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
themes: PropTypes.shape({
status: PropTypes.string,
}),
}; };
Modal.defaultProps = { Modal.defaultProps = {
open: false, open: false,
buttons: [], buttons: [],
closeText: 'Close', closeText: 'Close',
themes: {},
}; };
......
const Theme = {
status: {
DANGER: 'DANGER',
INFO: 'INFO',
SUCCESS: 'SUCCESS',
WARNING: 'WARNING',
},
};
export default Theme;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment