new-personal-site/components/lib/elements/CodeBlock.tsx
Benjamin Toby 8762e2da8d Updates
2025-03-27 07:37:16 +01:00

152 lines
5.5 KiB
TypeScript

import { Check, Copy } from "lucide-react";
import React, {
DetailedHTMLProps,
HTMLAttributes,
PropsWithChildren,
} from "react";
import { twMerge } from "tailwind-merge";
import Stack from "../layout/Stack";
import Row from "../layout/Row";
import Button from "../layout/Button";
import Divider from "../layout/Divider";
export const TWUIPrismLanguages = ["shell", "javascript"] as const;
type Props = PropsWithChildren &
DetailedHTMLProps<HTMLAttributes<HTMLPreElement>, HTMLPreElement> & {
wrapperProps?: DetailedHTMLProps<
HTMLAttributes<HTMLDivElement>,
HTMLDivElement
>;
"data-title"?: string;
backgroundColor?: string;
singleBlock?: boolean;
language?: (typeof TWUIPrismLanguages)[number];
};
/**
* # CodeBlock
*
* @className `twui-code-block-wrapper`
* @className `twui-code-pre-wrapper`
* @className `twui-code-block-pre`
* @className `twui-code-block-header`
*/
export default function CodeBlock({
children,
wrapperProps,
backgroundColor,
singleBlock,
language,
...props
}: Props) {
const codeRef = React.useRef<HTMLDivElement>();
const [copied, setCopied] = React.useState(false);
const title = props?.["data-title"];
const finalBackgroundColor = backgroundColor || "#28272b";
return (
<div
{...wrapperProps}
className={twMerge(
"outline outline-[1px] outline-slate-200 dark:outline-white/10",
`rounded w-full transition-all items-start`,
"relative",
"twui-code-block-wrapper",
wrapperProps?.className
)}
style={{
boxShadow: copied
? "0 0 10px 10px rgba(18, 139, 99, 0.2)"
: undefined,
maxWidth: "calc(100vw - 80px)",
backgroundColor: finalBackgroundColor,
...props.style,
}}
>
<Stack
className={twMerge(
"gap-0 w-full overflow-x-auto relative",
"max-h-[600px] overflow-y-auto"
)}
>
<Row
className={twMerge(
"w-full px-1 h-10 sticky top-0 py-2",
singleBlock ? "absolute !bg-transparent" : "",
"twui-code-block-header"
)}
style={{
backgroundColor: finalBackgroundColor,
}}
>
{title && <span className="text-white/70">{title}</span>}
<div className="ml-auto">
{copied ? (
<Row>
<span className="text-white text-xs twui-code-block-copied-text">
Copied!
</span>
<div className="w-5 h-5 rounded-full bg-emerald-600 text-white flex items-center justify-center">
<Check size={15} />
</div>
</Row>
) : (
<Button
variant="ghost"
color="gray"
beforeIcon={<Copy size={17} color="white" />}
className="!p-1 !bg-transparent"
onClick={() => {
const content =
codeRef.current?.textContent;
if (!content) {
window.alert("No Content to copy");
return;
}
window.navigator.clipboard
.writeText(content)
.then(() => {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 3000);
});
}}
title="Copy Code Snippet"
/>
)}
</div>
</Row>
{!singleBlock && (
<Divider className="!border-white/10 sticky top-10" />
)}
<div
className={twMerge(
`p-1 w-full [&_pre]:!bg-transparent`,
singleBlock ? "" : "-mt-1",
"twui-code-pre-wrapper"
)}
ref={codeRef as any}
>
<pre
{...props}
className={twMerge(
"!my-0",
language ? `language-${language}` : "",
"twui-code-block-pre",
props.className
)}
>
{children}
</pre>
</div>
</Stack>
</div>
);
}