Skip to content

Commit

Permalink
feat(modal): add modal provider
Browse files Browse the repository at this point in the history
  • Loading branch information
zouxuoz committed Sep 18, 2018
1 parent 2037538 commit d56612d
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 25 deletions.
36 changes: 36 additions & 0 deletions src/EightBaseBoostProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// @flow

import React from 'react';
import { ThemeProvider } from 'emotion-theming';

import { ModalProvider } from './atoms/Modal/ModalProvider';
import { defaultTheme, resetGlobal } from './theme';

type EightBaseBoostProviderProps = {
theme?: Object,
children: React$Node,
};

class EightBaseBoostProvider extends React.Component<EightBaseBoostProviderProps> {
static defaultProps = {
theme: defaultTheme,
};

componentDidMount() {
resetGlobal();
}

render() {
const { theme, children } = this.props;

return (
<ThemeProvider theme={ theme }>
<ModalProvider>
{ children }
</ModalProvider>
</ThemeProvider>
);
}
}

export { EightBaseBoostProvider };
31 changes: 29 additions & 2 deletions src/atoms/Dialog/Dialog.stories.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';

export default (asStory) => {
asStory('ATOMS/Dialog (skip shot)', module, (story, { Dialog, Paragraph, Button }) => {
asStory('ATOMS/Dialog (skip shot)', module, (story, { ModalConsumer, Dialog, Paragraph, Button }) => {
story
.add('with default header, body, actions and footer (skip shot)', () => (
<Dialog.Plate isOpen size="sm">
<Dialog.Plate id="ID" size="sm">
<Dialog.Header title="Mark Job as Completed" />
<Dialog.Body>
<Paragraph>
Expand All @@ -16,6 +16,33 @@ export default (asStory) => {
<Button type="submit" text="Apply" />
</Dialog.Footer>
</Dialog.Plate>
))
.add('with state', () => (
<React.Fragment>
<Dialog.Plate id="ID" size="sm">
{
({ args, onClose }) => (
<React.Fragment>
<Dialog.Header title="Mark Job as Completed" onClose={ onClose } />
<Dialog.Body>
<Paragraph>
Fagelia cancrivorous { args.foo } Curucaneca Echinocaris { args.bar } glassful agronomics
</Paragraph>
</Dialog.Body>
<Dialog.Footer>
<Button color="neutral" variant="outlined" text="Cancel" onClick={ onClose } />
<Button type="submit" text="Apply" />
</Dialog.Footer>
</React.Fragment>
)
}
</Dialog.Plate>
<ModalConsumer>
{
({ openModal }) => <Button onClick={ () => openModal('ID', { foo: '00', bar: '00' }) }>Open</Button>
}
</ModalConsumer>
</React.Fragment>
));
});
};
18 changes: 12 additions & 6 deletions src/atoms/Dialog/DialogPlate.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,26 @@ const StyledTag = createStyledTag(name, {

function DialogPlate({
children,
id,
isOpen,
onOpen,
onClose,
shouldCloseOnOverlayClick,
size,
args,
...rest
}: DialogPlateProps) {
return (
<Modal isOpen={ isOpen } onOpen={ onOpen } onClose={ onClose } shouldCloseOnOverlayClick={ shouldCloseOnOverlayClick }>
<StyledTag tagName="div" size={ size }>
<Card.Plate { ...rest }>
{ children }
</Card.Plate>
</StyledTag>
<Modal id={ id } isOpen={ isOpen } onOpen={ onOpen } onClose={ onClose } args={ args } shouldCloseOnOverlayClick={ shouldCloseOnOverlayClick }>
{
({ args, onClose }) => (
<StyledTag tagName="div" size={ size }>
<Card.Plate { ...rest } args={ args } onClose={ onClose }>
{ children }
</Card.Plate>
</StyledTag>
)
}
</Modal>
);
}
Expand Down
12 changes: 7 additions & 5 deletions src/atoms/Modal/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { Portal } from 'react-portal';
import { injectGlobal } from 'emotion';

import { createStyledTag, createTheme } from '../../utils';
import { withModalState } from './withModalState';

type ModalProps = {
children: React$Node,
isOpen?: boolean,
onClose?: (any) => void,
shouldCloseOnOverlayClick?: boolean,
id?: string,
};

type ModalState = {
Expand Down Expand Up @@ -88,8 +90,6 @@ class Modal extends PureComponent<ModalProps, ModalState> {
if (this.props.isOpen) {
Modal.openedModals -= 1;

this.setState({ isOpen: false });

if (typeof this.props.onClose === 'function') {
this.props.onClose();
}
Expand Down Expand Up @@ -120,7 +120,7 @@ class Modal extends PureComponent<ModalProps, ModalState> {

onOverlayMouseDown = () => {
if (this.props.shouldCloseOnOverlayClick) {
this.closeModal();
this.props.onClose();
}
};

Expand All @@ -129,14 +129,14 @@ class Modal extends PureComponent<ModalProps, ModalState> {
};

render() {
const { children, isOpen } = this.props;
const { children, isOpen, ...rest } = this.props;

return (
<If condition={ isOpen }>
<Portal>
<OverlayTag tagName="div" onMouseDown={ this.onOverlayMouseDown }>
<ModalTag tagName="div" onMouseDown={ this.onModalMouseDown }>
{ children }
{ typeof children === 'function' ? children(rest) : children }
</ModalTag>
</OverlayTag>
</Portal>
Expand All @@ -145,4 +145,6 @@ class Modal extends PureComponent<ModalProps, ModalState> {
}
}

Modal = withModalState(Modal);

export { Modal, theme };
29 changes: 28 additions & 1 deletion src/atoms/Modal/Modal.stories.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

export default (asStory) => {
asStory('ATOMS/Modal (skip shot)', module, (story, { Modal }) => {
asStory('ATOMS/Modal (skip shot)', module, (story, { Modal, ModalConsumer, Button }) => {
story
.add('multiple modals', () => (
<React.Fragment>
Expand All @@ -25,6 +25,33 @@ export default (asStory) => {
00000<br />
</Modal>
</React.Fragment>
))
.add('with state', () => (
<React.Fragment>
<Modal id="ID">
{
({ args }) => (
<React.Fragment>
XXXXXXXXXX<br />
XXXXXXXXXX<br />
XXXXXXXXXX<br />
XXXXXXXXXX<br />
XXXX{ args.foo }XXXX<br />
XXXX{ args.bar }XXXX<br />
XXXXXXXXXX<br />
XXXXXXXXXX<br />
XXXXXXXXXX<br />
XXXXXXXXXX<br />
</React.Fragment>
)
}
</Modal>
<ModalConsumer>
{
({ openModal }) => <Button onClick={ () => openModal('ID', { foo: '00', bar: '00' }) }>Open</Button>
}
</ModalConsumer>
</React.Fragment>
));
});
};
Expand Down
5 changes: 5 additions & 0 deletions src/atoms/Modal/ModalContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

const { Provider, Consumer } = React.createContext('modal');

export { Provider, Consumer };
46 changes: 46 additions & 0 deletions src/atoms/Modal/ModalProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { Component } from 'react';

import { Provider } from './ModalContext';

class ModalProvider extends Component {
constructor(props) {
super(props);

this.state = {};
}

openModal = (id, args = {}) => {
this.setState({
[id]: {
isOpen: true,
args,
},
});
};

closeModal = (id) => {
this.setState({
[id]: {
isOpen: false,
},
});
}

collectContextValue = () => ({
openModal: this.openModal,
closeModal: this.closeModal,
state: this.state,
});

render() {
const contextValue = this.collectContextValue();

return (
<Provider value={ contextValue }>
{ this.props.children }
</Provider>
);
}
}

export { ModalProvider };
25 changes: 25 additions & 0 deletions src/atoms/Modal/withModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

import { Consumer } from './ModalContext';

const withModal = (BaseComponent) => {
class ModalStateConsumer extends React.Component {
renderBaseComponent = (dialogContext) => {
const props = {
...this.props,
openModal: dialogContext.openModal,
closeModal: dialogContext.closeModal,
};

return <BaseComponent { ...props } />;
}

render() {
return <Consumer>{ this.renderBaseComponent }</Consumer>;
}
}

return ModalStateConsumer;
};

export { withModal };
34 changes: 34 additions & 0 deletions src/atoms/Modal/withModalState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import { Consumer } from './ModalContext';

const withModalState = (BaseComponent) => {
class ModalStateConsumer extends React.Component {
renderBaseComponentWithModalProps = (modalContext) => {
const { id } = this.props;

const props = {
...this.props,
onClose: () => modalContext.closeModal(id),
isOpen: modalContext.state[id] ? modalContext.state[id].isOpen : false,
args: modalContext.state[id] ? modalContext.state[id].args : undefined,
};

return <BaseComponent { ...props } />;
}

render() {
const { id, ...rest } = this.props;

if (id) {
return <Consumer>{ this.renderBaseComponentWithModalProps }</Consumer>;
}

return <BaseComponent { ...rest } />;
}
}

return ModalStateConsumer;
};

export { withModalState };
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ export { ThemeProvider } from 'emotion-theming';
export { defaultTheme, resetGlobal, Z_INDEX } from './theme';
export * from './atoms';
export * from './molecules';
export { Consumer as ModalConsumer } from './atoms/Modal/ModalContext';
export { withModal } from './atoms/Modal/withModal';
export { EightBaseBoostProvider } from './EightBaseBoostProvider';

15 changes: 4 additions & 11 deletions storybook/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,20 @@

import React from 'react';
import styled from 'react-emotion';
import { BrowserRouter } from 'react-router-dom';
import { storiesOf } from '@storybook/react';
import * as boost from '../src';
import { withInfo } from '@storybook/addon-info';

const { ThemeProvider, defaultTheme, resetGlobal, ...components } = boost;

resetGlobal();
const { EightBaseBoostProvider, ...components } = boost;

const Root = styled('div')`
margin: 2rem;
`;

const ThemeDecorator = (storyFn) => (
<Root>
<BrowserRouter>
<ThemeProvider theme={ defaultTheme }>
{ storyFn() }
</ThemeProvider>
</BrowserRouter>
</Root>
<EightBaseBoostProvider>
<Root>{ storyFn() }</Root>
</EightBaseBoostProvider>
);

export const asStory = (name: string, module: *, init: *) => {
Expand Down

0 comments on commit d56612d

Please sign in to comment.