globalModal.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import React from 'react';
  2. // eslint-disable-next-line no-restricted-imports
  3. import Modal from 'react-bootstrap/lib/Modal';
  4. import {browserHistory} from 'react-router';
  5. import {ClassNames} from '@emotion/react';
  6. import {closeModal, ModalOptions, ModalRenderProps} from 'app/actionCreators/modal';
  7. import Confirm from 'app/components/confirm';
  8. import ModalStore from 'app/stores/modalStore';
  9. type DefaultProps = {
  10. options: ModalOptions;
  11. visible: boolean;
  12. };
  13. type Props = DefaultProps & {
  14. /**
  15. * Needs to be a function that returns a React Element
  16. * Function is injected with:
  17. * Modal `Header`, `Body`, and `Footer`,
  18. * `closeModal`
  19. *
  20. */
  21. children?: null | ((renderProps: ModalRenderProps) => React.ReactNode);
  22. /**
  23. * Note this is the callback for the main App container and
  24. * NOT the calling component. GlobalModal is never used directly,
  25. * but is controlled via stores. To access the onClose callback from
  26. * the component, you must specify it when using the action creator.
  27. */
  28. onClose?: () => void;
  29. };
  30. class GlobalModal extends React.Component<Props> {
  31. static defaultProps: DefaultProps = {
  32. visible: false,
  33. options: {},
  34. };
  35. handleCloseModal = () => {
  36. const {options, onClose} = this.props;
  37. // onClose callback for calling component
  38. if (typeof options.onClose === 'function') {
  39. options.onClose();
  40. }
  41. // Action creator
  42. closeModal();
  43. if (typeof onClose === 'function') {
  44. onClose();
  45. }
  46. };
  47. render() {
  48. const {visible, children, options} = this.props;
  49. const renderedChild =
  50. typeof children === 'function'
  51. ? children({
  52. closeModal: this.handleCloseModal,
  53. Header: Modal.Header,
  54. Body: Modal.Body,
  55. Footer: Modal.Footer,
  56. })
  57. : undefined;
  58. if (options && options.type === 'confirm') {
  59. return <Confirm onConfirm={() => {}}>{() => renderedChild}</Confirm>;
  60. }
  61. return (
  62. <ClassNames>
  63. {({css, cx}) => (
  64. <Modal
  65. className={cx(
  66. options?.modalClassName,
  67. options?.modalCss && css(options.modalCss)
  68. )}
  69. dialogClassName={options && options.dialogClassName}
  70. show={visible}
  71. animation={false}
  72. onHide={this.handleCloseModal}
  73. backdrop={options?.backdrop}
  74. >
  75. {renderedChild}
  76. </Modal>
  77. )}
  78. </ClassNames>
  79. );
  80. }
  81. }
  82. type State = {
  83. modalStore: ReturnType<typeof ModalStore.get>;
  84. };
  85. class GlobalModalContainer extends React.Component<Partial<Props>, State> {
  86. state: State = {
  87. modalStore: ModalStore.get(),
  88. };
  89. componentDidMount() {
  90. // Listen for route changes so we can dismiss modal
  91. this.unlistenBrowserHistory = browserHistory.listen(() => closeModal());
  92. }
  93. componentWillUnmount() {
  94. this.unlistenBrowserHistory?.();
  95. this.unlistener?.();
  96. }
  97. unlistener = ModalStore.listen(
  98. (modalStore: State['modalStore']) => this.setState({modalStore}),
  99. undefined
  100. );
  101. unlistenBrowserHistory?: ReturnType<typeof browserHistory.listen>;
  102. render() {
  103. const {modalStore} = this.state;
  104. const visible = !!modalStore && typeof modalStore.renderer === 'function';
  105. return (
  106. <GlobalModal {...this.props} {...modalStore} visible={visible}>
  107. {visible ? modalStore.renderer : null}
  108. </GlobalModal>
  109. );
  110. }
  111. }
  112. export default GlobalModalContainer;