From d1b645ec306307069e347674286dcb9574307512 Mon Sep 17 00:00:00 2001 From: Rajan <139620395+Rajankannaujiya@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:51:58 +0000 Subject: [PATCH 1/5] feat: implement copy button UI (#7698) --- .../CodeBlockWithCopy/CodeBlockWithCopy.jsx | 72 ++++++++++++++++++ .../CodeBlockWithCopy/CodeBlockWithCopy.scss | 74 +++++++++++++++++++ src/mdx-components.js | 2 + 3 files changed, 148 insertions(+) create mode 100644 src/components/CodeBlockWithCopy/CodeBlockWithCopy.jsx create mode 100644 src/components/CodeBlockWithCopy/CodeBlockWithCopy.scss diff --git a/src/components/CodeBlockWithCopy/CodeBlockWithCopy.jsx b/src/components/CodeBlockWithCopy/CodeBlockWithCopy.jsx new file mode 100644 index 000000000000..07dd90a377f4 --- /dev/null +++ b/src/components/CodeBlockWithCopy/CodeBlockWithCopy.jsx @@ -0,0 +1,72 @@ +import { useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import './CodeBlockWithCopy.scss'; + +export default function CodeBlockWithCopy({ children }) { + const preRef = useRef(null); + const [copyStatus, setCopyStatus] = useState('copy'); + + const handleCopy = async () => { + if (!preRef.current) return; + + const codeElement = preRef.current.querySelector('code'); + if (!codeElement) return; + + const codeText = codeElement.textContent; + let successfulCopy = false; + + // Try modern API (navigator.clipboard) -> as document.execCommand() deprecated + try { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(codeText); + successfulCopy = true; + } + } catch (err) { + console.log(err); + } + + // If modern API failed, fall back to deprecated document.execCommand('copy') + if (!successfulCopy) { + const textarea = document.createElement('textarea'); + textarea.value = codeText; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + + document.body.appendChild(textarea); + textarea.select(); + + try { + // This deprecated method is kept as a fallback for compatibility/iframe environments. + successfulCopy = document.execCommand('copy'); + } catch (err) { + successfulCopy = false; + console.log(err); + } + + document.body.removeChild(textarea); + } + + setCopyStatus(successfulCopy ? 'copied' : 'error'); + setTimeout(() => setCopyStatus('copy'), 2000); + }; + + return ( +
+ + +
+        {children}
+      
+
+ ); +} + +CodeBlockWithCopy.propTypes = { + children: PropTypes.node.isRequired, +}; diff --git a/src/components/CodeBlockWithCopy/CodeBlockWithCopy.scss b/src/components/CodeBlockWithCopy/CodeBlockWithCopy.scss new file mode 100644 index 000000000000..18e59c74236d --- /dev/null +++ b/src/components/CodeBlockWithCopy/CodeBlockWithCopy.scss @@ -0,0 +1,74 @@ +.code-block-wrapper { + position: relative; + margin-bottom: 1.5rem; +} + +.code-block { + background-color: #2d3748; + color: #e2e8f0; + padding: 1rem; + padding-right: 3.5rem; + border-radius: 0.5rem; + overflow-x: auto; + font-size: 0.875rem; + line-height: 1.5; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); + + code { + font-family: monospace; + } +} + +.copy-button { + position: absolute; + top: 0.32rem; + right: 0.5rem; + z-index: 10; + + padding: 0.4rem 0.7rem; + border-radius: 0.35rem; + + border: none; + cursor: pointer; + + font-size: 0.75rem; + font-weight: 500; + + /* Always visible */ + opacity: 1; + + background-color: #7c3aed; + color: #e2e8f0; + + transition: + background-color 0.2s, + transform 0.1s; + + &:hover { + background-color: #6d28d9; + } + + /* Success */ + &.copied { + background-color: #38a169; + } + &.copied:hover { + background-color: #2f855a; + } + + /* Error */ + &.error { + background-color: #e53e3e; + } + &.error:hover { + background-color: #c53030; + } + + &:focus { + outline: none; + } + + &:active { + transform: scale(0.95); + } +} diff --git a/src/mdx-components.js b/src/mdx-components.js index 0a27d381804e..c6a120cec2d3 100644 --- a/src/mdx-components.js +++ b/src/mdx-components.js @@ -1,6 +1,7 @@ import Badge from './components/Badge/Badge'; import LinkComponent from './components/mdxComponents/Link'; import StackBlitzPreview from './components/StackBlitzPreview/StackBlitzPreview'; +import CodeBlockWithCopy from './components/CodeBlockWithCopy/CodeBlockWithCopy'; /** @returns {import('mdx/types.js').MDXComponents} */ export function useMDXComponents() { @@ -8,5 +9,6 @@ export function useMDXComponents() { a: LinkComponent, Badge: Badge, StackBlitzPreview: StackBlitzPreview, + pre: CodeBlockWithCopy, }; } From 6b5fdddc587c964832a7f16bbea4986bb0b45fa1 Mon Sep 17 00:00:00 2001 From: Rajan <139620395+Rajankannaujiya@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:11:00 +0530 Subject: [PATCH 2/5] wrapped output usage example with CodeBlockWithCopy --- src/content/concepts/output.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/content/concepts/output.mdx b/src/content/concepts/output.mdx index 71f599ccd84c..2af2a3d84741 100644 --- a/src/content/concepts/output.mdx +++ b/src/content/concepts/output.mdx @@ -17,6 +17,8 @@ The minimum requirement for the `output` property in your webpack configuration **webpack.config.js** + + ```javascript module.exports = { output: { @@ -24,6 +26,8 @@ module.exports = { }, }; ``` + + This configuration would output a single `bundle.js` file into the `dist` directory. From 1da6b56bc9e079307a8864fd471593e28cf47a47 Mon Sep 17 00:00:00 2001 From: Rajan <139620395+Rajankannaujiya@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:25:40 +0530 Subject: [PATCH 3/5] import CodeBlockWithCopy component in output.mdx --- src/content/concepts/output.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/content/concepts/output.mdx b/src/content/concepts/output.mdx index 2af2a3d84741..4f0851665c34 100644 --- a/src/content/concepts/output.mdx +++ b/src/content/concepts/output.mdx @@ -9,6 +9,8 @@ contributors: - EugeneHlushko --- +import CodeBlockWithCopy from "../../../components/CodeBlockWithCopy" + Configuring the `output` configuration options tells webpack how to write the compiled files to disk. Note that, while there can be multiple `entry` points, only one `output` configuration is specified. ## Usage @@ -26,7 +28,7 @@ module.exports = { }, }; ``` - + This configuration would output a single `bundle.js` file into the `dist` directory. From dc4a4c3bebcb6e7dae111f3231076893950143e4 Mon Sep 17 00:00:00 2001 From: Rajan <139620395+Rajankannaujiya@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:28:16 +0530 Subject: [PATCH 4/5] fixed singlequote issue of string --- src/content/concepts/output.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/concepts/output.mdx b/src/content/concepts/output.mdx index 4f0851665c34..cb97ff3de850 100644 --- a/src/content/concepts/output.mdx +++ b/src/content/concepts/output.mdx @@ -9,7 +9,7 @@ contributors: - EugeneHlushko --- -import CodeBlockWithCopy from "../../../components/CodeBlockWithCopy" +import CodeBlockWithCopy from '../../../components/CodeBlockWithCopy' Configuring the `output` configuration options tells webpack how to write the compiled files to disk. Note that, while there can be multiple `entry` points, only one `output` configuration is specified. From f29e6e63cd829b0a76d22e9ce58accac959773fa Mon Sep 17 00:00:00 2001 From: Rajan <139620395+Rajankannaujiya@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:37:38 +0530 Subject: [PATCH 5/5] fixed file path of CodBlockWithCopy --- src/content/concepts/output.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/concepts/output.mdx b/src/content/concepts/output.mdx index cb97ff3de850..ef87d4af7ba4 100644 --- a/src/content/concepts/output.mdx +++ b/src/content/concepts/output.mdx @@ -9,7 +9,7 @@ contributors: - EugeneHlushko --- -import CodeBlockWithCopy from '../../../components/CodeBlockWithCopy' +import CodeBlockWithCopy from '../../components/CodeBlockWithCopy/CodeBlockWithCopy' Configuring the `output` configuration options tells webpack how to write the compiled files to disk. Note that, while there can be multiple `entry` points, only one `output` configuration is specified.