index.tsx 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { ClipboardDocumentIcon, CheckIcon } from "@heroicons/react/24/outline";
  2. import clsx from "clsx";
  3. import type { ComponentProps } from "react";
  4. import { useCallback, useEffect, useState } from "react";
  5. import { Button } from "react-aria-components";
  6. import { useLogger } from "../../hooks/use-logger";
  7. type CopyButtonProps = ComponentProps<typeof Button> & {
  8. text: string;
  9. };
  10. export const CopyButton = ({
  11. text,
  12. children,
  13. className,
  14. ...props
  15. }: CopyButtonProps) => {
  16. const [isCopied, setIsCopied] = useState(false);
  17. const logger = useLogger();
  18. const copy = useCallback(() => {
  19. navigator.clipboard
  20. .writeText(text)
  21. .then(() => {
  22. setIsCopied(true);
  23. })
  24. .catch((error: unknown) => {
  25. /* TODO do something here? */
  26. logger.error(error);
  27. });
  28. }, [text, logger]);
  29. useEffect(() => {
  30. setIsCopied(false);
  31. }, [text]);
  32. useEffect(() => {
  33. if (isCopied) {
  34. const timeout = setTimeout(() => {
  35. setIsCopied(false);
  36. }, 2000);
  37. return () => {
  38. clearTimeout(timeout);
  39. };
  40. } else {
  41. return;
  42. }
  43. }, [isCopied]);
  44. return (
  45. <Button
  46. onPress={copy}
  47. isDisabled={isCopied}
  48. className={clsx(
  49. "group mx-[-0.25em] -mt-0.5 inline-block rounded-md px-[0.25em] py-0.5 transition hover:bg-white/10 focus:outline-none focus-visible:ring-1 focus-visible:ring-pythpurple-400",
  50. className,
  51. )}
  52. {...(isCopied && { "data-is-copied": true })}
  53. {...props}
  54. >
  55. {(...args) => (
  56. <>
  57. <span>
  58. {typeof children === "function" ? children(...args) : children}
  59. </span>
  60. <span className="relative top-[0.125em] ml-[0.25em] inline-block">
  61. <span className="opacity-50 transition-opacity duration-100 group-data-[is-copied]:opacity-0">
  62. <ClipboardDocumentIcon className="size-[1em]" />
  63. <div className="sr-only">Copy to clipboard</div>
  64. </span>
  65. <CheckIcon className="absolute inset-0 text-green-600 opacity-0 transition-opacity duration-100 group-data-[is-copied]:opacity-100" />
  66. </span>
  67. </>
  68. )}
  69. </Button>
  70. );
  71. };