Skip to content

Commit

Permalink
BREAKING CHANGE(react-components): update "useClipboard" hook interfa…
Browse files Browse the repository at this point in the history
…ce and use own realization (#144)
  • Loading branch information
donskov authored Mar 5, 2024
1 parent 17c25a6 commit 8b58cff
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 74 deletions.
1 change: 0 additions & 1 deletion packages/react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 0 additions & 1 deletion packages/react-components/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
9 changes: 3 additions & 6 deletions packages/react-components/src/hooks/use_clipboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<button
type="button"
onClick={onCopy}
onClick={() => copy('Text to copy')}
>
{hasCopied ? 'Copied' : 'Click to copy'}
{isCopied ? 'Copied' : 'Click to copy'}
</button>
);
};
Expand Down
9 changes: 3 additions & 6 deletions packages/react-components/src/hooks/use_clipboard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@ const meta = {
export default meta;

export const DemoExample = () => {
const {
onCopy,
hasCopied,
} = useClipboard('Text to copy');
const { copy, isCopied } = useClipboard();

return (
<button
type="button"
onClick={onCopy}
onClick={() => copy('Text to copy')}
>
{hasCopied ? 'Copied' : 'Click to copy'}
{isCopied ? 'Copied' : 'Click to copy'}
</button>
);
};
75 changes: 27 additions & 48 deletions packages/react-components/src/hooks/use_clipboard.ts
Original file line number Diff line number Diff line change
@@ -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<ReturnType<typeof setTimeout>>();
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 };
}
52 changes: 52 additions & 0 deletions packages/react-components/src/utils/copy_to_clipboard.ts
Original file line number Diff line number Diff line change
@@ -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));
};
1 change: 1 addition & 0 deletions packages/react-components/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { copyToClipboard } from './copy_to_clipboard';
12 changes: 0 additions & 12 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4878,13 +4878,6 @@ [email protected]:
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"
Expand Down Expand Up @@ -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=

[email protected]:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
Expand Down

0 comments on commit 8b58cff

Please sign in to comment.