From e554fd962bfc8c0983f3327c259bc46cb4f84a5a Mon Sep 17 00:00:00 2001 From: Adeniji Adekunle James Date: Sat, 16 Aug 2025 00:52:37 +0100 Subject: [PATCH] feat: add copy to clipboard functionality for code blocks (#934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 🚀 Feature: Copy to Clipboard for Code Blocks ### What's Changed - Added a copy button to all code blocks that allows users to easily copy code snippets - Implemented visual feedback showing a checkmark when code is successfully copied - Copy button automatically reverts back after 2 seconds ### Technical Details - Uses `navigator.clipboard.writeText()` for modern clipboard API - Positioned copy button in the top-right corner alongside language label - Maintains existing code highlighting functionality ### UI/UX Improvements - Clean, minimal copy button design that doesn't interfere with code readability - Clear visual feedback with copy and check icon transition - Consistent styling with existing theme system ### Video https://github.com/user-attachments/assets/8f388217-da8a-422e-9087-42cce8df68ad --------- Co-authored-by: Will Chen --- src/components/chat/CodeHighlight.tsx | 39 +++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/components/chat/CodeHighlight.tsx b/src/components/chat/CodeHighlight.tsx index 88d5dcf..201183d 100644 --- a/src/components/chat/CodeHighlight.tsx +++ b/src/components/chat/CodeHighlight.tsx @@ -1,9 +1,16 @@ -import React, { useEffect, useRef, memo, type ReactNode } from "react"; +import React, { + useState, + useEffect, + useRef, + memo, + type ReactNode, +} from "react"; import { isInlineCode, useShikiHighlighter } from "react-shiki"; import github from "@shikijs/themes/github-light-default"; import githubDark from "@shikijs/themes/github-dark-default"; import type { Element as HastElement } from "hast"; import { useTheme } from "../../contexts/ThemeContext"; +import { Copy, Check } from "lucide-react"; interface CodeHighlightProps { className?: string | undefined; @@ -16,6 +23,13 @@ export const CodeHighlight = memo( const code = String(children).trim(); const language = className?.match(/language-(\w+)/)?.[1]; const isInline = node ? isInlineCode(node) : false; + //handle copying code to clipboard with transition effect + const [copied, setCopied] = useState(false); + const handleCopy = () => { + navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); // revert after 2s + }; const { isDarkMode } = useTheme(); @@ -44,15 +58,24 @@ export const CodeHighlight = memo( return !isInline ? (
{language ? ( - - {language} - +
+ + {language} + + {code && ( + + )} +
) : null} {displayedCode}