feat: New dialog service (#1202)
* Add dialog provider boilerplate and improve provider imports * Add react-draggable dependency * Add dialog service boilerplate * Refactor modal to use react-modal and remove dependency on modal themed styles * Update cinedialog to use new dialog provider * Add multiple dialogs and refactor provider (dialogs) * Set app root (modal) * Add modal zindex bigger than tooltip * Block drag on buttons/svgs/paths/input and improve jsdoc * Use guid util to generate dialog ids * Explicit props in dialog provider * Improve jsdocs * Fix tests broken * Fix boundaries calculation * Remember last dialog position * Update providers location * Add scroll to modal * Add toggable button toolbar and fix css modal
This commit is contained in:
1 parent
cdd75bda2a
commit
f65639c2b0
39 files changed
+741
-193
No files matched your search
@@ -1,28 +0,0 @@
|
||||
// Reference > https://reactjs.org/docs/context.html
|
||||
import React from 'react';
|
||||
import {
|
||||
withTranslation as I18NextWithTranslation,
|
||||
I18nextProvider,
|
||||
} from 'react-i18next';
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
const WrapperI18n = Component => {
|
||||
const WrapperComponent = props => (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Component {...props} />
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
return WrapperComponent;
|
||||
};
|
||||
|
||||
const withTranslation = namespace => Component => {
|
||||
const TranslatedComponent = props => {
|
||||
return <Component {...props} />;
|
||||
};
|
||||
|
||||
return WrapperI18n(I18NextWithTranslation(namespace)(TranslatedComponent));
|
||||
};
|
||||
|
||||
export { withTranslation };
|
||||
export default withTranslation;
|
||||
@@ -1,122 +0,0 @@
|
||||
import React, {
|
||||
useState,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const ModalContext = createContext(null);
|
||||
const { Provider } = ModalContext;
|
||||
|
||||
export const useModal = () => useContext(ModalContext);
|
||||
|
||||
const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
const DEFAULT_OPTIONS = {
|
||||
component: null /* The component instance inside the modal. */,
|
||||
header: null /* The content inside the modal header. */,
|
||||
footer: null /* The content inside the modal footer. */,
|
||||
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);
|
||||
|
||||
/**
|
||||
* Sets the implementation of a modal service that can be used by extensions.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (service) {
|
||||
service.setServiceImplementation({ hide, show });
|
||||
}
|
||||
}, [hide, service, show]);
|
||||
|
||||
/**
|
||||
* Show the modal and override its configuration props.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const show = useCallback(
|
||||
(component, props = {}) =>
|
||||
setOptions(Object.assign({}, options, props, { component })),
|
||||
[options]
|
||||
);
|
||||
|
||||
/**
|
||||
* Hide the modal and set its properties to default.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [
|
||||
DEFAULT_OPTIONS,
|
||||
]);
|
||||
|
||||
const { component: Component } = options;
|
||||
|
||||
return (
|
||||
<Provider value={{ show, hide }}>
|
||||
{options.component && (
|
||||
<Modal
|
||||
className={classNames(
|
||||
options.customClassName,
|
||||
options.component.className
|
||||
)}
|
||||
backdrop={options.backdrop}
|
||||
keyboard={options.keyboard}
|
||||
show={options.show}
|
||||
title={options.title}
|
||||
closeButton={options.closeButton}
|
||||
footer={options.footer}
|
||||
header={options.header}
|
||||
onHide={hide}
|
||||
>
|
||||
<Component {...options} show={show} hide={hide} />
|
||||
</Modal>
|
||||
)}
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ModalProvider.defaultProps = {
|
||||
service: null,
|
||||
};
|
||||
|
||||
ModalProvider.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
modal: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]).isRequired,
|
||||
service: PropTypes.shape({
|
||||
setServiceImplementation: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Higher Order Component to use the modal methods through a Class Component.
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export const withModal = Component => {
|
||||
return function WrappedComponent(props) {
|
||||
const { show, hide } = useModal();
|
||||
return <Component {...props} modal={{ show, hide }} />;
|
||||
};
|
||||
};
|
||||
|
||||
export default ModalProvider;
|
||||
|
||||
export const ModalConsumer = ModalContext.Consumer;
|
||||
@@ -1,142 +0,0 @@
|
||||
import React, {
|
||||
useState,
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
|
||||
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
|
||||
|
||||
const SnackbarContext = createContext(null);
|
||||
|
||||
export const useSnackbarContext = () => useContext(SnackbarContext);
|
||||
|
||||
const SnackbarProvider = ({ children, service }) => {
|
||||
const DEFAULT_OPTIONS = {
|
||||
title: '',
|
||||
message: '',
|
||||
duration: 5000,
|
||||
autoClose: true,
|
||||
position: 'bottomRight',
|
||||
type: SnackbarTypes.INFO,
|
||||
};
|
||||
|
||||
const [count, setCount] = useState(1);
|
||||
const [snackbarItems, setSnackbarItems] = useState([]);
|
||||
|
||||
/**
|
||||
* Sets the implementation of a notification service that can be used by extensions.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (service) {
|
||||
service.setServiceImplementation({ hide, show });
|
||||
}
|
||||
}, [service, hide, show]);
|
||||
|
||||
const show = useCallback(
|
||||
options => {
|
||||
if (!options || (!options.title && !options.message)) {
|
||||
console.warn(
|
||||
'Snackbar cannot be rendered without required parameters: title | message'
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const newItem = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
id: count,
|
||||
visible: true,
|
||||
};
|
||||
|
||||
setSnackbarItems(state => [...state, newItem]);
|
||||
setCount(count + 1);
|
||||
},
|
||||
[count, DEFAULT_OPTIONS]
|
||||
);
|
||||
|
||||
const hide = useCallback(
|
||||
id => {
|
||||
const hideItem = items => {
|
||||
const newItems = items.map(item => {
|
||||
if (item.id === id) {
|
||||
item.visible = false;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
return newItems;
|
||||
};
|
||||
|
||||
setSnackbarItems(state => hideItem(state));
|
||||
|
||||
setTimeout(() => {
|
||||
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
|
||||
}, 1000);
|
||||
},
|
||||
[setSnackbarItems]
|
||||
);
|
||||
|
||||
const hideAll = () => {
|
||||
// reset count
|
||||
setCount(1);
|
||||
|
||||
// remove all items from array
|
||||
setSnackbarItems(() => []);
|
||||
};
|
||||
|
||||
/**
|
||||
* expose snackbar methods to window for debug purposes
|
||||
* TODO: Check if it's really necessary
|
||||
*/
|
||||
window.snackbar = {
|
||||
show,
|
||||
hide,
|
||||
hideAll,
|
||||
};
|
||||
|
||||
return (
|
||||
<SnackbarContext.Provider value={{ show, hide, hideAll, snackbarItems }}>
|
||||
{!!snackbarItems && <SnackbarContainer />}
|
||||
{children}
|
||||
</SnackbarContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
SnackbarProvider.defaultProps = {
|
||||
service: null,
|
||||
};
|
||||
|
||||
SnackbarProvider.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]).isRequired,
|
||||
service: PropTypes.shape({
|
||||
setServiceImplementation: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* High Order Component to use the snackbar methods through a Class Component
|
||||
*
|
||||
*/
|
||||
export const withSnackbar = Component => {
|
||||
return function WrappedComponent(props) {
|
||||
const snackbarContext = {
|
||||
...useSnackbarContext(),
|
||||
};
|
||||
return <Component {...props} snackbarContext={snackbarContext} />;
|
||||
};
|
||||
};
|
||||
|
||||
export default SnackbarProvider;
|
||||
Reference in new issue
Block a user