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`] = `
</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`] = `
<div
className="form-group"
......@@ -707,12 +734,12 @@ exports[`Storyshots Modal basic usage 1`] = `
<div
className="modal-header"
>
<h5
<h2
className="modal-title"
id="id2"
>
Modal title.
</h5>
</h2>
<button
aria-label="Close"
className="btn btn-light"
......@@ -769,12 +796,12 @@ exports[`Storyshots Modal configurable buttons 1`] = `
<div
className="modal-header"
>
<h5
<h2
className="modal-title"
id="id3"
>
Modal title.
</h5>
</h2>
<button
aria-label="Close"
className="btn btn-light"
......@@ -858,12 +885,12 @@ exports[`Storyshots Modal configurable buttons that perform actions 1`] = `
<div
className="modal-header"
>
<h5
<h2
className="modal-title"
id="id5"
>
Modal title.
</h5>
</h2>
<button
aria-label="Close"
className="btn btn-light"
......@@ -929,12 +956,12 @@ exports[`Storyshots Modal configurable close button 1`] = `
<div
className="modal-header"
>
<h5
<h2
className="modal-title"
id="id6"
>
Modal title.
</h5>
</h2>
<button
aria-label="SHOO!"
className="btn btn-light"
......@@ -991,12 +1018,12 @@ exports[`Storyshots Modal configurable title and body 1`] = `
<div
className="modal-header"
>
<h5
<h2
className="modal-title"
id="id4"
>
Custom title!
</h5>
</h2>
<button
aria-label="Close"
className="btn btn-light"
......@@ -1063,12 +1090,12 @@ exports[`Storyshots Modal modal invoked via a button 1`] = `
<div
className="modal-header"
>
<h5
<h2
className="modal-title"
id="id7"
>
I am the modal!
</h5>
</h2>
<button
aria-label="Close"
className="btn btn-light"
......@@ -1135,12 +1162,12 @@ exports[`Storyshots Modal modal with element body 1`] = `
<div
className="modal-header"
>
<h5
<h2
className="modal-title"
id="id8"
>
Modal title.
</h5>
</h2>
<button
aria-label="Close"
className="btn btn-light"
......@@ -1224,6 +1251,103 @@ exports[`Storyshots Modal modal with element body 1`] = `
</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`] = `
<div
className="css-jail"
......@@ -2072,10 +2196,10 @@ exports[`Storyshots Tabs basic usage 1`] = `
>
<li
className="nav-item"
id="tab-label-tabInterface9-0"
id="tab-label-tabInterface11-0"
>
<a
aria-controls="tab-panel-tabInterface9-0"
aria-controls="tab-panel-tabInterface11-0"
aria-selected={true}
className="nav-link active"
onClick={[Function]}
......@@ -2087,10 +2211,10 @@ exports[`Storyshots Tabs basic usage 1`] = `
</li>
<li
className="nav-item"
id="tab-label-tabInterface9-1"
id="tab-label-tabInterface11-1"
>
<a
aria-controls="tab-panel-tabInterface9-1"
aria-controls="tab-panel-tabInterface11-1"
aria-selected={false}
className="nav-link"
onClick={[Function]}
......@@ -2102,10 +2226,10 @@ exports[`Storyshots Tabs basic usage 1`] = `
</li>
<li
className="nav-item"
id="tab-label-tabInterface9-2"
id="tab-label-tabInterface11-2"
>
<a
aria-controls="tab-panel-tabInterface9-2"
aria-controls="tab-panel-tabInterface11-2"
aria-selected={false}
className="nav-link"
onClick={[Function]}
......@@ -2121,9 +2245,9 @@ exports[`Storyshots Tabs basic usage 1`] = `
>
<div
aria-hidden={false}
aria-labelledby="tab-label-tabInterface9-0"
aria-labelledby="tab-label-tabInterface11-0"
className="tab-pane active"
id="tab-panel-tabInterface9-0"
id="tab-panel-tabInterface11-0"
role="tabpanel"
>
<div>
......@@ -2132,9 +2256,9 @@ exports[`Storyshots Tabs basic usage 1`] = `
</div>
<div
aria-hidden={true}
aria-labelledby="tab-label-tabInterface9-1"
aria-labelledby="tab-label-tabInterface11-1"
className="tab-pane"
id="tab-panel-tabInterface9-1"
id="tab-panel-tabInterface11-1"
role="tabpanel"
>
<div>
......@@ -2143,9 +2267,9 @@ exports[`Storyshots Tabs basic usage 1`] = `
</div>
<div
aria-hidden={true}
aria-labelledby="tab-label-tabInterface9-2"
aria-labelledby="tab-label-tabInterface11-2"
className="tab-pane"
id="tab-panel-tabInterface9-2"
id="tab-panel-tabInterface11-2"
role="tabpanel"
>
<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/_modal";
@import "~bootstrap/scss/_grid";
@import "~bootstrap/scss/_utilities";
.modal-open {
display: block;
......
......@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import Modal from './index';
import Button from '../Button';
import InputText from '../InputText';
import Theme from '../utils/constants';
class ModalWrapper extends React.Component {
constructor(props) {
......@@ -144,4 +145,22 @@ storiesOf('Modal', module)
)}
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';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import styles from './Modal.scss';
import Button, { buttonPropTypes } from '../Button';
import Icon from '../Icon';
import newId from '../utils/newId';
import Theme from '../utils/constants';
class Modal extends React.Component {
constructor(props) {
......@@ -48,6 +51,60 @@ class Modal extends React.Component {
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() {
this.setState({ open: false });
this.props.onClose();
......@@ -89,12 +146,17 @@ class Modal extends React.Component {
}
renderBody() {
const { themes } = this.props;
let { body } = this.props;
if (typeof body === 'string') {
body = <p>{body}</p>;
}
if (themes.status) {
body = this.getGridBody(body);
}
return body;
}
......@@ -119,7 +181,7 @@ class Modal extends React.Component {
<div className={styles['modal-dialog']}>
<div className={styles['modal-content']}>
<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
label={<span aria-hidden="true">&times;</span>}
aria-label={this.props.closeText}
......@@ -159,12 +221,16 @@ Modal.propTypes = {
])),
closeText: PropTypes.string,
onClose: PropTypes.func.isRequired,
themes: PropTypes.shape({
status: PropTypes.string,
}),
};
Modal.defaultProps = {
open: false,
buttons: [],
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