feat: 🎸 New modal provider (#1110)

* feat: 🎸 New modal provider

A new modal provider/context component to unify modal related code and
simplify modal use

Closes: #1086

* refactor(ModalContext): Keep current modal lib

* Update modal provider version according to PR #1116

* Update modal provider props to use inner props

* Add custom class prop

* CR Update: Refactor provider to extract specific modal

* Fix modal import

* CR Update: Move from spread to object assign

* CR Update: Add proptypes, use classnames dependency over interpolation and rename modal
This commit is contained in:
Igor Octaviano 2019-11-04 12:55:05 -03:00 committed by Danny Brown
parent 8baba22a2e
commit 5ee832b195
7 changed files with 209 additions and 9 deletions

View File

@ -16,6 +16,7 @@ import { QuickSwitch } from './quickSwitch';
import { RoundedButtonGroup } from './roundedButtonGroup';
import { SelectTree } from './selectTree';
import { SimpleDialog } from './simpleDialog';
import { OHIFModal } from './ohifModal';
import {
PageToolbar,
StudyList,
@ -52,4 +53,5 @@ export {
AboutModal,
UserPreferences,
UserPreferencesModal,
OHIFModal,
};

View File

@ -0,0 +1,60 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactBootstrapModal from 'react-bootstrap-modal';
import classNames from 'classnames';
const OHIFModal = ({
className,
closeButton,
backdrop,
keyboard,
show,
title,
onHide,
footer: Footer,
header: Header,
children: Component,
}) => (
<ReactBootstrapModal
className={classNames('modal fade themed in', className)}
backdrop={backdrop}
keyboard={keyboard}
show={show}
large={true}
title={title}
onHide={onHide}
>
{(Header || title) && (
<ReactBootstrapModal.Header closeButton={closeButton}>
{title && (
<ReactBootstrapModal.Title>{title}</ReactBootstrapModal.Title>
)}
{Header && <Header hide={onHide} />}
</ReactBootstrapModal.Header>
)}
<ReactBootstrapModal.Body>
{Component && <Component hide={onHide} />}
</ReactBootstrapModal.Body>
{Footer && (
<ReactBootstrapModal.Footer>
{' '}
<Footer hide={onHide} />
</ReactBootstrapModal.Footer>
)}
</ReactBootstrapModal>
);
OHIFModal.propTypes = {
className: PropTypes.string,
closeButton: PropTypes.bool,
backdrop: PropTypes.bool,
keyboard: PropTypes.bool,
show: PropTypes.bool,
title: PropTypes.string,
onHide: PropTypes.func,
footer: PropTypes.node,
header: PropTypes.node,
children: PropTypes.node,
};
export default OHIFModal;

View File

@ -0,0 +1,39 @@
---
name: OHIFModal
menu: Components
route: /components/ohif-modal
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { OHIFModal } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# OHIF Modal
## Basic usage
<Playground>
<State
initial={{}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<OHIFModal />
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={OHIFModal} />
## Translation Namespace
<NameSpace name="OHIFModal" />

View File

@ -0,0 +1,2 @@
import OHIFModal from './OHIFModal';
export { OHIFModal };

View File

@ -25,6 +25,7 @@ import {
AboutModal,
UserPreferences,
UserPreferencesModal,
OHIFModal,
} from './components';
import { useDebounce, useMedia } from './hooks';
@ -52,6 +53,11 @@ import SnackbarProvider, {
useSnackbarContext,
withSnackbar,
} from './utils/SnackbarProvider';
import ModalProvider, {
useModal,
withModal,
ModalConsumer,
} from './utils/ModalProvider';
export {
// Elements
@ -100,6 +106,11 @@ export {
SnackbarProvider,
useSnackbarContext,
withSnackbar,
ModalProvider,
useModal,
ModalConsumer,
withModal,
OHIFModal,
// Hooks
useDebounce,
useMedia,

View File

@ -0,0 +1,83 @@
import React, { useState, createContext, useContext } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
const ModalContext = createContext(null);
const { Provider, Consumer } = ModalContext;
export const useModal = () => useContext(ModalContext);
const ModalProvider = ({ children, modal: Modal }) => {
const DEFAULT_OPTIONS = {
component: null /* The component instance inside the modal. */,
backdrop: false /* Should the modal render a backdrop overlay. */,
keyboard: false /* Modal is dismissible via the esc key. */,
show: true /* Make the Modal visible or hidden. */,
closeButton: true /* Should the modal body render the close button. */,
title: null /* Should the modal render the title independently of the body content. */,
customClassName: null /* The custom class to style the modal. */,
};
const [options, setOptions] = useState(DEFAULT_OPTIONS);
/**
* Show the modal and override its configuration props.
*
* @returns void
*/
const show = (component, props = {}) =>
setOptions(Object.assign({}, options, props, { component }));
/**
* Hide the modal and set its properties to default.
*
* @returns void
*/
const hide = () => setOptions(DEFAULT_OPTIONS);
return (
<Provider value={{ ...options, show, hide }}>
<Consumer>
{props => {
const { component, footer, header, customClassName } = props;
return component ? (
<Modal
className={classNames(customClassName, component.className)}
backdrop={options.backdrop}
keyboard={options.keyboard}
show={options.show}
title={options.title}
closeButton={options.closeButton}
onHide={hide}
footer={footer}
header={header}
>
{component}
</Modal>
) : null;
}}
</Consumer>
{children}
</Provider>
);
};
ModalProvider.propTypes = {
children: PropTypes.node,
modal: PropTypes.node,
};
/**
* Higher Order Component to use the modal methods through a Class Component.
*
* @returns
*/
export const withModal = Component => {
return function WrappedComponent(props) {
return <Component {...props} modalContext={{ ...useModal() }} />;
};
};
export default ModalProvider;
export const ModalConsumer = ModalContext.Consumer;

View File

@ -28,7 +28,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
import { getActiveContexts } from './store/layout/selectors.js';
import i18n from '@ohif/i18n';
import store from './store';
import { SnackbarProvider } from '@ohif/ui';
import { SnackbarProvider, ModalProvider, OHIFModal } from '@ohif/ui';
// Contexts
import WhiteLabellingContext from './context/WhiteLabellingContext';
@ -85,6 +85,7 @@ class App extends Component {
}
render() {
const { whiteLabelling, routerBasename } = this.props;
const userManager = this._userManager;
const config = {
appConfig: this._appConfig,
@ -97,12 +98,12 @@ class App extends Component {
<I18nextProvider i18n={i18n}>
<OidcProvider store={store} userManager={userManager}>
<UserManagerContext.Provider value={userManager}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider
value={this.props.whiteLabelling}
>
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider>
<OHIFStandaloneViewer userManager={userManager} />
<ModalProvider modal={OHIFModal}>
<OHIFStandaloneViewer userManager={userManager} />
</ModalProvider>
</SnackbarProvider>
</WhiteLabellingContext.Provider>
</Router>
@ -118,10 +119,12 @@ class App extends Component {
<AppContext.Provider value={config}>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider>
<OHIFStandaloneViewer />
<ModalProvider modal={OHIFModal}>
<OHIFStandaloneViewer />
</ModalProvider>
</SnackbarProvider>
</WhiteLabellingContext.Provider>
</Router>