useCopyToClipboard.tsx 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import {useCallback, useRef, useState} from 'react';
  2. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  3. import {t} from 'sentry/locale';
  4. type Opts = {
  5. /**
  6. * The text that you want to copy.
  7. *
  8. * Use `JSON.stringify()` if you have an object to share.
  9. *
  10. * If/When `new ClipboardItem()` accepts a mime type of application/json then
  11. * it could accept other types, but for now string is the most common case.
  12. */
  13. text: string;
  14. /**
  15. * The toast message that will appear if an error happens when copying.
  16. *
  17. * Disable toast messages by setting the `hideMessages` prop.
  18. */
  19. errorMessage?: React.ReactNode;
  20. /**
  21. * Disable creating toast messages when copying has succeeded/errored.
  22. */
  23. hideMessages?: boolean;
  24. /**
  25. * Callback after copying is complete.
  26. */
  27. onCopy?: undefined | ((copiedText: string) => void);
  28. /**
  29. * Callback if an error happened while copying.
  30. */
  31. onError?: undefined | ((error: Error) => void);
  32. /**
  33. * The toast messaage that will appear after the copy operation is done.
  34. *
  35. * Disable toast messages by setting the `hideMessages` prop.
  36. */
  37. successMessage?: React.ReactNode;
  38. };
  39. export default function useCopyToClipboard({
  40. errorMessage = t('Error copying to clipboard'),
  41. hideMessages,
  42. onCopy,
  43. onError,
  44. successMessage = t('Copied to clipboard'),
  45. text,
  46. }: Opts) {
  47. const timeoutRef = useRef<undefined | ReturnType<typeof setTimeout>>();
  48. const [state, setState] = useState<'ready' | 'copied' | 'error'>('ready');
  49. const handleOnClick = useCallback(() => {
  50. navigator.clipboard
  51. .writeText(text)
  52. .then(() => {
  53. setState('copied');
  54. if (!hideMessages) {
  55. addSuccessMessage(successMessage);
  56. }
  57. onCopy?.(text);
  58. })
  59. .catch(error => {
  60. setState('error');
  61. if (!hideMessages) {
  62. addErrorMessage(errorMessage);
  63. }
  64. onError?.(error);
  65. })
  66. .finally(() => {
  67. if (timeoutRef.current) {
  68. clearTimeout(timeoutRef.current);
  69. }
  70. timeoutRef.current = setTimeout(() => setState('ready'), 1000);
  71. });
  72. }, [errorMessage, hideMessages, onCopy, onError, successMessage, text]);
  73. const label =
  74. state === 'ready'
  75. ? t('Copy')
  76. : state === 'copied'
  77. ? t('Copied')
  78. : t('Unable to copy');
  79. return {
  80. onClick: handleOnClick,
  81. label,
  82. };
  83. }