Skip to content

Commit aa739ee

Browse files
JubayeromiCopilot
andcommitted
Add copy action to sandbox toolbar
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0821e7d commit aa739ee

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
/*
9+
* Copyright (c) Facebook, Inc. and its affiliates.
10+
*/
11+
12+
import {useEffect, useState} from 'react';
13+
import {useSandpack} from '@codesandbox/sandpack-react/unstyled';
14+
import {IconCopy} from '../../Icon/IconCopy';
15+
16+
type CopyStatus = 'idle' | 'copied' | 'error';
17+
18+
export function CopyButton() {
19+
const {sandpack} = useSandpack();
20+
const [copyStatus, setCopyStatus] = useState<CopyStatus>('idle');
21+
22+
useEffect(() => {
23+
if (copyStatus === 'idle') {
24+
return;
25+
}
26+
27+
const timeout = setTimeout(() => setCopyStatus('idle'), 2000);
28+
return () => clearTimeout(timeout);
29+
}, [copyStatus]);
30+
31+
async function handleCopy() {
32+
const code = sandpack.files[sandpack.activeFile]?.code;
33+
if (typeof code !== 'string') {
34+
console.error('Unable to copy the active sandbox file: no code found.');
35+
setCopyStatus('error');
36+
return;
37+
}
38+
39+
try {
40+
await navigator.clipboard.writeText(code);
41+
setCopyStatus('copied');
42+
} catch (error) {
43+
console.error('Failed to copy the active sandbox file.', error);
44+
setCopyStatus('error');
45+
}
46+
}
47+
48+
const statusText =
49+
copyStatus === 'copied'
50+
? 'Copied!'
51+
: copyStatus === 'error'
52+
? 'Copy failed'
53+
: 'Copy';
54+
const accessibleLabel =
55+
copyStatus === 'copied'
56+
? 'Code copied'
57+
: copyStatus === 'error'
58+
? 'Copy failed'
59+
: 'Copy active file';
60+
61+
return (
62+
<button
63+
aria-label={accessibleLabel}
64+
className="text-sm text-primary dark:text-primary-dark inline-flex items-center hover:text-link duration-100 ease-in transition mx-1"
65+
onClick={handleCopy}
66+
title={accessibleLabel}
67+
type="button">
68+
<IconCopy className="inline mx-1 relative" />
69+
<span className="hidden md:block" aria-live="polite">
70+
{statusText}
71+
</span>
72+
</button>
73+
);
74+
}

‎src/components/MDX/Sandpack/NavigationBar.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {OpenInCodeSandboxButton} from './OpenInCodeSandboxButton';
2727
import {ReloadButton} from './ReloadButton';
2828
import {ClearButton} from './ClearButton';
2929
import {DownloadButton} from './DownloadButton';
30+
import {CopyButton} from './CopyButton';
3031
import {IconChevron} from '../../Icon/IconChevron';
3132
import {Listbox} from '@headlessui/react';
3233
import {OpenInTypeScriptPlaygroundButton} from './OpenInTypeScriptPlayground';
@@ -201,6 +202,7 @@ export function NavigationBar({
201202
<div
202203
className="px-3 flex items-center justify-end text-start"
203204
translate="yes">
205+
<CopyButton />
204206
<DownloadButton providedFiles={providedFiles} />
205207
<ReloadButton onReload={handleReload} />
206208
<ClearButton onClear={handleClear} />

0 commit comments

Comments
 (0)