Skip to content

Commit

Permalink
feat(component): new Modal component
Browse files Browse the repository at this point in the history
  • Loading branch information
Eszter Hofmann committed Jul 18, 2019
1 parent 468305a commit c46b7b6
Show file tree
Hide file tree
Showing 11 changed files with 844 additions and 741 deletions.
1,349 changes: 609 additions & 740 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"dependencies": {
"@babel/polyfill": "7.0.0",
"css-vars-ponyfill": "1.16.4",
"prop-types": "^15.7.2"
"prop-types": "^15.7.2",
"react-modal": "3.9.1"
},
"peerDependencies": {
"react": "^16.8.6",
Expand Down
78 changes: 78 additions & 0 deletions src/components/Modal/Modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import PropTypes from 'prop-types';
import bem from 'bem';
import ReactModal from 'react-modal';
import styles from './Modal.scss';

const { block, elem } = bem({
name: 'Modal',
classnames: styles
});

const Modal = props => {
const {
children,
isOpen,
contentLabel,
onRequestClose,
className,
portalClassName,
overlayClassName,
...rest
} = props;

const { className: portalClass } = block(props);
const { className: overlayClass } = elem('overlay', props);
const { className: overlayEnterClass } = elem('overlayEnter', props);
const { className: overlayExitClass } = elem('overlayExit', props);
const { className: contentClass } = elem('content', props);
const { className: contentEnterClass } = elem('contentEnter', props);
const { className: contentExitClass } = elem('contentExit', props);

const contentClassJoined = [contentClass, className].join(' ');

return (
<ReactModal
isOpen={isOpen}
contentLabel={contentLabel}
onRequestClose={onRequestClose}
closeTimeoutMS={300}
portalClassName={portalClass}
overlayClassName={{
base: overlayClass,
afterOpen: overlayEnterClass,
beforeClose: overlayExitClass
}}
className={{
base: contentClassJoined,
afterOpen: contentEnterClass,
beforeClose: contentExitClass
}}
{...rest}
>
{children}
</ReactModal>
);
};

Modal.displayName = 'Modal';

Modal.propTypes = {
/** elements to be rendered within the modal */
children: PropTypes.node.isRequired,
/** The state of the modal */
isOpen: PropTypes.bool.isRequired,
/** A title for the modal that will be used by screenreaders */
contentLabel: PropTypes.string.isRequired,
/** A function to be called when the modal is closed */
onRequestClose: PropTypes.func,
/** Additional class to be applied to the content part */
className: PropTypes.string
};

Modal.defaultProps = {
onRequestClose: null,
className: ''
};

export default Modal;
45 changes: 45 additions & 0 deletions src/components/Modal/Modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.Modal {
&__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity var(--transition-duration) ease-in-out;
}

&__overlayEnter {
opacity: 1;
}

&__overlayExit {
opacity: 0;
}

&__content {
position: relative;
top: 15vh;
width: 65vw;
height: 65vh;
margin: auto;
max-width: var(--site-container-max-size);
border: 1px solid var(--border-color-strong);
border-radius: var(--border-radius);
background-color: var(--color-background);
box-shadow: 0 1px 25px 0 rgba(0, 0, 0, 0.8);
overflow: auto;
outline: none;
transform: translateY(50px);
transition: transform var(--transition-duration);
}

&__contentEnter {
transform: translateY(0);
}

&__contentExit {
transform: translateY(50px);
}
}
14 changes: 14 additions & 0 deletions src/components/Modal/__tests__/Modal.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import toJson from 'enzyme-to-json';
import Modal from '../Modal';

describe('Modal', () => {
it('should render correctly', () => {
const wrapper = shallow(
<Modal isOpen contentLabel="Content label">
Some children
</Modal>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
35 changes: 35 additions & 0 deletions src/components/Modal/__tests__/__snapshots__/Modal.spec.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Modal should render correctly 1`] = `
<Modal
ariaHideApp={true}
bodyOpenClassName="ReactModal__Body--open"
className={
Object {
"afterOpen": "Modal__contentEnter",
"base": "Modal__content ",
"beforeClose": "Modal__contentExit",
}
}
closeTimeoutMS={300}
contentLabel="Content label"
isOpen={true}
onRequestClose={null}
overlayClassName={
Object {
"afterOpen": "Modal__overlayEnter",
"base": "Modal__overlay",
"beforeClose": "Modal__overlayExit",
}
}
parentSelector={[Function]}
portalClassName="ReactModalPortal"
role="dialog"
shouldCloseOnEsc={true}
shouldCloseOnOverlayClick={true}
shouldFocusAfterRender={true}
shouldReturnFocusAfterClose={true}
>
Some children
</Modal>
`;
1 change: 1 addition & 0 deletions src/components/Modal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Modal';
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export { default as Input } from './components/Input';
export { default as Link } from './components/Link';
export { List, ListItem, ListActions } from './components/List';
export { default as LoadingSpinner } from './components/LoadingSpinner';
export { default as Modal } from './components/Modal';
export { NavBar, NavItem } from './components/Navigation';
export { PageWidthRestrictor, BlockWidthRestrictor } from './components/WidthRestrictor';
export { default as Pagination } from './components/Pagination';
Expand Down
9 changes: 9 additions & 0 deletions src/utils/OneUI/OneUI.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import cssVarsPonyfill from 'css-vars-ponyfill';
import ReactModal from 'react-modal';

const isInternetExplorer11 = () => {
const ua = window.navigator.userAgent;
Expand Down Expand Up @@ -83,6 +84,14 @@ class OneUI {
head.appendChild(styleElement);
});
}

/**
* Inits ReactModal and sets global params for it.
* @param {string} selector - css selector for the app element
*/
static modalInit(selector) {
ReactModal.setAppElement(selector);
}
}

export default OneUI;
49 changes: 49 additions & 0 deletions stories/Modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { boolean, text, withKnobs } from '@storybook/addon-knobs';
import { withInfo } from '@storybook/addon-info';
import { Modal } from '@textkernel/oneui';

storiesOf('Modal', module)
.addDecorator(withKnobs)
.addDecorator(withInfo)
.add(
'Modal',
() => {
const onClose = () => console.log('Modal was requested to be closed.');
return (
<Modal
isOpen={boolean('Open', false)}
contentLabel={text(
'Label for content visible to screenreaders',
'My superb modal'
)}
onRequestClose={onClose}
>
Some content
</Modal>
);
},
{
info: {
text: `
## Usage information
This component is a wrapper around [react-modal](http://reactcommunity.org/react-modal/#usage).
* You can pass other props according to their definition, apart from classes.
* For accessibility reasons you need to __initialise__ modal use in your app.
For more info see [app element](http://reactcommunity.org/react-modal/accessibility/#app-element) in thier documentation.
To do this:
> import OneUI from '@textkernel/oneui';
> OneUI.__initModal__(appElementSelector);
To see the modal in action here, change the Open prop to 'true' in the Knobs section.
`
}
}
);
1 change: 1 addition & 0 deletions stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import './Input';
import './Link';
import './List';
import './LoadingSpinner';
import './Modal';
import './Navigation';
import './WidthRestrictor';
import './Pagination';
Expand Down

0 comments on commit c46b7b6

Please sign in to comment.