feat: Add showLineNumbers prop to Snippet component

This commit is contained in:
Gauthier Daniels 2025-04-13 16:25:01 +02:00
parent 3f6d324980
commit 8bf5c5de40
4 changed files with 95 additions and 73 deletions

View File

@ -6,8 +6,19 @@ import { Fragment, useMemo } from "react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { Button } from "./Button"; import { Button } from "./Button";
import Prism from "prismjs"; import Prism from "prismjs";
import clsx from "clsx";
export default function CSRFence({ children, language }: { children: string; language: string }) { export default function CSRSnippet({
children,
language,
label,
showLineNumbers = false,
}: {
children: string;
language: string;
label?: string;
showLineNumbers?: boolean;
}) {
const { theme } = useTheme(); const { theme } = useTheme();
const prismTheme = useMemo(() => { const prismTheme = useMemo(() => {
@ -23,10 +34,24 @@ export default function CSRFence({ children, language }: { children: string; lan
<> <>
<Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}> <Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}>
{({ className, style, tokens, getTokenProps }) => ( {({ className, style, tokens, getTokenProps }) => (
<pre className={className} style={style}> <div className="relative w-full">
{label && (
<div className="absolute px-4 py-1 left-0 text-sm text-gray-700 dark:text-gray-200 italic w-full bg-gray-200 dark:bg-gray-700 rounded-t-xl">
{label}
</div>
)}
<pre className={clsx(className, { "pt-11": !!label })} style={style}>
<code> <code>
{tokens.map((line, lineIndex) => ( {tokens.map((line, lineIndex) => (
<Fragment key={lineIndex}> <Fragment key={lineIndex}>
{showLineNumbers && (
<span
className="text-gray-400 dark:text-gray-500 text-right font-mono w-8 inline-block pr-4"
style={{ userSelect: "none" }}
>
{lineIndex + 1}
</span>
)}
{line {line
.filter((token) => !token.empty) .filter((token) => !token.empty)
.map((token, tokenIndex) => ( .map((token, tokenIndex) => (
@ -37,11 +62,15 @@ export default function CSRFence({ children, language }: { children: string; lan
))} ))}
</code> </code>
</pre> </pre>
</div>
)} )}
</Highlight> </Highlight>
<Button <Button
className="absolute top-2 right-2 w-8 h-8 aspect-square opacity-0 group-hover:opacity-50 hover:opacity-100 transition-opacity" className={clsx(
"absolute right-2 w-8 h-8 aspect-square opacity-0 group-hover:opacity-50 hover:opacity-100 transition-opacity",
!!label ? "top-10" : "top-2",
)}
size="sm" size="sm"
variant="secondary" variant="secondary"
onClick={copyToClipboard} onClick={copyToClipboard}

View File

@ -10,13 +10,26 @@ import { Fragment, useMemo } from "react";
const CSRSnippet = clientOnly(() => import("./CSRSnippet")); const CSRSnippet = clientOnly(() => import("./CSRSnippet"));
function SSRSnippet({ language, children }: { language: string; children: string }) { function SSRSnippet({
language,
children,
label,
showLineNumbers = false,
}: {
language: string;
children: string;
label?: string;
showLineNumbers?: boolean;
}) {
const { theme } = useTheme(); const { theme } = useTheme();
const prismTheme = useMemo(() => { const prismTheme = useMemo(() => {
return prismThemes[theme]; return prismThemes[theme];
}, [theme]); }, [theme]);
return ( return (
<>
{label && <div className="text-sm text-gray-500 dark:text-gray-400 mb-2">{label}</div>}
<Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}> <Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}>
{({ className, style, tokens, getTokenProps }) => ( {({ className, style, tokens, getTokenProps }) => (
<pre className={className} style={style}> <pre className={className} style={style}>
@ -35,10 +48,21 @@ function SSRSnippet({ language, children }: { language: string; children: string
</pre> </pre>
)} )}
</Highlight> </Highlight>
</>
); );
} }
export function Snippet({ path, language, label }: { path: string; language: string; label?: string }) { export function Snippet({
path,
language,
label,
showLineNumbers,
}: {
path: string;
language: string;
label?: string;
showLineNumbers: boolean;
}) {
const { snippets } = useData<Data>(); const { snippets } = useData<Data>();
const snippet = snippets.find((snippet) => snippet.path === path); const snippet = snippets.find((snippet) => snippet.path === path);
@ -47,6 +71,7 @@ export function Snippet({ path, language, label }: { path: string; language: str
const props = { const props = {
language, language,
label, label,
showLineNumbers,
children: snippet.content, children: snippet.content,
}; };

View File

@ -50,7 +50,7 @@ Parlons dans un premier temps de la signature d'un reducer :
{% tab value="jsx" label="JSX" %} {% tab value="jsx" label="JSX" %}
{% snippet path="data/docs/react/usereducer/reducer-example.jsx" language="jsx" label="test" /%} {% snippet path="data/docs/react/usereducer/reducer-example.jsx" language="jsx" label="test" showLineNumbers=true /%}
{% /tab %} {% /tab %}

View File

@ -83,44 +83,12 @@ const tags = {
}, },
label: { type: String }, label: { type: String },
path: { type: String }, path: { type: String },
showLineNumbers: {
type: Boolean,
default: false,
},
}, },
}, },
// snippet: {
// // render: Fence2,
// attributes: {
// language: {
// type: String,
// default: "auto",
// },
// label: { type: String },
// description: { type: String },
// path: { type: String },
// },
// async transform(node: any, config: any) {
// const attributes = node.transformAttributes(config);
// const pathValue = attributes.path;
// let language = attributes.language ?? "auto";
// let content = "";
// if (!pathValue) {
// console.warn("No path provided for snippet tag");
// } else {
// const absolutePath = path.resolve(__dirname, pathValue);
// // Read the file content
// try {
// content = await fs.readFile(absolutePath, "utf-8");
// } catch (error) {
// console.error("Error reading file:", error);
// content = `Error reading file: ${absolutePath}`;
// language = "plain";
// }
// // return new Tag("fence2", { ...attributes, language, content, label: "Temp" });
// }
// },
// },
}; };
export default tags; export default tags;