UI: Improve performance by reducing usage of @radix-ui/react-scroll-area

This commit is contained in:
Fuma Nama
2025-05-14 19:50:26 +09:00
parent 5723bbe58e
commit ac0ab12294
5 changed files with 40 additions and 53 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-ui': patch
---
Improve performance by reducing usage of `@radix-ui/react-scroll-area`
@@ -38,12 +38,10 @@ export function useAnchorObserver(watch: string[], single: boolean): string[] {
function onScroll(): void {
const element = document.scrollingElement;
if (!element) return;
const top = element.scrollTop;
if (element.scrollTop === 0 && single) setActiveAnchor(watch.slice(0, 1));
else if (
element.scrollTop + element.clientHeight >=
element.scrollHeight - 6
) {
if (top <= 0 && single) setActiveAnchor(watch.slice(0, 1));
else if (top + element.clientHeight >= element.scrollHeight - 6) {
setActiveAnchor((active) => {
return active.length > 0 && !single
? watch.slice(watch.indexOf(active[0]))
+19 -26
View File
@@ -8,14 +8,8 @@ import {
useRef,
} from 'react';
import { cn } from '@/utils/cn';
import {
ScrollArea,
ScrollBar,
ScrollViewport,
} from '@/components/ui/scroll-area';
import { useCopyButton } from '@/utils/use-copy-button';
import { buttonVariants } from '@/components/ui/button';
import type { ScrollAreaViewportProps } from '@radix-ui/react-scroll-area';
export type CodeBlockProps = HTMLAttributes<HTMLElement> & {
/**
@@ -39,7 +33,7 @@ export type CodeBlockProps = HTMLAttributes<HTMLElement> & {
*/
keepBackground?: boolean;
viewportProps?: ScrollAreaViewportProps;
viewportProps?: HTMLAttributes<HTMLElement>;
/**
* show line numbers
@@ -97,6 +91,7 @@ export const CodeBlock = forwardRef<HTMLElement, CodeBlockProps>(
return (
<figure
ref={ref}
dir="ltr"
{...props}
className={cn(
'not-prose group relative my-4 overflow-hidden rounded-lg border bg-fd-card text-sm outline-none',
@@ -135,25 +130,23 @@ export const CodeBlock = forwardRef<HTMLElement, CodeBlockProps>(
/>
)
)}
<ScrollArea ref={areaRef} dir="ltr">
<ScrollViewport
{...viewportProps}
className={cn(
'text-[13px] py-3.5 [&_.line]:px-4 max-h-[600px]',
props['data-line-numbers'] && '[&_.line]:pl-3',
viewportProps?.className,
)}
style={{
counterSet: props['data-line-numbers']
? `line ${Number(props['data-line-numbers-start'] ?? 1) - 1}`
: undefined,
...viewportProps?.style,
}}
>
{children}
</ScrollViewport>
<ScrollBar orientation="horizontal" />
</ScrollArea>
<div
ref={areaRef}
{...viewportProps}
className={cn(
'text-[13px] py-3.5 overflow-auto [&_.line]:px-4 max-h-[600px] [scrollbar-width:thin]',
props['data-line-numbers'] && '[&_.line]:pl-3',
viewportProps?.className,
)}
style={{
counterSet: props['data-line-numbers']
? `line ${Number(props['data-line-numbers-start'] ?? 1) - 1}`
: undefined,
...viewportProps?.style,
}}
>
{children}
</div>
</figure>
);
},
+12 -21
View File
@@ -10,7 +10,6 @@ import {
import { cn } from '@/utils/cn';
import { useI18n } from '@/contexts/i18n';
import { TocThumb } from '@/components/layout/toc-thumb';
import { ScrollArea, ScrollViewport } from '../ui/scroll-area';
import { usePageStyles } from '@/contexts/layout';
export interface TOCProps {
@@ -47,7 +46,7 @@ export function Toc(props: HTMLAttributes<HTMLDivElement>) {
} as object
}
>
<div className="flex h-full w-(--fd-toc-width) max-w-full flex-col gap-3 pe-4">
<div className="flex h-full w-(--fd-toc-width) max-w-full flex-col pe-4">
{props.children}
</div>
</div>
@@ -64,30 +63,22 @@ export function TocItemsEmpty() {
);
}
export function TOCScrollArea({
isMenu,
...props
}: ComponentProps<typeof ScrollArea> & { isMenu?: boolean }) {
export function TOCScrollArea(props: ComponentProps<'div'>) {
const viewRef = useRef<HTMLDivElement>(null);
return (
<ScrollArea
<div
{...props}
className={cn('flex flex-col ps-px', props.className)}
ref={viewRef}
className={cn(
'relative min-h-0 text-sm ms-px overflow-auto [scrollbar-width:none] [mask-image:linear-gradient(to_bottom,transparent,white_16px,white_calc(100%-16px),transparent)] py-3',
props.className,
)}
>
<ScrollViewport
ref={viewRef}
className={cn(
'relative min-h-0 text-sm',
isMenu &&
'[mask-image:linear-gradient(to_bottom,transparent,white_16px,white_calc(100%-16px),transparent)] px-4 md:px-6 py-2',
)}
>
<Primitive.ScrollProvider containerRef={viewRef}>
{props.children}
</Primitive.ScrollProvider>
</ScrollViewport>
</ScrollArea>
<Primitive.ScrollProvider containerRef={viewRef}>
{props.children}
</Primitive.ScrollProvider>
</div>
);
}
+1 -1
View File
@@ -154,7 +154,7 @@ export function DocsPage({
<TocPopoverTrigger className="w-full" items={toc} />
<TocPopoverContent>
{tocPopoverOptions.header}
<TOCScrollArea isMenu>
<TOCScrollArea className="px-4 md:px-6">
{tocPopoverOptions.style === 'clerk' ? (
<ClerkTOCItems items={toc} />
) : (