import * as React from 'react'; import {ModalRenderProps, openModal} from 'app/actionCreators/modal'; import Button from 'app/components/button'; import ButtonBar from 'app/components/buttonBar'; import {t} from 'app/locale'; export type ConfirmMessageRenderProps = { /** * Confirms the modal */ confirm: () => void; /** * Closes the modal, if `bypass` is true, will call `onConfirm` callback */ close: (e: React.MouseEvent) => void; /** * Set the disabled state of the confirm button */ disableConfirmButton: (disable: boolean) => void; /** * When the modal is confirmed the function registered will be called. * * Useful if your rendered message contains some functionality that should be * triggered upon the modal being confirmed. * * This should be called in the components componentDidMount. */ setConfirmCallback: (cb: () => void) => void; }; type ChildrenRenderProps = { open: () => void; }; type Props = { /** * Callback when user confirms */ onConfirm?: () => void; /** * If true, will skip the confirmation modal and call `onConfirm` callback */ bypass?: boolean; /** * Message to display to user when asking for confirmation */ message?: React.ReactNode; /** * Used to render a message instead of using the static `message` prop. */ renderMessage?: (renderProps: ConfirmMessageRenderProps) => React.ReactNode; /** * Render props to control rendering of the modal in its entirety */ children?: | ((renderProps: ChildrenRenderProps) => React.ReactNode) | React.ReactElement<{disabled: boolean; onClick: (e: React.MouseEvent) => void}>; /** * Passed to `children` render function */ disabled?: boolean; /** * Callback function when user is in the confirming state called when the * confirm modal is opened */ onConfirming?: () => void; /** * User cancels the modal */ onCancel?: () => void; /** * Header of modal */ header?: React.ReactNode; /** * Stop event propagation when opening the confirm modal */ stopPropagation?: boolean; /** * Disables the confirm button. * * XXX: Once the modal has been opened mutating this property will _not_ * propagate into the modal. * * If you need the confirm buttons disabled state to be reactively * controlled, consider using the renderMessage prop, which receives a * `disableConfirmButton` function that you may use to control the state of it. */ disableConfirmButton?: boolean; /** * Button priority */ priority?: React.ComponentProps['priority']; /** * Text to show in the cancel button */ cancelText?: React.ReactNode; /** * Text to show in the confirmation button */ confirmText?: React.ReactNode; }; function Confirm({ bypass, renderMessage, message, header, disabled, children, onConfirm, onConfirming, onCancel, priority = 'primary', cancelText = t('Cancel'), confirmText = t('Confirm'), stopPropagation = false, disableConfirmButton = false, }: Props) { const triggerModal = (e?: React.MouseEvent) => { if (stopPropagation) { e?.stopPropagation(); } if (disabled) { return; } if (bypass) { onConfirm?.(); return; } onConfirming?.(); const modalProps = { priority, renderMessage, message, confirmText, cancelText, header, onConfirm, onCancel, disableConfirmButton, }; openModal(renderProps => ); }; if (typeof children === 'function') { return children({open: triggerModal}); } if (!React.isValidElement(children)) { return null; } // TODO(ts): Understand why the return type of `cloneElement` is strange return React.cloneElement(children, {disabled, onClick: triggerModal}) as any; } type ModalProps = ModalRenderProps & Pick< Props, | 'priority' | 'renderMessage' | 'message' | 'confirmText' | 'cancelText' | 'header' | 'onConfirm' | 'onCancel' | 'disableConfirmButton' >; type ModalState = { /** * Is confirm button disabled */ disableConfirmButton: boolean; /** * The callback registered from the rendered message to call */ confirmCallback: null | (() => void); }; class ConfirmModal extends React.Component { state: ModalState = { disableConfirmButton: !!this.props.disableConfirmButton, confirmCallback: null, }; confirming: boolean = false; handleClose = () => { const {disableConfirmButton, onCancel, closeModal} = this.props; onCancel?.(); this.setState({disableConfirmButton: disableConfirmButton ?? false}); // always reset `confirming` when modal visibility changes this.confirming = false; closeModal(); }; handleConfirm = () => { const {onConfirm, closeModal} = this.props; // `confirming` is used to ensure `onConfirm` or the confirm callback is // only called once if (!this.confirming) { onConfirm?.(); this.state.confirmCallback?.(); } this.setState({disableConfirmButton: true}); this.confirming = true; closeModal(); }; get confirmMessage() { const {message, renderMessage} = this.props; if (typeof renderMessage === 'function') { return renderMessage({ confirm: this.handleConfirm, close: this.handleClose, disableConfirmButton: (state: boolean) => this.setState({disableConfirmButton: state}), setConfirmCallback: (confirmCallback: () => void) => this.setState({confirmCallback}), }); } if (React.isValidElement(message)) { return message; } return (

{message}

); } render() { const {Header, Body, Footer, priority, confirmText, cancelText, header} = this.props; return ( {header &&
{header}
} {this.confirmMessage}
); } } export default Confirm;