1"use client";
2
3import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
4
5import { cn } from "~/lib/utils";
6
7function getVirtualizedScrollFadeClassName({ top, bottom }: { top: boolean; bottom: boolean }) {
8 if (!top && !bottom) return undefined;
9
10 return cn(
11 "virtualized-scroll-fade [--fade-size:1.5rem]",
12 top &&
13 bottom &&
14 "[--virtualized-scroll-fade-mask:linear-gradient(to_bottom,transparent,black_var(--fade-size),black_calc(100%-var(--fade-size)),transparent)]",
15 top &&
16 !bottom &&
17 "[--virtualized-scroll-fade-mask:linear-gradient(to_bottom,transparent,black_var(--fade-size))]",
18 !top &&
19 bottom &&
20 "[--virtualized-scroll-fade-mask:linear-gradient(to_bottom,black_calc(100%-var(--fade-size)),transparent)]",
21 );
22}
23
24function ScrollArea({
25 className,
26 children,
27 scrollFade = false,
28 scrollFadePadding = true,
29 scrollbarGutter = false,
30 hideScrollbars = false,
31 chainVerticalScroll = false,
32 radius = "inherit",
33 viewportTabIndex,
34 ...props
35}: ScrollAreaPrimitive.Root.Props & {
36 scrollFade?: boolean;
37 /** Keep focused and highlighted items clear of the fade. Off for lists
38 * whose rows take focus on click, where the scroll would nudge the list. */
39 scrollFadePadding?: boolean;
40 scrollbarGutter?: boolean;
41 hideScrollbars?: boolean;
42 chainVerticalScroll?: boolean;
43 /** The viewport clips to the parent's radius; "none" for a region flush to an edge. */
44 radius?: "inherit" | "none";
45 /** Override Base UI's focusable viewport when focusable descendants provide scroll access. */
46 viewportTabIndex?: number;
47}) {
48 return (
49 <ScrollAreaPrimitive.Root
50 className={cn(
51 "relative size-full min-h-0 overflow-hidden",
52 radius === "none" ? "rounded-none" : "rounded-[inherit]",
53 className,
54 )}
55 {...props}
56 >
57 <ScrollAreaPrimitive.Viewport
58 {...(viewportTabIndex === undefined ? {} : { tabIndex: viewportTabIndex })}
59 className={cn(
60 "h-full max-h-[inherit] overflow-auto overscroll-contain rounded-[inherit] outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
61 chainVerticalScroll && "overscroll-y-auto",
62 scrollFade &&
63 "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] focus-visible:mask-none [--fade-size:1.5rem]",
64 scrollFade && scrollFadePadding && "scroll-p-[var(--fade-size)]",
65 scrollFade && !scrollFadePadding && "has-focus-visible:mask-none",
66 scrollbarGutter && "scrollbar-gutter-stable",
67 hideScrollbars &&
68 "[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
69 )}
70 data-slot="scroll-area-viewport"
71 >
72 {children}
73 </ScrollAreaPrimitive.Viewport>
74 {!hideScrollbars && (
75 <>
76 <ScrollBar orientation="vertical" />
77 <ScrollBar orientation="horizontal" />
78 <ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
79 </>
80 )}
81 </ScrollAreaPrimitive.Root>
82 );
83}
84
85function ScrollBar({
86 className,
87 orientation = "vertical",
88 ...props
89}: ScrollAreaPrimitive.Scrollbar.Props) {
90 return (
91 <ScrollAreaPrimitive.Scrollbar
92 className={cn(
93 "flex opacity-0 transition-opacity delay-300 data-[orientation=horizontal]:mx-1 data-[orientation=horizontal]:mb-px data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:my-1 data-[orientation=vertical]:mr-px data-[orientation=vertical]:w-1.5 data-[orientation=horizontal]:flex-col data-hovering:opacity-100 data-scrolling:opacity-100 data-hovering:delay-0 data-scrolling:delay-0 data-hovering:duration-100 data-scrolling:duration-100",
94 className,
95 )}
96 data-slot="scroll-area-scrollbar"
97 orientation={orientation}
98 {...props}
99 >
100 <ScrollAreaPrimitive.Thumb
101 className="relative flex-1 rounded-full bg-[var(--app-scrollbar-thumb)] transition-colors hover:bg-[var(--app-scrollbar-thumb-hover)]"
102 data-slot="scroll-area-thumb"
103 />
104 </ScrollAreaPrimitive.Scrollbar>
105 );
106}
107
108export { getVirtualizedScrollFadeClassName, ScrollArea, ScrollBar };