diff --git a/src/components/MDX/Sandpack/CopyButton.tsx b/src/components/MDX/Sandpack/CopyButton.tsx new file mode 100644 index 00000000000..2c8d028bff2 --- /dev/null +++ b/src/components/MDX/Sandpack/CopyButton.tsx @@ -0,0 +1,74 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useEffect, useState} from 'react'; +import {useSandpack} from '@codesandbox/sandpack-react/unstyled'; +import {IconCopy} from '../../Icon/IconCopy'; + +type CopyStatus = 'idle' | 'copied' | 'error'; + +export function CopyButton() { + const {sandpack} = useSandpack(); + const [copyStatus, setCopyStatus] = useState('idle'); + + useEffect(() => { + if (copyStatus === 'idle') { + return; + } + + const timeout = setTimeout(() => setCopyStatus('idle'), 2000); + return () => clearTimeout(timeout); + }, [copyStatus]); + + async function handleCopy() { + const code = sandpack.files[sandpack.activeFile]?.code; + if (typeof code !== 'string') { + console.error('Unable to copy the active sandbox file: no code found.'); + setCopyStatus('error'); + return; + } + + try { + await navigator.clipboard.writeText(code); + setCopyStatus('copied'); + } catch (error) { + console.error('Failed to copy the active sandbox file.', error); + setCopyStatus('error'); + } + } + + const statusText = + copyStatus === 'copied' + ? 'Copied!' + : copyStatus === 'error' + ? 'Copy failed' + : 'Copy'; + const accessibleLabel = + copyStatus === 'copied' + ? 'Code copied' + : copyStatus === 'error' + ? 'Copy failed' + : 'Copy active file'; + + return ( + + ); +} diff --git a/src/components/MDX/Sandpack/NavigationBar.tsx b/src/components/MDX/Sandpack/NavigationBar.tsx index 042f4b93a0e..52052513961 100644 --- a/src/components/MDX/Sandpack/NavigationBar.tsx +++ b/src/components/MDX/Sandpack/NavigationBar.tsx @@ -27,6 +27,7 @@ import {OpenInCodeSandboxButton} from './OpenInCodeSandboxButton'; import {ReloadButton} from './ReloadButton'; import {ClearButton} from './ClearButton'; import {DownloadButton} from './DownloadButton'; +import {CopyButton} from './CopyButton'; import {IconChevron} from '../../Icon/IconChevron'; import {Listbox} from '@headlessui/react'; import {OpenInTypeScriptPlaygroundButton} from './OpenInTypeScriptPlayground'; @@ -201,6 +202,7 @@ export function NavigationBar({
+