Unverified 提交 e554fd96 authored 作者: Adeniji Adekunle James's avatar Adeniji Adekunle James 提交者: GitHub

feat: add copy to clipboard functionality for code blocks (#934)

## 🚀 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: 's avatarWill Chen <willchen90@gmail.com>
上级 b06f658f
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 { isInlineCode, useShikiHighlighter } from "react-shiki";
import github from "@shikijs/themes/github-light-default"; import github from "@shikijs/themes/github-light-default";
import githubDark from "@shikijs/themes/github-dark-default"; import githubDark from "@shikijs/themes/github-dark-default";
import type { Element as HastElement } from "hast"; import type { Element as HastElement } from "hast";
import { useTheme } from "../../contexts/ThemeContext"; import { useTheme } from "../../contexts/ThemeContext";
import { Copy, Check } from "lucide-react";
interface CodeHighlightProps { interface CodeHighlightProps {
className?: string | undefined; className?: string | undefined;
...@@ -16,6 +23,13 @@ export const CodeHighlight = memo( ...@@ -16,6 +23,13 @@ export const CodeHighlight = memo(
const code = String(children).trim(); const code = String(children).trim();
const language = className?.match(/language-(\w+)/)?.[1]; const language = className?.match(/language-(\w+)/)?.[1];
const isInline = node ? isInlineCode(node) : false; 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(); const { isDarkMode } = useTheme();
...@@ -44,15 +58,24 @@ export const CodeHighlight = memo( ...@@ -44,15 +58,24 @@ export const CodeHighlight = memo(
return !isInline ? ( return !isInline ? (
<div <div
className="shiki not-prose relative [&_pre]:overflow-auto className="shiki not-prose relative [&_pre]:overflow-auto
[&_pre]:rounded-lg [&_pre]:px-6 [&_pre]:py-5" [&_pre]:rounded-lg [&_pre]:px-6 [&_pre]:py-7"
> >
{language ? ( {language ? (
<span <div className="absolute top-2 left-2 right-2 text-xs flex justify-between">
className="absolute right-3 top-2 text-xs tracking-tighter <span className="tracking-tighter text-muted-foreground/85">
text-muted-foreground/85" {language}
> </span>
{language} {code && (
</span> <button
className="mr-2 flex items-center text-xs cursor-pointer"
onClick={handleCopy}
type="button"
>
{copied ? <Check size={14} /> : <Copy size={14} />}
<span className="ml-1">{copied ? "Copied" : "Copy"}</span>
</button>
)}
</div>
) : null} ) : null}
{displayedCode} {displayedCode}
</div> </div>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论