mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat: codeblog is now rendered only when visible, this made a huge performance improvement
This commit is contained in:
parent
7cd74ade2f
commit
bc6437762c
@ -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}) {
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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")}
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user