Feat/code blocks with copy

This commit is contained in:
Krish Parekh
2025-12-07 11:08:54 +05:30
committed by GitHub
parent b4640ac5e4
commit 641d5f36d2
3 changed files with 68 additions and 7 deletions
@@ -1,5 +1,6 @@
import { Button } from "@usesend/ui/src/button"; import { Button } from "@usesend/ui/src/button";
import { CodeBlock } from "@usesend/ui/src/code-block"; import { CodeBlock } from "@usesend/ui/src/code-block";
import { CodeBlockWithCopy } from "@usesend/ui/src/code-block-with-copy";
import { LangToggle } from "./CodeLangToggle"; import { LangToggle } from "./CodeLangToggle";
const TS_CODE = `import { UseSend } from "usesend-js"; const TS_CODE = `import { UseSend } from "usesend-js";
@@ -149,12 +150,14 @@ export function CodeExample() {
className={idx === 0 ? "block" : "hidden"} className={idx === 0 ? "block" : "hidden"}
> >
{/* Cast to any to align with shiki BundledLanguage without importing types here */} {/* Cast to any to align with shiki BundledLanguage without importing types here */}
<CodeBlock <CodeBlockWithCopy code={l.code}>
lang={l.shiki as any} <CodeBlock
className="p-4 rounded-[10px]" lang={l.shiki as any}
> className="p-4 rounded-[10px]"
{l.code} >
</CodeBlock> {l.code}
</CodeBlock>
</CodeBlockWithCopy>
</div> </div>
))} ))}
</div> </div>
+53
View File
@@ -0,0 +1,53 @@
"use client";
import * as React from "react";
import { CheckIcon } from "lucide-react";
import { Button } from "./button";
import { cn } from "../lib/utils";
import { Copy } from "lucide-react";
interface CodeBlockWithCopyProps {
code: string;
children: React.ReactNode;
className?: string;
}
export function CodeBlockWithCopy({
code,
children,
className,
}: CodeBlockWithCopyProps) {
const [isCopied, setIsCopied] = React.useState(false);
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(code);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
console.error("Failed to copy: ", err);
}
};
return (
<div className={cn("relative group", className)}>
{children}
<Button
variant="ghost"
size="icon"
className={cn(
"absolute top-2 right-2 h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity",
"bg-background/80 backdrop-blur-sm hover:bg-background/90 border border-border/50"
)}
onClick={copyToClipboard}
aria-label="Copy code"
>
{isCopied ? (
<CheckIcon className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</div>
);
}
+6 -1
View File
@@ -139,13 +139,18 @@
} }
} }
/* Prevent italic styling in Shiki code blocks */
.shiki,
.shiki span {
font-style: normal !important;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.shiki, .shiki,
.shiki span { .shiki span {
color: var(--shiki-dark) !important; color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important; background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */ /* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important; font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important; text-decoration: var(--shiki-dark-text-decoration) !important;
} }