clipboard.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import {cloneElement, isValidElement, useCallback, useEffect, useState} from 'react';
  2. import {findDOMNode} from 'react-dom';
  3. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  4. import {t} from 'sentry/locale';
  5. type Props = {
  6. children: React.ReactNode;
  7. /**
  8. * Text to be copied on click
  9. */
  10. value: string;
  11. /**
  12. * Toast message to show on copy failures
  13. */
  14. errorMessage?: string;
  15. /**
  16. * Do not show a toast message on success
  17. */
  18. hideMessages?: boolean;
  19. /**
  20. * Hide children if browser does not support copying
  21. */
  22. hideUnsupported?: boolean;
  23. /**
  24. * Triggered if we fail to copy
  25. */
  26. onError?: () => void;
  27. /**
  28. * Trigger if we successfully copy
  29. */
  30. onSuccess?: () => void;
  31. /**
  32. * Message to show when we successfully copy
  33. */
  34. successMessage?: string;
  35. };
  36. /**
  37. * copy-text-to-clipboard relies on `document.execCommand('copy')`
  38. */
  39. function isSupported() {
  40. return !!document.queryCommandSupported?.('copy');
  41. }
  42. /**
  43. * @deprecated
  44. * Replaced by `useCopyToClipboard()` and `<CopyToClipboardButton>`.
  45. */
  46. function Clipboard({
  47. hideMessages = false,
  48. successMessage = t('Copied to clipboard'),
  49. errorMessage = t('Error copying to clipboard'),
  50. value,
  51. onSuccess,
  52. onError,
  53. hideUnsupported,
  54. children,
  55. }: Props) {
  56. const [element, setElement] = useState<ReturnType<typeof findDOMNode>>();
  57. const handleClick = useCallback(() => {
  58. navigator.clipboard
  59. .writeText(value)
  60. .then(() => {
  61. onSuccess?.();
  62. if (!hideMessages) {
  63. addSuccessMessage(successMessage);
  64. }
  65. })
  66. .catch(() => {
  67. onError?.();
  68. if (!hideMessages) {
  69. addErrorMessage(errorMessage);
  70. }
  71. });
  72. }, [value, onError, onSuccess, errorMessage, successMessage, hideMessages]);
  73. useEffect(() => {
  74. element?.addEventListener('click', handleClick);
  75. return () => element?.removeEventListener('click', handleClick);
  76. }, [handleClick, element]);
  77. // XXX: Instead of assigning the `onClick` to the cloned child element, we
  78. // attach a event listener, otherwise we would wipeout whatever click handler
  79. // may be assigned on the child.
  80. const handleMount = useCallback((ref: HTMLElement) => {
  81. // eslint-disable-next-line react/no-find-dom-node
  82. setElement(findDOMNode(ref));
  83. }, []);
  84. // Browser doesn't support `execCommand`
  85. if (hideUnsupported && !isSupported()) {
  86. return null;
  87. }
  88. if (!isValidElement(children)) {
  89. return null;
  90. }
  91. return cloneElement<any>(children, {ref: handleMount});
  92. }
  93. export default Clipboard;