diff --git a/src/theme/CodeBlock/CopyButton/index.tsx b/src/theme/CodeBlock/CopyButton/index.tsx new file mode 100644 index 000000000..5661ffdda --- /dev/null +++ b/src/theme/CodeBlock/CopyButton/index.tsx @@ -0,0 +1,62 @@ +import React, { useCallback, useState, useRef, useEffect } from 'react'; +import clsx from 'clsx'; +import { translate } from '@docusaurus/Translate'; +import type { Props } from '@theme/CodeBlock/CopyButton'; +import IconCopy from '@theme/Icon/Copy'; +import IconSuccess from '@theme/Icon/Success'; +import { copyToClipboard } from './utils'; + +import styles from './styles.module.css'; + +export default function CopyButton({ code, className }: Props): JSX.Element { + const [isCopied, setIsCopied] = useState(false); + const copyTimeout = useRef(undefined); + + // handleCopyCode is the only REAL addition to this swizzled component. + // We simply call it whenever the button is clicked. + const handleCopyCode = useCallback(async () => { + try { + await copyToClipboard(code); + setIsCopied(true); + copyTimeout.current = window.setTimeout(() => { + setIsCopied(false); + }, 1000); + } catch (err) { + console.error('Failed to copy text:', err); + setIsCopied(false); + } + }, [code]); + + useEffect(() => () => window.clearTimeout(copyTimeout.current), []); + + return ( + + ); +} diff --git a/src/theme/CodeBlock/CopyButton/styles.module.css b/src/theme/CodeBlock/CopyButton/styles.module.css new file mode 100644 index 000000000..d5268e900 --- /dev/null +++ b/src/theme/CodeBlock/CopyButton/styles.module.css @@ -0,0 +1,40 @@ +:global(.theme-code-block:hover) .copyButtonCopied { + opacity: 1 !important; +} + +.copyButtonIcons { + position: relative; + width: 1.125rem; + height: 1.125rem; +} + +.copyButtonIcon, +.copyButtonSuccessIcon { + position: absolute; + top: 0; + left: 0; + fill: currentColor; + opacity: inherit; + width: inherit; + height: inherit; + transition: all var(--ifm-transition-fast) ease; +} + +.copyButtonSuccessIcon { + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.33); + opacity: 0; + color: #00d600; +} + +.copyButtonCopied .copyButtonIcon { + transform: scale(0.33); + opacity: 0; +} + +.copyButtonCopied .copyButtonSuccessIcon { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + transition-delay: 0.075s; +} diff --git a/src/theme/CodeBlock/CopyButton/utils.ts b/src/theme/CodeBlock/CopyButton/utils.ts new file mode 100644 index 000000000..3060dcfe9 --- /dev/null +++ b/src/theme/CodeBlock/CopyButton/utils.ts @@ -0,0 +1,10 @@ +// copyToClipboard utilizes the standard navigator.clipboard API to copy a text value from an HTML page. +export function copyToClipboard(text: string): Promise { + // First, we'll check to make sure the clipboard API is available. If it is, copy that text. + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } else { + // Just in case navigator.clipboard isn't supported...which is very unlikely. + console.log('This is a very old version of a browser.'); + } +}