152 lines
5.5 KiB
TypeScript
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>
|
|
);
|
|
}
|