Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ export type SpeechState = {
volume: number;
};

export type ClipboardFormats = {
plain?: string;
html?: string;
markdown?: string;
};

declare module "@uidotdev/usehooks" {
export function useBattery(): BatteryManager;

Expand All @@ -125,9 +131,9 @@ declare module "@uidotdev/usehooks" {

export function useCopyToClipboard(): [
string | null,
(value: string) => Promise<void>
(value: string | ClipboardFormats) => void
];

export function useCounter(
startingValue?: number,
options?: {
Expand Down
23 changes: 19 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,39 @@ export function useCopyToClipboard() {
const copyToClipboard = React.useCallback((value) => {
const handleCopy = async () => {
try {
if (navigator?.clipboard?.writeText) {
// ClipboardItem API for multiple formats
if (typeof value === 'object' && value !== null && window.ClipboardItem) {
const items = {};
if (value.plain) {
items["text/plain"] = new Blob([value.plain], { type: "text/plain" });
}
if (value.html) {
items["text/html"] = new Blob([value.html], { type: "text/html" });
}
if (value.markdown) {
items["text/markdown"] = new Blob([value.markdown], { type: "text/markdown" });
}
const clipboardItem = new ClipboardItem(items);
await navigator.clipboard.write([clipboardItem]);
setState(value.plain || value.html || value.markdown);
} else if (navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(value);
setState(value);
} else {
throw new Error("writeText not supported");
}
} catch (e) {
oldSchoolCopy(value);
setState(value);
oldSchoolCopy(typeof value === 'object' ? value.plain : value);
setState(typeof value === 'object' ? value.plain : value);
}
};

handleCopy();
}, []);

return [state, copyToClipboard];
}


export function useCounter(startingValue = 0, options = {}) {
const { min, max } = options;

Expand Down
62 changes: 43 additions & 19 deletions usehooks.com/src/content/hooks/useCopyToClipboard.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: useCopyToClipboard
rank: 31
tagline: Copy text to the clipboard using useCopyToClipboard.
tagline: Copy text to the clipboard with support for multiple formats.
sandboxId: usecopytoclipboard-y22r6w
previewHeight: 300px
relatedHooks:
Expand All @@ -14,12 +14,11 @@ import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
The useCopyToClipboard hook is useful because it abstracts the complexity of
copying text to the clipboard in a cross-browser compatible manner. It
utilizes the modern navigator.clipboard.writeText method if available, which
provides a more efficient and secure way to copy text. In case the writeText
method is not supported by the browser, it falls back to a traditional method
using the document.execCommand("copy") approach.
The useCopyToClipboard hook provides a simple way to copy text to the clipboard
with support for multiple clipboard formats. It handles both plain text and rich
content formats, utilizing the modern Clipboard API with fallback support for
older browsers. The hook tracks the copied value and provides error handling
for failed copy operations.
</HookDescription>

<div class="reference">
Expand All @@ -28,10 +27,12 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
The `useCopyToClipboard` hook returns an array with the following elements:

<div class="table-container">

| Index | Type | Description |
| ----- | -------- | ------------------------------------------------------ |
| 0 | string | The value that was last copied to the clipboard. |
| 1 | function | A function to copy a specified value to the clipboard. |
| 0 | object | An object containing `value` (last copied text) and `error` (any error that occurred) |
| 1 | function | A function to copy a specified value to the clipboard. Accepts either a string or an object with `text` and optional `html` properties. |

</div>
</div>

Expand All @@ -41,7 +42,6 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useCopyToClipboard } from "@uidotdev/usehooks";
Expand All @@ -50,38 +50,62 @@ import { copyIcon, checkIcon } from "./icons";
const randomHash = crypto.randomUUID();

export default function App() {
const [copiedText, copyToClipboard] = useCopyToClipboard();
const hasCopiedText = Boolean(copiedText);
const [{ value, error }, copyToClipboard] = useCopyToClipboard();
const hasCopiedText = Boolean(value);

const handleCopyPlainText = () => {
copyToClipboard(randomHash);
};

const handleCopyRichText = () => {
copyToClipboard({
text: randomHash,
html: `<strong>API Key:</strong> <code>${randomHash}</code>`
});
};

return (
<section>
<h1>useCopyToClipboard</h1>
<article>
<label>Fake API Key</label>
<h3>Fake API Key</h3>
<pre>
<code>{randomHash}</code>
{randomHash}
<button
className="link"
onClick={handleCopyPlainText}
disabled={hasCopiedText}
>
{hasCopiedText ? checkIcon : copyIcon} Copy Plain
</button>
<button
className="link"
onClick={() => copyToClipboard(randomHash)}
onClick={handleCopyRichText}
disabled={hasCopiedText}
>
{hasCopiedText ? checkIcon : copyIcon}
{hasCopiedText ? checkIcon : copyIcon} Copy Rich
</button>
</pre>
</article>
{error && (
<dialog open>
<h4>Error copying to clipboard</h4>
<p>{error.message}</p>
</dialog>
)}
{hasCopiedText && (
<dialog open={hasCopiedText}>
<dialog open>
<h4>
Copied{" "}
<span role="img" aria-label="Celebrate Emoji">
🎉
</span>
</h4>
<textarea placeholder="Paste your copied text" />
<textarea placeholder="Paste your copied text"></textarea>
</dialog>
)}
</section>
);
}
```

</StaticCodeContainer>