Skip to content

Commit

Permalink
feat: 代码块支持复制
Browse files Browse the repository at this point in the history
  • Loading branch information
huayemao committed Jun 21, 2024
1 parent 822e743 commit 9164bfe
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 3 deletions.
32 changes: 32 additions & 0 deletions components/Pre.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use client";
import type { ComponentProps, ReactElement } from "react";
import React, { useRef } from "react";
import { CopyToClipboard } from "./copy-to-clipboard";

export const Pre = ({
children,
className,
...props
}: ComponentProps<"pre">): ReactElement => {
const preRef = useRef<HTMLPreElement | null>(null);
const arr = React.Children.toArray(children);
const lidEl = arr[0] as ReactElement;
const containerEl = arr[1] as ReactElement;
return (
<pre {...props} ref={preRef} className="relative !pt-14">
<div className="absolute top-0 right-0 left-0 flex items-center justify-between w-full px-6 py-2 pr-4 bg-muted-700 text-muted-100">
<span className="text-xs lowercase">{lidEl.props.children}</span>
<div className="flex items-center space-x-1">
{
<CopyToClipboard
getValue={() =>
preRef.current?.querySelector("code")?.textContent || ""
}
/>
}
</div>
</div>
{containerEl}
</pre>
);
};
47 changes: 47 additions & 0 deletions components/copy-to-clipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { BaseButtonIcon } from '@shuriken-ui/react'
import { CheckIcon, CopyIcon } from 'lucide-react'
import type { ComponentProps, ReactElement } from 'react'
import { useCallback, useEffect, useState } from 'react'

export const CopyToClipboard = ({
getValue,
...props
}: {
getValue: () => string
} & ComponentProps<'button'>): ReactElement => {
const [isCopied, setCopied] = useState(false)

useEffect(() => {
if (!isCopied) return
const timerId = setTimeout(() => {
setCopied(false)
}, 2000)

return () => {
clearTimeout(timerId)
}
}, [isCopied])

const handleClick = useCallback<
NonNullable<ComponentProps<'button'>['onClick']>
>(async () => {
setCopied(true)
if (!navigator?.clipboard) {
console.error('Access to clipboard rejected!')
}
try {
await navigator.clipboard.writeText(getValue())
} catch {
console.error('Failed to copy!')
}
}, [getValue])

const IconToUse = isCopied ? CheckIcon : CopyIcon

return (
/* @ts-ignore */
<BaseButtonIcon size='sm' onClick={handleClick} title="Copy code" tabIndex={0} {...props}>
<IconToUse className="h-4 w-4" />
</BaseButtonIcon>
)
}
2 changes: 2 additions & 0 deletions lib/parseMDX.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Columns from "@/components/Columns";
import DataList from "@/components/DataList";
import { DigitsHighlightButton } from "@/components/DigitsHighlightButton";
import { PersonList } from "@/components/Person";
import { Pre } from "@/components/Pre";
import { QuestionList } from "@/components/Question";
import Raw from "@/components/Raw";
import Tag from "@/components/Tag";
Expand Down Expand Up @@ -46,6 +47,7 @@ const components = {
h3: (props) => <h3 id={encodeURIComponent(props.children)} {...props}></h3>,
h4: (props) => <h4 id={encodeURIComponent(props.children)} {...props}></h4>,
h5: (props) => <h5 id={encodeURIComponent(props.children)} {...props}></h5>,
pre: Pre,
img: async (props) => {
return (
<figure suppressHydrationWarning>
Expand Down
4 changes: 1 addition & 3 deletions styles/post.module.css
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
.content :global .language-id {
@apply bg-slate-100 shadow rounded w-fit px-3 text-primary-600 text-sm font-bold mb-2;
}

0 comments on commit 9164bfe

Please sign in to comment.