Files
mintel.me/apps/web/src/components/FileExample.tsx
Marc Mintel c1295546a6
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m2s
Build & Deploy / 🏗️ Build (push) Failing after 3m44s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
feat: unify code-like components with shared CodeWindow, fix blog re-render loop, and stabilize layouts
2026-02-15 17:34:07 +01:00

181 lines
5.2 KiB
TypeScript

"use client";
import React, { useState, useRef } from "react";
import * as Prism from "prismjs";
import "prismjs/components/prism-python";
import "prismjs/components/prism-typescript";
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-jsx";
import "prismjs/components/prism-tsx";
import "prismjs/components/prism-docker";
import "prismjs/components/prism-yaml";
import "prismjs/components/prism-json";
import "prismjs/components/prism-markup";
import "prismjs/components/prism-css";
import "prismjs/components/prism-sql";
import "prismjs/components/prism-bash";
import "prismjs/components/prism-markdown";
import { cn } from "../utils/cn";
import { CodeWindow } from "./Effects/CodeWindow";
interface FileExampleProps {
filename: string;
content: string;
language: string;
description?: string;
tags?: string[];
id: string;
}
const prismLanguageMap: Record<string, string> = {
py: "python",
ts: "typescript",
tsx: "tsx",
js: "javascript",
jsx: "jsx",
dockerfile: "docker",
docker: "docker",
yml: "yaml",
yaml: "yaml",
json: "json",
html: "markup",
css: "css",
sql: "sql",
sh: "bash",
bash: "bash",
md: "markdown",
};
export const FileExample: React.FC<FileExampleProps> = ({
filename,
content,
language,
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const [isCopied, setIsCopied] = useState(false);
const fileExtension = filename.split(".").pop() || language;
const prismLanguage = prismLanguageMap[fileExtension] || "markup";
const highlightedCode = Prism.highlight(
content,
Prism.languages[prismLanguage] || Prism.languages.markup,
prismLanguage,
);
const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(content);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 900);
} catch (err) {
console.error("Failed to copy:", err);
}
};
const handleDownload = (e: React.MouseEvent) => {
e.stopPropagation();
const blob = new Blob([content], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const actions = (
<>
<button
type="button"
className={`h-7 w-7 inline-flex items-center justify-center rounded-full border border-slate-200 bg-white text-slate-400 hover:text-slate-900 hover:border-slate-400 transition-all ${isCopied ? "text-green-500 border-green-200" : ""}`}
onClick={handleCopy}
title="Copy to clipboard"
>
<svg
className="w-3 h-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
</button>
<button
type="button"
className="h-7 w-7 inline-flex items-center justify-center rounded-full border border-slate-200 bg-white text-slate-400 hover:text-slate-900 hover:border-slate-400 transition-all"
onClick={handleDownload}
title="Download file"
>
<svg
className="w-3 h-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
</button>
</>
);
return (
<div className="my-8 group/file">
<CodeWindow
title={filename}
actions={actions}
className={cn(
"transition-all duration-300",
isExpanded
? "max-w-4xl"
: "max-w-[600px] cursor-pointer hover:border-slate-200",
)}
minHeight={isExpanded ? "auto" : "80px"}
>
<div
onClick={() => setIsExpanded(!isExpanded)}
className={cn(
"relative",
!isExpanded && "max-h-[80px] overflow-hidden",
)}
>
<pre
className="m-0 p-0 overflow-x-auto text-[13px] leading-[1.65] font-mono text-slate-800"
style={{
fontFamily:
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
}}
>
<code
className={`language-${prismLanguage}`}
dangerouslySetInnerHTML={{ __html: highlightedCode }}
></code>
</pre>
{!isExpanded && (
<div className="absolute inset-0 bg-gradient-to-t from-white/80 to-transparent flex items-end justify-center pb-2">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">
Click to expand
</span>
</div>
)}
</div>
</CodeWindow>
</div>
);
};