Scroll Area

PreviousNext

A customizable scroll area component

Docs
pureuiui

Preview

Loading preview…
registry/pure-ui/ui/scroll-area/index.tsx
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";

import { cn } from "@/lib/classes";

interface ScrollAreaProps extends ScrollAreaPrimitive.Root.Props {
  orientation?: "horizontal" | "vertical" | "both";
  scrollShadow?: "vertical" | "horizontal" | "both" | "none";
}

function ScrollArea({
  className,
  orientation = "vertical",
  scrollShadow = "none",
  children,
  ...props
}: ScrollAreaProps) {
  return (
    <ScrollAreaPrimitive.Root className="min-h-0" {...props}>
      <ScrollAreaPrimitive.Viewport
        data-slot="scroll-area-viewport"
        className={cn(
          "border border-border",
          "size-full overscroll-contain rounded-[inherit] outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
          className
        )}
      >
        <div
          data-slot="scroll-area-vertical-shadow"
          className={cn(
            scrollShadow === "vertical" || scrollShadow === "both"
              ? "block"
              : "hidden",
            [
              "absolute inset-0 pointer-events-none",
              "before:content-[''] after:content-[''] before:block after:block before:absolute after:absolute before:left-0 after:left-0 before:w-full after:w-full before:pointer-events-none after:pointer-events-none",
              "before:rounded-md before:rounded-b-none after:rounded-md after:rounded-t-none",
              "before:transition-[height] after:transition-[height] before:duration-100 after:duration-100 before:ease-out after:ease-out",
              "before:[--scroll-area-overflow-y-start:inherit]",
              "after:[--scroll-area-overflow-y-end:inherit]",
              "before:top-0 after:bottom-0 [--bg:var(--background)] before:bg-[linear-gradient(to_bottom,var(--bg),transparent)] after:bg-[linear-gradient(to_top,var(--bg),transparent)] before:h-[min(40px,var(--scroll-area-overflow-y-start))] after:h-[min(40px,var(--scroll-area-overflow-y-end,40px))]",
            ]
          )}
          style={
            {
              "--scroll-area-overflow-y-start": "inherit",
              "--scroll-area-overflow-y-end": "inherit",
            } as React.CSSProperties
          }
        />
        <div
          data-slot="scroll-area-horizontal-shadow"
          className={cn(
            scrollShadow === "horizontal" || scrollShadow === "both"
              ? "block"
              : "hidden",
            [
              "absolute inset-px pointer-events-none",
              "before:content-[''] after:content-[''] before:block after:block before:absolute after:absolute before:top-0 after:top-0 before:h-full after:h-full before:pointer-events-none after:pointer-events-none",
              "before:rounded-md before:rounded-r-none after:rounded-md after:rounded-l-none",
              "before:transition-[height] after:transition-[height] before:duration-100 after:duration-100 before:ease-out after:ease-out",
              "before:[--scroll-area-overflow-x-start:inherit]",
              "after:[--scroll-area-overflow-x-end:inherit]",
              "before:left-0 after:right-0 [--bg:var(--background)] dark:[--bg:var(--background)] before:bg-[linear-gradient(to_right,var(--bg),transparent)] after:bg-[linear-gradient(to_left,var(--bg),transparent)] before:w-[min(40px,var(--scroll-area-overflow-x-start))] after:w-[min(40px,var(--scroll-area-overflow-x-end,40px))]",
            ]
          )}
          style={
            {
              "--scroll-area-overflow-x-start": "inherit",
              "--scroll-area-overflow-x-end": "inherit",
            } as React.CSSProperties
          }
        />
        {children}
      </ScrollAreaPrimitive.Viewport>
      {orientation === "both" ? (
        <>
          <ScrollBar orientation="vertical" />
          <ScrollBar orientation="horizontal" />
        </>
      ) : (
        <ScrollBar orientation={orientation} />
      )}
      <ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
    </ScrollAreaPrimitive.Root>
  );
}

interface ScrollAreaContentProps extends ScrollAreaPrimitive.Viewport.Props {}

function ScrollAreaContent({
  className,
  children,
  ...props
}: ScrollAreaContentProps) {
  return (
    <ScrollAreaPrimitive.Content
      data-slot="scroll-area-content"
      className={cn("size-full", className)}
      {...props}
    >
      {children}
    </ScrollAreaPrimitive.Content>
  );
}

interface ScrollBarProps extends ScrollAreaPrimitive.Scrollbar.Props {}

function ScrollBar({
  className,
  orientation = "vertical",
  ...props
}: ScrollBarProps) {
  return (
    <ScrollAreaPrimitive.Scrollbar
      className={cn(
        "m-1.5 flex opacity-0 bg-muted rounded-full transition-opacity delay-200 data-[orientation=horizontal]:h-1.5 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",
        className
      )}
      data-slot="scroll-area-scrollbar"
      orientation={orientation}
      {...props}
    >
      <ScrollAreaPrimitive.Thumb
        className="relative flex-1 rounded-full bg-muted-foreground/40"
        data-slot="scroll-area-thumb"
      />
    </ScrollAreaPrimitive.Scrollbar>
  );
}

export { ScrollArea, ScrollAreaContent, ScrollBar };

Installation

npx shadcn@latest add @pureui/scroll-area

Usage

import { ScrollArea } from "@/components/ui/scroll-area"
<ScrollArea />