From 276ea40ae7b33a3803534b7f94b8b477eb3e7ffb Mon Sep 17 00:00:00 2001 From: Ganesh <7030871503ganeshpatil@gmail.com> Date: Sun, 23 Nov 2025 19:28:33 +0530 Subject: [PATCH] Fix: updated docs and added CodeBlock component --- src/components/CodeBlock/CodeBlock.jsx | 148 ++++++++++++++++++++++++ src/components/CodeBlock/CodeBlock.scss | 98 ++++++++++++++++ src/mdx-components.js | 2 + 3 files changed, 248 insertions(+) create mode 100644 src/components/CodeBlock/CodeBlock.jsx create mode 100644 src/components/CodeBlock/CodeBlock.scss diff --git a/src/components/CodeBlock/CodeBlock.jsx b/src/components/CodeBlock/CodeBlock.jsx new file mode 100644 index 000000000000..1de4cb5b7357 --- /dev/null +++ b/src/components/CodeBlock/CodeBlock.jsx @@ -0,0 +1,148 @@ +import React, { useState, useRef, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import './CodeBlock.scss'; + +const CodeBlock = ({ children, ...props }) => { + const [copied, setCopied] = useState(false); + const preRef = useRef(null); + const timeoutRef = useRef(null); + + // Extract the code content from the children + const getCodeContent = () => { + if (!preRef.current) return ''; + + const codeElement = preRef.current.querySelector('code'); + if (codeElement) { + return codeElement.textContent || codeElement.innerText || ''; + } + + // Fallback: get text from pre element + return preRef.current.textContent || preRef.current.innerText || ''; + }; + + const handleCopy = async () => { + const codeContent = getCodeContent(); + + if (!codeContent) return; + + try { + await navigator.clipboard.writeText(codeContent); + setCopied(true); + + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // Reset the copied state after 2 seconds + timeoutRef.current = setTimeout(() => { + setCopied(false); + }, 2000); + } catch (err) { + // Fallback for browsers that don't support clipboard API + console.error('Failed to copy code:', err); + + // Try the fallback method + const textArea = document.createElement('textarea'); + textArea.value = codeContent; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + document.body.appendChild(textArea); + textArea.select(); + + try { + document.execCommand('copy'); + setCopied(true); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + setCopied(false); + }, 2000); + } catch (fallbackErr) { + console.error('Fallback copy failed:', fallbackErr); + } + + document.body.removeChild(textArea); + } + }; + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + return ( +
+
+        {children}
+      
+ +
+ ); +}; + +CodeBlock.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default CodeBlock; + diff --git a/src/components/CodeBlock/CodeBlock.scss b/src/components/CodeBlock/CodeBlock.scss new file mode 100644 index 000000000000..70273ad15619 --- /dev/null +++ b/src/components/CodeBlock/CodeBlock.scss @@ -0,0 +1,98 @@ +@import 'vars'; +@import 'functions'; + +.code-block-wrapper { + position: relative; + margin: 1em 0; // Match the margin from .markdown pre + + // Ensure pre has relative positioning for absolute button + pre { + position: relative; + margin: 0; // Remove margin from pre since wrapper has it + } +} + +.code-block-copy-button { + position: absolute; + top: 8px; + right: 8px; + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background-color: transparentize(getColor(elephant), 0.2); + border: 1px solid transparentize(getColor(white), 0.9); + border-radius: 4px; + color: getColor(malibu); + font-size: 12px; + font-family: $font-stack-body; + cursor: pointer; + transition: all 200ms ease; + z-index: 10; + + // Subtle by default, more visible on hover + opacity: 0.7; + + .code-block-wrapper:hover & { + opacity: 1; + background-color: transparentize(getColor(elephant), 0.1); + border-color: transparentize(getColor(white), 0.8); + } + + &:hover { + background-color: transparentize(getColor(elephant), 0.05); + border-color: transparentize(getColor(white), 0.75); + color: lighten(getColor(malibu), 10%); + opacity: 1; + } + + &:active { + transform: scale(0.98); + } + + &:focus { + outline: 2px solid getColor(malibu); + outline-offset: 2px; + opacity: 1; + } +} + +.code-block-copy-icon { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.code-block-copy-text { + white-space: nowrap; + font-weight: 500; +} + +// Dark theme support +[data-theme='dark'] { + .code-block-copy-button { + background-color: transparentize(#131b1f, 0.3); + border-color: transparentize(getColor(white), 0.95); + color: #69a8ee; + opacity: 0.7; + + .code-block-wrapper:hover & { + opacity: 1; + background-color: transparentize(#131b1f, 0.15); + border-color: transparentize(getColor(white), 0.9); + } + + &:hover { + background-color: transparentize(#131b1f, 0.05); + border-color: transparentize(getColor(white), 0.8); + color: #82b7f6; + opacity: 1; + } + + &:focus { + outline-color: #69a8ee; + opacity: 1; + } + } +} + diff --git a/src/mdx-components.js b/src/mdx-components.js index 0a27d381804e..e6ef9e8e6b16 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 CodeBlock from './components/CodeBlock/CodeBlock'; /** @returns {import('mdx/types.js').MDXComponents} */ export function useMDXComponents() { @@ -8,5 +9,6 @@ export function useMDXComponents() { a: LinkComponent, Badge: Badge, StackBlitzPreview: StackBlitzPreview, + pre: CodeBlock, }; }