diff --git a/packages/react-components/package.json b/packages/react-components/package.json
index 4e2b491f..62d053f1 100644
--- a/packages/react-components/package.json
+++ b/packages/react-components/package.json
@@ -52,7 +52,6 @@
"@popperjs/core": "^2.11.7",
"@types/flat": "^5.0.2",
"@types/react-transition-group": "^4.4.5",
- "copy-to-clipboard": "^3.3.1",
"deepmerge": "^4.3.1",
"flat": "^5.0.2",
"focus-trap": "^7.5.4",
diff --git a/packages/react-components/src/hooks/index.ts b/packages/react-components/src/hooks/index.ts
index f84ad124..5be5ca6a 100644
--- a/packages/react-components/src/hooks/index.ts
+++ b/packages/react-components/src/hooks/index.ts
@@ -1,7 +1,6 @@
export { useMediaQuery } from './use_media_query';
export { useMergedRef } from './use_merged_ref';
export { useClipboard } from './use_clipboard';
-export type { UseClipboardOptions } from './use_clipboard';
export { useControllableState } from './use_controllable';
export type { UseControllableStateProps } from './use_controllable';
export { useId } from './use_id';
diff --git a/packages/react-components/src/hooks/use_clipboard.mdx b/packages/react-components/src/hooks/use_clipboard.mdx
index a1280efe..e86acaa9 100644
--- a/packages/react-components/src/hooks/use_clipboard.mdx
+++ b/packages/react-components/src/hooks/use_clipboard.mdx
@@ -13,17 +13,14 @@ React hook that provides copy to clipboard functionality.
import { useClipboard } from '@peculiar/react-components';
export const Demo = () => {
- const {
- onCopy,
- hasCopied,
- } = useClipboard('Text to copy');
+ const { copy, isCopied } = useClipboard();
return (
);
};
diff --git a/packages/react-components/src/hooks/use_clipboard.stories.tsx b/packages/react-components/src/hooks/use_clipboard.stories.tsx
index 547029d1..8e725827 100644
--- a/packages/react-components/src/hooks/use_clipboard.stories.tsx
+++ b/packages/react-components/src/hooks/use_clipboard.stories.tsx
@@ -9,17 +9,14 @@ const meta = {
export default meta;
export const DemoExample = () => {
- const {
- onCopy,
- hasCopied,
- } = useClipboard('Text to copy');
+ const { copy, isCopied } = useClipboard();
return (
);
};
diff --git a/packages/react-components/src/hooks/use_clipboard.ts b/packages/react-components/src/hooks/use_clipboard.ts
index 51d18132..20c85cb6 100644
--- a/packages/react-components/src/hooks/use_clipboard.ts
+++ b/packages/react-components/src/hooks/use_clipboard.ts
@@ -1,59 +1,38 @@
import React from 'react';
-import copy from 'copy-to-clipboard';
-
-export type UseClipboardOptions = {
- /**
- * timeout delay (in ms) to switch back to initial state once copied.
- */
- timeout?: number;
- /**
- * Set the desired MIME type
- */
- format?: string;
-};
+import { copyToClipboard } from '../utils';
/**
- * React hook to copy content to clipboard
- *
- * @param text the text or value to copy
- * @param {Number} [optionsOrTimeout=1500] optionsOrTimeout -
- * delay (in ms) to switch back to initial state once copied.
- * @param {Object} optionsOrTimeout
- * @param {string} optionsOrTimeout.format - set the desired MIME type
- * @param {number} optionsOrTimeout.timeout -
- * delay (in ms) to switch back to initial state once copied.
+ * React hook to copy content to clipboard.
*/
-export function useClipboard(
- text: string,
- optionsOrTimeout: number | UseClipboardOptions = {},
-) {
- const [hasCopied, setHasCopied] = React.useState(false);
-
- const { timeout = 1500, ...copyOptions } = typeof optionsOrTimeout === 'number'
- ? { timeout: optionsOrTimeout }
- : optionsOrTimeout;
-
- const onCopy = React.useCallback(() => {
- const didCopy = copy(text, copyOptions);
-
- setHasCopied(didCopy);
- }, [text, copyOptions]);
+export function useClipboard() {
+ const [isCopied, setIsCopied] = React.useState(false);
+ const timeout = React.useRef>();
+ const mounted = React.useRef(false);
React.useEffect(() => {
- let timeoutId: number | null = null;
-
- if (hasCopied) {
- timeoutId = window.setTimeout(() => {
- setHasCopied(false);
- }, timeout);
- }
+ mounted.current = true;
return () => {
- if (timeoutId) {
- window.clearTimeout(timeoutId);
- }
+ mounted.current = false;
};
- }, [timeout, hasCopied]);
+ }, []);
+
+ const copy = async (text: string) => {
+ try {
+ setIsCopied(true);
+ clearTimeout(timeout.current);
+
+ timeout.current = setTimeout(() => {
+ if (mounted) {
+ setIsCopied(false);
+ }
+ }, 1500);
+
+ copyToClipboard(text);
+ } catch (error) {
+ // ignore error
+ }
+ };
- return { value: text, onCopy, hasCopied };
+ return { copy, isCopied };
}
diff --git a/packages/react-components/src/utils/copy_to_clipboard.ts b/packages/react-components/src/utils/copy_to_clipboard.ts
new file mode 100644
index 00000000..84663230
--- /dev/null
+++ b/packages/react-components/src/utils/copy_to_clipboard.ts
@@ -0,0 +1,52 @@
+type SuccessCallback = () => void;
+
+const fallbackCopyToClipboard = (text: string, onSuccess?: SuccessCallback) => {
+ const textareaElement = document.createElement('textarea');
+
+ textareaElement.value = text;
+ document.body.appendChild(textareaElement);
+
+ if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
+ const range = document.createRange();
+
+ textareaElement.setAttribute('contentEditable', 'true');
+ textareaElement.setAttribute('readOnly', 'true');
+
+ range.selectNodeContents(textareaElement);
+
+ const selection = window.getSelection();
+
+ if (selection) {
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+
+ textareaElement.setSelectionRange(0, text.length);
+ } else {
+ textareaElement.select();
+ }
+
+ try {
+ document.execCommand('copy');
+
+ if (onSuccess) {
+ onSuccess();
+ }
+ } catch (error) {
+ console.error('Fallback copy error:', error);
+ }
+
+ document.body.removeChild(textareaElement);
+};
+
+export const copyToClipboard = (text: string, onSuccess?: SuccessCallback) => {
+ if (!navigator.clipboard) {
+ fallbackCopyToClipboard(text, onSuccess);
+
+ return;
+ }
+
+ navigator.clipboard.writeText(text)
+ .then(onSuccess)
+ .catch(() => fallbackCopyToClipboard(text, onSuccess));
+};
diff --git a/packages/react-components/src/utils/index.ts b/packages/react-components/src/utils/index.ts
index e69de29b..58e3bb61 100644
--- a/packages/react-components/src/utils/index.ts
+++ b/packages/react-components/src/utils/index.ts
@@ -0,0 +1 @@
+export { copyToClipboard } from './copy_to_clipboard';
diff --git a/yarn.lock b/yarn.lock
index 15d88a7e..c3000c29 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4878,13 +4878,6 @@ cookie@0.5.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
-copy-to-clipboard@^3.3.1:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
- integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
- dependencies:
- toggle-selection "^1.0.6"
-
core-js-compat@^3.31.0, core-js-compat@^3.33.1:
version "3.35.0"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.35.0.tgz#c149a3d1ab51e743bc1da61e39cb51f461a41873"
@@ -10562,11 +10555,6 @@ tocbot@^4.20.1:
resolved "https://registry.yarnpkg.com/tocbot/-/tocbot-4.25.0.tgz#bc38aea5ec8f076779bb39636f431b044129a237"
integrity sha512-kE5wyCQJ40hqUaRVkyQ4z5+4juzYsv/eK+aqD97N62YH0TxFhzJvo22RUQQZdO3YnXAk42ZOfOpjVdy+Z0YokA==
-toggle-selection@^1.0.6:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
- integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
-
toidentifier@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"