diff --git a/src/app/globals.css b/src/app/globals.css
index 9602afe..3065800 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -99,4 +99,20 @@
[cmdk-empty=""] {
color: hsl(var(--muted-foreground)) !important;
}
+
+ .code-block {
+ @apply relative flex flex-col rounded-xl border border-border bg-secondary/50 overflow-hidden;
+ }
+
+ .code-block__header {
+ @apply flex items-center justify-between px-4 py-2 border-b border-border bg-secondary/30 text-xs font-medium text-muted-foreground;
+ }
+
+ .code-block__code {
+ @apply p-4 overflow-x-auto font-mono text-sm leading-relaxed;
+ }
+
+ .code-block__copy-button {
+ @apply hover:bg-secondary/80 transition-colors;
+ }
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 854363e..6293fa4 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,4 +1,5 @@
import { TemplateCard } from "@/components/TemplateCard";
+import { CodeBlock } from "@/components/CodeBlock";
export default function Page() {
const jsonLd = {
@@ -27,6 +28,15 @@ export default function Page() {
Opinionated production-ready templates for mobile, cloud, and web developers
+
+
+
+ terminal
+
+
+
+
+
{/* Templates Section */}
diff --git a/src/components/CodeBlock/CopyButton.tsx b/src/components/CodeBlock/CopyButton.tsx
new file mode 100644
index 0000000..e02376c
--- /dev/null
+++ b/src/components/CodeBlock/CopyButton.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { useState } from "react";
+import { Button } from "@heroui/react";
+import { Copy, Check } from "lucide-react";
+
+export function CodeBlockCopyButton({ code, className }: { code: string; className?: string }) {
+ const [copied, setCopied] = useState(false);
+
+ const copyToClipboard = async () => {
+ try {
+ await navigator.clipboard.writeText(code);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ console.error("Failed to copy: ", err);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/CodeBlock/__tests__/index.test.tsx b/src/components/CodeBlock/__tests__/index.test.tsx
new file mode 100644
index 0000000..6689a0b
--- /dev/null
+++ b/src/components/CodeBlock/__tests__/index.test.tsx
@@ -0,0 +1,49 @@
+import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import { CodeBlock } from "../index";
+import { describe, it, expect, vi, beforeEach } from "vitest";
+
+// Mock navigator.clipboard
+Object.assign(navigator, {
+ clipboard: {
+ writeText: vi.fn().mockImplementation(() => Promise.resolve()),
+ },
+});
+
+describe("CodeBlock", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("renders the code correctly", () => {
+ const code = "npx create-cur8d";
+ render(
+
+
+
+ );
+
+ expect(screen.getByText(code)).toBeInTheDocument();
+ });
+
+ it("copies code to clipboard when copy button is clicked", async () => {
+ const code = "npx create-cur8d";
+ render(
+
+
+
+
+
+
+ );
+
+ const copyButton = screen.getByLabelText("Copy code");
+ fireEvent.click(copyButton);
+
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith(code);
+
+ // Check if it changes to check icon (success)
+ await waitFor(() => {
+ expect(screen.queryByLabelText("Copy code")?.querySelector(".text-success")).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/components/CodeBlock/index.tsx b/src/components/CodeBlock/index.tsx
new file mode 100644
index 0000000..e9dd1b8
--- /dev/null
+++ b/src/components/CodeBlock/index.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+import { CodeBlockCopyButton } from "./CopyButton";
+
+interface CodeBlockProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+function CodeBlockRoot({ children, className }: CodeBlockProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+interface CodeBlockHeaderProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+function CodeBlockHeader({ children, className }: CodeBlockHeaderProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+interface CodeBlockCodeProps {
+ code: string;
+ language?: string;
+ className?: string;
+}
+
+function CodeBlockCode({ code, language, className }: CodeBlockCodeProps) {
+ return (
+
+ {code}
+
+ );
+}
+
+export const CodeBlock = Object.assign(CodeBlockRoot, {
+ Header: CodeBlockHeader,
+ Code: CodeBlockCode,
+ CopyButton: CodeBlockCopyButton,
+});