diff --git a/app/javascript/flavours/glitch/components/account_header/buttons.tsx b/app/javascript/flavours/glitch/components/account_header/buttons.tsx
index c90048fa66..77003e71e3 100644
--- a/app/javascript/flavours/glitch/components/account_header/buttons.tsx
+++ b/app/javascript/flavours/glitch/components/account_header/buttons.tsx
@@ -11,7 +11,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react';
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
-import { CopyIconButton } from '../copy_icon_button';
+import { CopyIconButton } from '../copy_button';
import { FollowButton } from '../follow_button';
import { IconButton } from '../icon_button';
diff --git a/app/javascript/flavours/glitch/components/account_header/name.tsx b/app/javascript/flavours/glitch/components/account_header/name.tsx
index 53dbc905ec..01453c2b92 100644
--- a/app/javascript/flavours/glitch/components/account_header/name.tsx
+++ b/app/javascript/flavours/glitch/components/account_header/name.tsx
@@ -7,17 +7,16 @@ import classNames from 'classnames';
import Overlay from 'react-overlays/esm/Overlay';
-import { showAlert } from '@/flavours/glitch/actions/alerts';
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
import { useRelationship } from '@/flavours/glitch/hooks/useRelationship';
-import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
+import { useAppSelector } from '@/flavours/glitch/store';
import AtIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
import DomainIcon from '@/material-icons/400-24px/language.svg?react';
import { FollowsYouBadge } from '../badge';
-import { Button } from '../button';
+import { CopyButton } from '../copy_button';
import { DisplayName } from '../display_name';
import { Icon } from '../icon';
@@ -90,17 +89,6 @@ const AccountNameHelp: FC<{
const handle = `@${username}@${domain}`;
- const dispatch = useAppDispatch();
- const [copied, setCopied] = useState(false);
- const handleCopy = useCallback(() => {
- void navigator.clipboard.writeText(handle);
- setCopied(true);
- dispatch(showAlert({ message: messages.copied }));
- setTimeout(() => {
- setCopied(false);
- }, 700);
- }, [handle, dispatch]);
-
return (
<>
-
+
)}
diff --git a/app/javascript/flavours/glitch/components/copy_button.tsx b/app/javascript/flavours/glitch/components/copy_button.tsx
new file mode 100644
index 0000000000..cb1ad29b80
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/copy_button.tsx
@@ -0,0 +1,75 @@
+import { useState, useCallback } from 'react';
+
+import { defineMessages } from 'react-intl';
+
+import classNames from 'classnames';
+
+import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
+import { showAlert } from 'flavours/glitch/actions/alerts';
+import { IconButton } from 'flavours/glitch/components/icon_button';
+import { useAppDispatch } from 'flavours/glitch/store';
+
+import { Button } from './button';
+
+const messages = defineMessages({
+ copied: {
+ id: 'copy_icon_button.copied',
+ defaultMessage: 'Copied to clipboard',
+ },
+});
+
+export function useCopyToClipboard({ text }: { text: string }) {
+ const [wasCopied, setWasCopied] = useState(false);
+ const dispatch = useAppDispatch();
+
+ const copyText = useCallback(() => {
+ void navigator.clipboard.writeText(text);
+ setWasCopied(true);
+ dispatch(showAlert({ message: messages.copied }));
+ setTimeout(() => {
+ setWasCopied(false);
+ }, 700);
+ }, [setWasCopied, text, dispatch]);
+
+ return { copyText, wasCopied };
+}
+
+export const CopyButton: React.FC<
+ Omit<
+ React.ComponentPropsWithoutRef,
+ 'onClick' | 'text' | 'children'
+ > & {
+ value: string;
+ children: React.ReactNode | ((wasCopied: boolean) => React.ReactNode);
+ }
+> = ({ value, children, ...otherProps }) => {
+ const { copyText, wasCopied } = useCopyToClipboard({ text: value });
+
+ const label = typeof children === 'function' ? children(wasCopied) : children;
+
+ return (
+
+ );
+};
+
+export const CopyIconButton: React.FC<{
+ title: string;
+ value: string;
+ className?: string;
+ 'aria-describedby'?: string;
+}> = ({ title, value, className, 'aria-describedby': ariaDescribedBy }) => {
+ const { copyText, wasCopied } = useCopyToClipboard({ text: value });
+
+ return (
+
+ );
+};
diff --git a/app/javascript/flavours/glitch/components/copy_icon_button.tsx b/app/javascript/flavours/glitch/components/copy_icon_button.tsx
deleted file mode 100644
index 6f1dffbdf6..0000000000
--- a/app/javascript/flavours/glitch/components/copy_icon_button.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useState, useCallback } from 'react';
-
-import { defineMessages } from 'react-intl';
-
-import classNames from 'classnames';
-
-import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
-import { showAlert } from 'flavours/glitch/actions/alerts';
-import { IconButton } from 'flavours/glitch/components/icon_button';
-import { useAppDispatch } from 'flavours/glitch/store';
-
-const messages = defineMessages({
- copied: {
- id: 'copy_icon_button.copied',
- defaultMessage: 'Copied to clipboard',
- },
-});
-
-export const CopyIconButton: React.FC<{
- title: string;
- value: string;
- className?: string;
- 'aria-describedby'?: string;
-}> = ({ title, value, className, 'aria-describedby': ariaDescribedBy }) => {
- const [copied, setCopied] = useState(false);
- const dispatch = useAppDispatch();
-
- const handleClick = useCallback(() => {
- void navigator.clipboard.writeText(value);
- setCopied(true);
- dispatch(showAlert({ message: messages.copied }));
- setTimeout(() => {
- setCopied(false);
- }, 700);
- }, [setCopied, value, dispatch]);
-
- return (
-
- );
-};
diff --git a/app/javascript/flavours/glitch/components/form_fields/copy_link_field.tsx b/app/javascript/flavours/glitch/components/form_fields/copy_link_field.tsx
index 6404e5ed56..aa2b63392d 100644
--- a/app/javascript/flavours/glitch/components/form_fields/copy_link_field.tsx
+++ b/app/javascript/flavours/glitch/components/form_fields/copy_link_field.tsx
@@ -4,7 +4,7 @@ import { useIntl } from 'react-intl';
import classNames from 'classnames';
-import { CopyIconButton } from 'flavours/glitch/components/copy_icon_button';
+import { CopyIconButton } from '@/flavours/glitch/components/copy_button';
import classes from './copy_link_field.module.scss';
import { FormFieldWrapper } from './form_field_wrapper';