feat: codeblog is now rendered only when visible, this made a huge performance improvement

This commit is contained in:
Junior Garcia 2024-12-05 18:00:14 -03:00
parent 7cd74ade2f
commit bc6437762c
3 changed files with 217 additions and 137 deletions

View File

@ -43,11 +43,11 @@ export const metadata: Metadata = {
openGraph: siteConfig.openGraph,
authors: [
{
name: "jrgarciadev",
url: "https://jrgarciadev.com",
name: "getnextui",
url: "https://x.com/getnextui",
},
],
creator: "jrgarciadev",
creator: "getnextui",
alternates: {
canonical: "https://nextui.org",
types: {
@ -57,14 +57,14 @@ export const metadata: Metadata = {
};
export const viewport: Viewport = {
initialScale: 1,
themeColor: [
{color: "#f4f4f5", media: "(prefers-color-scheme: light)"},
{color: "#111111", media: "(prefers-color-scheme: dark)"},
],
userScalable: true,
viewportFit: "cover",
width: "device-width",
initialScale: 1,
maximumScale: 1,
userScalable: false,
};
export default function RootLayout({children}: {children: React.ReactNode}) {

View File

@ -53,7 +53,7 @@ export const ReactLiveDemo: React.FC<ReactLiveDemoProps> = ({
const content = (
<>
{files?.[DEFAULT_FILE] && (
<div className="absolute top-[-28px] right-[-8px]">
<div className="absolute top-[-28px] right-[-8px] z-50">
<CopyButton
className="before:hidden opacity-0 group-hover/code-demo:opacity-100 transition-opacity text-zinc-400"
value={files?.[DEFAULT_FILE] as string}

View File

@ -3,6 +3,7 @@
import type {Language, PrismTheme} from "prism-react-renderer";
import type {TransformTokensTypes} from "./helper";
import {useIntersectionObserver} from "usehooks-ts";
import React, {forwardRef, useEffect} from "react";
import {clsx, dataAttr, getUniqueID} from "@nextui-org/shared-utils";
import BaseHighlight, {defaultProps} from "prism-react-renderer";
@ -63,6 +64,194 @@ const calculateLinesToHighlight = (meta?: string) => {
};
};
const calculateHeight = (codeString: string) => {
const lines = codeString.split("\n").length;
return lines * 24;
};
const CodeBlockHighlight = ({
codeString,
language,
codeLang,
theme,
showLines,
removeIndent,
hideScrollBar,
preRef,
isMultiLine,
shouldHighlightLine,
highlightStyle,
className: classNameProp,
...props
}: CodeblockProps & {
codeLang: Language;
isMultiLine: boolean;
shouldHighlightLine: (index: number) => boolean;
highlightStyle: HighlightStyle[];
preRef: React.Ref<HTMLElement>;
}) => {
const height = calculateHeight(codeString);
const [intersectionRef, isVisible] = useIntersectionObserver({
threshold: 0,
});
return (
<div
ref={intersectionRef}
style={{
height: isVisible ? "auto" : `${height}px`,
}}
>
{isVisible ? (
<BaseHighlight
{...defaultProps}
code={codeString}
language={codeLang}
theme={theme}
{...props}
>
{({className, style, tokens, getLineProps, getTokenProps}) => (
<pre
ref={(element) => {
// Merge the refs
if (typeof preRef === "function") {
preRef(element);
} else if (preRef) {
// @ts-ignore
preRef.current = element;
}
}}
className={clsx(
className,
classNameProp,
`language-${codeLang}`,
"max-w-full contents",
{
"flex-col": isMultiLine,
"overflow-x-scroll scrollbar-hide": hideScrollBar,
},
)}
data-language={language}
style={style}
>
{transformTokens(tokens).map((line) => {
const folderLine = line[0] as TransformTokensTypes;
const isFolder = folderLine.types?.includes("folderStart");
const renderLine = (
line: TransformTokensTypes[],
i: number,
as: "div" | "span" = "div",
) => {
const Tag = as;
const lineProps = getLineProps({line, key: i});
return (
<Tag
{...omit(lineProps, ["key"])}
key={`${i}-${getUniqueID("line-wrapper")}`}
className={clsx(
lineProps.className,
removeIndent ? "pr-4" : "px-4",
"relative [&>span]:relative [&>span]:z-10",
{
"px-2": showLines,
},
{
"before:to-code-background before:absolute before:left-0 before:z-0 before:h-full before:w-full before:bg-gradient-to-r before:from-white/10 before:content-[''] before:pointer-events-none":
isFolder ? false : shouldHighlightLine(i),
},
)}
data-deleted={dataAttr(highlightStyle?.[i] === "deleted")}
data-inserted={dataAttr(highlightStyle?.[i] === "inserted")}
>
{showLines && (
<span
className={cn(
"mr-6 select-none text-xs opacity-30",
i + 1 >= 10 ? "mr-4" : "",
i + 1 >= 100 ? "mr-2" : "",
i + 1 >= 1000 ? "mr-0" : "",
)}
>
{i + 1}
</span>
)}
{line.map((token, key) => {
const props = getTokenProps({token, key}) || {};
const isCopy = token.types.includes("copy");
return isCopy ? (
<span key={key} className="copy-token" style={{whiteSpace: "inherit"}}>
{token.folderContent?.map((folderTokens) => {
return folderTokens.map((token, index) => {
// Hack for wrap line
return token.content === "" ? (
<div key={`${index}-${getUniqueID("line")}`} />
) : (
<span key={`${index}-${getUniqueID("line")}`}>
{token.content}
</span>
);
});
})}
</span>
) : (
<span
{...omit(props, ["key"])}
key={`${key}-${getUniqueID("line")}`}
className={cn(className, token.class)}
style={{
...props.style,
...(highlightStyleToken.some((t) => {
const content = token.content.trim();
const regex = t instanceof RegExp ? t : new RegExp(t);
return regex.test(content);
})
? {color: "rgb(var(--code-function))"}
: {}),
}}
/>
);
})}
</Tag>
);
};
const renderFolderTokens = (tokens: TransformTokensTypes[][]) => {
return tokens.map((token, key) => {
const index = key + folderLine.index! + 1;
return renderLine(token, index);
});
};
return isFolder ? (
<details key={`${folderLine.index}`} open={folderLine.open ? true : undefined}>
<summary className="cursor-pointer">
{renderLine(folderLine.summaryContent as any, folderLine.index!, "span")}
</summary>
{renderFolderTokens(folderLine.folderContent as any)}
</details>
) : (
renderLine(line, folderLine.index!)
);
})}
</pre>
)}
</BaseHighlight>
) : (
<div className={clsx(classNameProp, "w-full bg-code-background rounded-lg")} />
)}
</div>
);
};
const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
(
{
@ -142,136 +331,27 @@ const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
}, []);
return (
<BaseHighlight
{...defaultProps}
code={codeString}
language={codeLang}
theme={theme}
{...props}
>
{({className, style, tokens, getLineProps, getTokenProps}) => (
<pre
ref={ref}
className={clsx(
className,
classNameProp,
`language-${codeLang}`,
"max-w-full contents",
{
"flex-col": isMultiLine,
"overflow-x-scroll scrollbar-hide": hideScrollBar,
},
)}
data-language={language}
style={style}
>
{transformTokens(tokens).map((line) => {
const folderLine = line[0] as TransformTokensTypes;
const isFolder = folderLine.types?.includes("folderStart");
const renderLine = (
line: TransformTokensTypes[],
i: number,
as: "div" | "span" = "div",
) => {
const Tag = as;
const lineProps = getLineProps({line, key: i});
return (
<Tag
{...omit(lineProps, ["key"])}
key={`${i}-${getUniqueID("line-wrapper")}`}
className={clsx(
lineProps.className,
removeIndent ? "pr-4" : "px-4",
"relative [&>span]:relative [&>span]:z-10",
{
"px-2": showLines,
},
{
"before:to-code-background before:absolute before:left-0 before:z-0 before:h-full before:w-full before:bg-gradient-to-r before:from-white/10 before:content-['']":
isFolder ? false : shouldHighlightLine(i),
},
)}
data-deleted={dataAttr(highlightStyle?.[i] === "deleted")}
data-inserted={dataAttr(highlightStyle?.[i] === "inserted")}
>
{showLines && (
<span
className={cn(
"mr-6 select-none text-xs opacity-30",
i + 1 >= 10 ? "mr-4" : "",
i + 1 >= 100 ? "mr-2" : "",
i + 1 >= 1000 ? "mr-0" : "",
)}
>
{i + 1}
</span>
)}
{line.map((token, key) => {
const props = getTokenProps({token, key}) || {};
const isCopy = token.types.includes("copy");
return isCopy ? (
<span key={key} className="copy-token" style={{whiteSpace: "inherit"}}>
{token.folderContent?.map((folderTokens) => {
return folderTokens.map((token, index) => {
// Hack for wrap line
return token.content === "" ? (
<div key={`${index}-${getUniqueID("line")}`} />
) : (
<span key={`${index}-${getUniqueID("line")}`}>{token.content}</span>
);
});
})}
</span>
) : (
<span
{...omit(props, ["key"])}
key={`${key}-${getUniqueID("line")}`}
className={cn(className, token.class)}
style={{
...props.style,
...(highlightStyleToken.some((t) => {
const content = token.content.trim();
const regex = t instanceof RegExp ? t : new RegExp(t);
return regex.test(content);
})
? {color: "rgb(var(--code-function))"}
: {}),
}}
/>
);
})}
</Tag>
);
};
const renderFolderTokens = (tokens: TransformTokensTypes[][]) => {
return tokens.map((token, key) => {
const index = key + folderLine.index! + 1;
return renderLine(token, index);
});
};
return isFolder ? (
<details key={`${folderLine.index}`} open={folderLine.open ? true : undefined}>
<summary className="cursor-pointer">
{renderLine(folderLine.summaryContent as any, folderLine.index!, "span")}
</summary>
{renderFolderTokens(folderLine.folderContent as any)}
</details>
) : (
renderLine(line, folderLine.index!)
);
})}
</pre>
)}
</BaseHighlight>
<>
<CodeBlockHighlight
className={classNameProp}
codeLang={codeLang}
codeString={codeString}
hideScrollBar={hideScrollBar}
highlightStyle={highlightStyle}
isMultiLine={isMultiLine}
language={language}
preRef={ref}
removeIndent={removeIndent}
shouldHighlightLine={shouldHighlightLine}
showLines={showLines}
theme={theme}
{...props}
/>
{/* <div
ref={intersectionRef}
className={clsx(classNameProp, "w-full bg-code-background rounded-lg")}
/> */}
</>
);
},
);