clipboard.tsx 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import {cloneElement, Component, isValidElement} from 'react';
  2. import ReactDOM from 'react-dom';
  3. import copy from 'copy-text-to-clipboard';
  4. import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
  5. import {t} from 'app/locale';
  6. type DefaultProps = {
  7. successMessage: string;
  8. errorMessage: string;
  9. hideMessages: boolean;
  10. };
  11. type Props = {
  12. /** Text to be copied on click */
  13. value: string;
  14. /** Hide children if browser does not support copy */
  15. hideUnsupported?: boolean;
  16. onSuccess?: () => void;
  17. onError?: () => void;
  18. } & DefaultProps;
  19. /**
  20. * copy-text-to-clipboard relies on `document.execCommand('copy')`
  21. */
  22. function isSupported() {
  23. const support = !!document.queryCommandSupported;
  24. return support && !!document.queryCommandSupported('copy');
  25. }
  26. class Clipboard extends Component<Props> {
  27. static defaultProps: DefaultProps = {
  28. hideMessages: false,
  29. successMessage: t('Copied to clipboard'),
  30. errorMessage: t('Error copying to clipboard'),
  31. };
  32. componentWillUnmount() {
  33. this.element?.removeEventListener('click', this.handleClick);
  34. }
  35. element?: ReturnType<typeof ReactDOM.findDOMNode>;
  36. handleClick = () => {
  37. const {value, hideMessages, successMessage, errorMessage, onSuccess, onError} =
  38. this.props;
  39. // Copy returns whether it succeeded to copy the text
  40. const success = copy(value);
  41. if (!success) {
  42. if (!hideMessages) {
  43. addErrorMessage(errorMessage);
  44. }
  45. onError?.();
  46. return;
  47. }
  48. if (!hideMessages) {
  49. addSuccessMessage(successMessage);
  50. }
  51. onSuccess?.();
  52. };
  53. handleMount = (ref: HTMLElement) => {
  54. if (!ref) {
  55. return;
  56. }
  57. // eslint-disable-next-line react/no-find-dom-node
  58. this.element = ReactDOM.findDOMNode(ref);
  59. this.element?.addEventListener('click', this.handleClick);
  60. };
  61. render() {
  62. const {children, hideUnsupported} = this.props;
  63. // Browser doesn't support `execCommand`
  64. if (hideUnsupported && !isSupported()) {
  65. return null;
  66. }
  67. if (!isValidElement(children)) {
  68. return null;
  69. }
  70. return cloneElement(children, {
  71. ref: this.handleMount,
  72. });
  73. }
  74. }
  75. export default Clipboard;