Skip to content

Commit

Permalink
chore: add new hook useCopyToClipboard (nodejs#3109)
Browse files Browse the repository at this point in the history
* Chore: add new hook `useCopyToClipboard`

* Chore: replace util with hook

* Chore: remove util

* CHore: update delay

* Chore: update format

* Chore: update test

* Chore: fix nits

* Chore: fix nits
  • Loading branch information
shanpriyan authored Jan 4, 2023
1 parent 568b952 commit 2e4bc25
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 100 deletions.
20 changes: 3 additions & 17 deletions src/components/ArticleComponents/Codebox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { highlight, languages } from 'prismjs';
import { sanitize } from 'isomorphic-dompurify';
import classnames from 'classnames';
import { copyTextToClipboard } from '../../../util/copyTextToClipboard';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
import styles from './index.module.scss';

interface Props {
Expand All @@ -26,8 +26,8 @@ const replaceLabelLanguages = (language: string) =>
.toUpperCase();

const Codebox = ({ children: { props } }: Props): JSX.Element => {
const [copied, setCopied] = useState(false);
const [parsedCode, setParsedCode] = useState('');
const [copied, copyText] = useCopyToClipboard();

// eslint-disable-next-line react/prop-types
const className = props.className || 'text';
Expand All @@ -43,23 +43,9 @@ const Codebox = ({ children: { props } }: Props): JSX.Element => {

const handleCopyCode = async (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
setCopied(await copyTextToClipboard(stringCode));
copyText(stringCode);
};

useEffect((): (() => void) => {
let timer: ReturnType<typeof setTimeout>;

if (copied) {
timer = setTimeout(() => setCopied(false), 3000);
}

return () => {
if (timer) {
clearTimeout(timer);
}
};
}, [copied]);

useEffect(() => {
const parsedLangauge = replaceLanguages(language);

Expand Down
22 changes: 4 additions & 18 deletions src/components/CommonComponents/ShellBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { copyTextToClipboard } from '../../../util/copyTextToClipboard';
import React from 'react';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
import styles from './index.module.scss';

interface Props {
Expand All @@ -10,27 +10,13 @@ const ShellBox = ({
children,
textToCopy,
}: React.PropsWithChildren<Props>): JSX.Element => {
const [copied, setCopied] = useState(false);
const [copied, copyText] = useCopyToClipboard();

const handleCopyCode = async (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
setCopied(await copyTextToClipboard(textToCopy));
copyText(textToCopy);
};

useEffect((): (() => void) => {
let timer: ReturnType<typeof setTimeout>;

if (copied) {
timer = setTimeout(() => setCopied(false), 3000);
}

return () => {
if (timer) {
clearTimeout(timer);
}
};
}, [copied]);

return (
<pre className={styles.shellBox}>
<div className={styles.top}>
Expand Down
77 changes: 77 additions & 0 deletions src/hooks/__tests__/useCopyToClipboard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import { render, fireEvent, screen, waitFor } from '@testing-library/react';
import { useCopyToClipboard } from '../useCopyToClipboard';

describe('useCopyToClipboard', () => {
const HookRenderer = ({ text }: { text: string }): JSX.Element => {
const [copied, copyText] = useCopyToClipboard();

return (
<button onClick={() => copyText(text)} type="button">
{copied ? 'copied' : 'copy'}
</button>
);
};

it('should have `copy` text when failed', async () => {
const navigatorClipboardWriteTextSpy = jest
.fn()
.mockImplementation(() => Promise.reject());

Object.defineProperty(window.navigator, 'clipboard', {
writable: true,
value: {
writeText: navigatorClipboardWriteTextSpy,
},
});

render(<HookRenderer text="test copy" />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(button).toHaveTextContent('copy');
});

it('should change to `copied` when copy succeeded', async () => {
jest.useFakeTimers();
const navigatorClipboardWriteTextSpy = jest
.fn()
.mockImplementation(() => Promise.resolve());

Object.defineProperty(window.navigator, 'clipboard', {
writable: true,
value: {
writeText: navigatorClipboardWriteTextSpy,
},
});

render(<HookRenderer text="test copy" />);
const button = screen.getByRole('button');
fireEvent.click(button);
await waitFor(() => {
expect(button).toHaveTextContent('copied');
});
jest.advanceTimersByTime(3000);
await waitFor(() => {
expect(button).toHaveTextContent('copy');
});
});

it('should call clipboard API with `test` once', () => {
const navigatorClipboardWriteTextSpy = jest
.fn()
.mockImplementation(() => Promise.resolve());

Object.defineProperty(window.navigator, 'clipboard', {
writable: true,
value: {
writeText: navigatorClipboardWriteTextSpy,
},
});

render(<HookRenderer text="test" />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(navigatorClipboardWriteTextSpy).toHaveBeenCalledTimes(1);
expect(navigatorClipboardWriteTextSpy).toHaveBeenCalledWith('test');
});
});
30 changes: 30 additions & 0 deletions src/hooks/useCopyToClipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect, useState } from 'react';

const copyToClipboard = (value: string) => {
if (typeof window === 'undefined') {
return Promise.resolve(false);
}

return navigator.clipboard
.writeText(value)
.then(() => true)
.catch(() => false);
};

export const useCopyToClipboard = (): [boolean, (text: string) => void] => {
const [copied, setCopied] = useState(false);

const copyText = (text: string) => copyToClipboard(text).then(setCopied);

useEffect(() => {
if (!copied) {
return undefined;
}

const timerId = setTimeout(() => setCopied(false), 3000);

return () => clearTimeout(timerId);
}, [copied]);

return [copied, copyText];
};
55 changes: 0 additions & 55 deletions src/util/__tests__/copyTextToClipboard.test.ts

This file was deleted.

10 changes: 0 additions & 10 deletions src/util/copyTextToClipboard.ts

This file was deleted.

0 comments on commit 2e4bc25

Please sign in to comment.