import {cloneElement, Component, isValidElement} from 'react'; import ReactDOM from 'react-dom'; import copy from 'copy-text-to-clipboard'; import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator'; import {t} from 'app/locale'; type DefaultProps = { successMessage: string; errorMessage: string; hideMessages: boolean; }; type Props = { /** Text to be copied on click */ value: string; /** Hide children if browser does not support copy */ hideUnsupported?: boolean; onSuccess?: () => void; onError?: () => void; } & DefaultProps; /** * copy-text-to-clipboard relies on `document.execCommand('copy')` */ function isSupported() { const support = !!document.queryCommandSupported; return support && !!document.queryCommandSupported('copy'); } class Clipboard extends Component<Props> { static defaultProps: DefaultProps = { hideMessages: false, successMessage: t('Copied to clipboard'), errorMessage: t('Error copying to clipboard'), }; componentWillUnmount() { this.element?.removeEventListener('click', this.handleClick); } element?: ReturnType<typeof ReactDOM.findDOMNode>; handleClick = () => { const { value, hideMessages, successMessage, errorMessage, onSuccess, onError, } = this.props; // Copy returns whether it succeeded to copy the text const success = copy(value); if (!success) { if (!hideMessages) { addErrorMessage(errorMessage); } onError?.(); return; } if (!hideMessages) { addSuccessMessage(successMessage); } onSuccess?.(); }; handleMount = (ref: HTMLElement) => { if (!ref) { return; } // eslint-disable-next-line react/no-find-dom-node this.element = ReactDOM.findDOMNode(ref); this.element?.addEventListener('click', this.handleClick); }; render() { const {children, hideUnsupported} = this.props; // Browser doesn't support `execCommand` if (hideUnsupported && !isSupported()) { return null; } if (!isValidElement(children)) { return null; } return cloneElement(children, { ref: this.handleMount, }); } } export default Clipboard;