clipboard.tsx 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  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 {
  38. value,
  39. hideMessages,
  40. successMessage,
  41. errorMessage,
  42. onSuccess,
  43. onError,
  44. } = this.props;
  45. // Copy returns whether it succeeded to copy the text
  46. const success = copy(value);
  47. if (!success) {
  48. if (!hideMessages) {
  49. addErrorMessage(errorMessage);
  50. }
  51. onError?.();
  52. return;
  53. }
  54. if (!hideMessages) {
  55. addSuccessMessage(successMessage);
  56. }
  57. onSuccess?.();
  58. };
  59. handleMount = (ref: HTMLElement) => {
  60. if (!ref) {
  61. return;
  62. }
  63. // eslint-disable-next-line react/no-find-dom-node
  64. this.element = ReactDOM.findDOMNode(ref);
  65. this.element?.addEventListener('click', this.handleClick);
  66. };
  67. render() {
  68. const {children, hideUnsupported} = this.props;
  69. // Browser doesn't support `execCommand`
  70. if (hideUnsupported && !isSupported()) {
  71. return null;
  72. }
  73. if (!isValidElement(children)) {
  74. return null;
  75. }
  76. return cloneElement(children, {
  77. ref: this.handleMount,
  78. });
  79. }
  80. }
  81. export default Clipboard;