feat(frontend): replace native scrollbar styling with OverlayScrollbars
Adopt OverlayScrollbars for cross-platform consistent overlay scrollbars with auto-hide on pointer leave, hover grow effect, and click-to-scroll. - Add overlayscrollbars + overlayscrollbars-react dependencies - Rewrite ScrollArea component from Radix to OverlayScrollbars wrapper - Define custom theme `os-theme-codeg` in globals.css (6px → 8px on hover) - Initialize body-level overlay scrollbar via OverlayScrollbarsInit - Migrate all scrollbar-thin / scrollbar-thin-edge usages to ScrollArea - Keep native .scrollbar-thin fallback for virtua scroll containers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,8 @@
|
||||
"next": "^16",
|
||||
"next-intl": "^4.8.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"overlayscrollbars": "^2.15.1",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"postcss": "^8.5.6",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.1.0",
|
||||
|
||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@@ -107,6 +107,12 @@ importers:
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
overlayscrollbars:
|
||||
specifier: ^2.15.1
|
||||
version: 2.15.1
|
||||
overlayscrollbars-react:
|
||||
specifier: ^0.5.6
|
||||
version: 0.5.6(overlayscrollbars@2.15.1)(react@19.2.4)
|
||||
postcss:
|
||||
specifier: ^8.5.6
|
||||
version: 8.5.6
|
||||
@@ -5227,6 +5233,15 @@ packages:
|
||||
outvariant@1.4.3:
|
||||
resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
|
||||
|
||||
overlayscrollbars-react@0.5.6:
|
||||
resolution: {integrity: sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw==}
|
||||
peerDependencies:
|
||||
overlayscrollbars: ^2.0.0
|
||||
react: '>=16.8.0'
|
||||
|
||||
overlayscrollbars@2.15.1:
|
||||
resolution: {integrity: sha512-glX26JwjL+Tkzv0JNOWdW4VozP5dGXO+Wx8+TPrdTEJTSYT/8eJS8yXM+fewjU0nFq/JeCa+X+BqABNjC4YZSA==}
|
||||
|
||||
own-keys@1.0.1:
|
||||
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -12402,6 +12417,13 @@ snapshots:
|
||||
|
||||
outvariant@1.4.3: {}
|
||||
|
||||
overlayscrollbars-react@0.5.6(overlayscrollbars@2.15.1)(react@19.2.4):
|
||||
dependencies:
|
||||
overlayscrollbars: 2.15.1
|
||||
react: 19.2.4
|
||||
|
||||
overlayscrollbars@2.15.1: {}
|
||||
|
||||
own-keys@1.0.1:
|
||||
dependencies:
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@xterm/xterm/css/xterm.css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
@@ -1002,14 +1001,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Unified scrollbar style for scrollable containers.
|
||||
Thin overlay scrollbar — no gutter reserved, no layout shift. */
|
||||
.scrollbar-thin,
|
||||
.scrollbar-thin-edge {
|
||||
/* Native fallback for containers that cannot use OverlayScrollbars (e.g. virtua) */
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
}
|
||||
|
||||
/* OverlayScrollbars custom theme */
|
||||
.os-theme-codeg {
|
||||
--os-size: 6px;
|
||||
--os-handle-bg: var(--border);
|
||||
--os-handle-bg-hover: var(--muted-foreground);
|
||||
--os-handle-bg-active: var(--muted-foreground);
|
||||
--os-handle-border-radius: 999px;
|
||||
--os-handle-perpendicular-size: 100%;
|
||||
--os-handle-perpendicular-size-hover: 100%;
|
||||
--os-handle-perpendicular-size-active: 100%;
|
||||
}
|
||||
|
||||
.os-theme-codeg:hover {
|
||||
--os-size: 8px;
|
||||
}
|
||||
|
||||
/* Streamdown code blocks: dark mode via shiki dual-theme CSS variables */
|
||||
.dark [data-streamdown="code-block-body"] {
|
||||
background-color: var(--shiki-dark-bg, var(--sdm-bg, transparent)) !important;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ThemeProvider } from "@/components/theme-provider"
|
||||
import { toIntlLocale } from "@/lib/i18n"
|
||||
import { APPEARANCE_INIT_SCRIPT } from "@/lib/appearance-script"
|
||||
import { AppearanceProvider } from "@/components/appearance-provider"
|
||||
import { OverlayScrollbarsInit } from "@/components/overlay-scrollbars-init"
|
||||
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
subsets: ["latin"],
|
||||
@@ -68,7 +69,10 @@ export default async function RootLayout({
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<AppearanceProvider>{children}</AppearanceProvider>
|
||||
<AppearanceProvider>
|
||||
<OverlayScrollbarsInit />
|
||||
{children}
|
||||
</AppearanceProvider>
|
||||
</ThemeProvider>
|
||||
</AppI18nProvider>
|
||||
</NextIntlClientProvider>
|
||||
|
||||
@@ -28,6 +28,7 @@ import { STATUS_ORDER, STATUS_COLORS } from "@/lib/types"
|
||||
import { SidebarConversationCard } from "./sidebar-conversation-card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
@@ -205,8 +206,6 @@ export function SidebarConversationList({
|
||||
cancelled: false,
|
||||
})
|
||||
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const scrollToActiveRef = useRef<() => void>(() => {})
|
||||
const pendingScrollRef = useRef(false)
|
||||
const virtualizerRef = useRef<VirtualizerHandle>(null)
|
||||
@@ -479,12 +478,8 @@ export function SidebarConversationList({
|
||||
) : (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger asChild>
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className={cn(
|
||||
"flex-1 min-h-0 overflow-y-auto scrollbar-thin px-2",
|
||||
"[overflow-anchor:none]"
|
||||
)}
|
||||
<ScrollArea
|
||||
className={cn("flex-1 min-h-0 px-2", "[overflow-anchor:none]")}
|
||||
>
|
||||
<Virtualizer ref={virtualizerRef} itemSize={CARD_HEIGHT}>
|
||||
{flatItems.map((item) => {
|
||||
@@ -537,7 +532,7 @@ export function SidebarConversationList({
|
||||
)
|
||||
})}
|
||||
</Virtualizer>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem onSelect={handleNewConversation}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useMemo } from "react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { useFolderContext } from "@/contexts/folder-context"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
|
||||
type RowMarker = "none" | "added" | "deleted" | "modified"
|
||||
type DiffFileMode = "modified" | "added" | "deleted" | "renamed"
|
||||
@@ -546,16 +547,16 @@ export function UnifiedDiffPreview({
|
||||
|
||||
if (files.length === 0) {
|
||||
return (
|
||||
<div className={cn("h-full overflow-auto scrollbar-thin", className)}>
|
||||
<ScrollArea className={cn("h-full", className)} x="scroll">
|
||||
<pre className="font-mono text-[11px] leading-5 whitespace-pre-wrap text-muted-foreground p-3">
|
||||
{diffText}
|
||||
</pre>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("h-full overflow-auto scrollbar-thin", className)}>
|
||||
<ScrollArea className={cn("h-full", className)} x="scroll">
|
||||
<div className="space-y-3">
|
||||
{files.map((file) => {
|
||||
const newFile = isNewFileOnly(file)
|
||||
@@ -586,7 +587,7 @@ export function UnifiedDiffPreview({
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="overflow-auto scrollbar-thin">
|
||||
<ScrollArea x="scroll">
|
||||
<div className="inline-block min-w-full">
|
||||
{newFile
|
||||
? file.hunks.map((hunk) => (
|
||||
@@ -599,11 +600,11 @@ export function UnifiedDiffPreview({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</section>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { Streamdown } from "streamdown"
|
||||
import { readFileBase64 } from "@/lib/api"
|
||||
import { normalizeMathDelimiters } from "@/components/ai-elements/message"
|
||||
import { defineMonacoThemes, useMonacoThemeSync } from "@/lib/monaco-themes"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import "@/lib/monaco-local"
|
||||
|
||||
const math = createMathPlugin({ singleDollarTextMath: true })
|
||||
@@ -693,7 +694,7 @@ function DiffFileList({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto scrollbar-thin">
|
||||
<ScrollArea className="flex-1 min-h-0">
|
||||
<div className="py-1">
|
||||
{diffOutline.files.map((file) => (
|
||||
<ContextMenu key={file.key}>
|
||||
@@ -743,7 +744,7 @@ function DiffFileList({
|
||||
</ContextMenu>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
stopFileTreeWatch,
|
||||
} from "@/lib/api"
|
||||
import { emitAttachFileToSession } from "@/lib/session-attachment-events"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import type {
|
||||
FileTreeChangedEvent,
|
||||
FileTreeNode,
|
||||
@@ -2167,7 +2168,7 @@ export function FileTreeTab() {
|
||||
<div className="flex flex-col h-full">
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger asChild>
|
||||
<div className="flex-1 min-h-0 overflow-auto pb-1 scrollbar-thin-edge">
|
||||
<ScrollArea className="flex-1 min-h-0 pb-1" x="scroll">
|
||||
<FileTree
|
||||
key={folder?.path ?? "file-tree-empty"}
|
||||
className="border-0 rounded-none bg-transparent w-max min-w-full"
|
||||
@@ -2319,7 +2320,7 @@ export function FileTreeTab() {
|
||||
</ContextMenu>
|
||||
)}
|
||||
</FileTree>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuSub>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
FileTreeFolder,
|
||||
} from "@/components/ai-elements/file-tree"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@@ -1287,7 +1288,7 @@ export function GitChangesTab() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full min-h-0 overflow-y-auto scrollbar-thin-edge">
|
||||
<ScrollArea className="h-full min-h-0" x="scroll">
|
||||
{trackedChanges.length === 0 && untrackedChanges.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full p-4">
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
@@ -1506,7 +1507,7 @@ export function GitChangesTab() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<Dialog
|
||||
open={Boolean(directoryGitActionType && directoryGitActionTarget)}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {
|
||||
type ReactElement,
|
||||
type UIEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
@@ -89,6 +88,7 @@ import {
|
||||
import type { GitBranchList, GitLogEntry, GitLogFileChange } from "@/lib/types"
|
||||
import { toast } from "sonner"
|
||||
import { toErrorMessage } from "@/lib/app-error"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
|
||||
function formatRelativeTime(
|
||||
dateStr: string,
|
||||
@@ -896,14 +896,15 @@ export function GitLogTab() {
|
||||
}
|
||||
}, [folder, refreshBranches, fetchLog])
|
||||
|
||||
const handleScroll = useCallback((e: UIEvent<HTMLDivElement>) => {
|
||||
const nextScrolled = e.currentTarget.scrollTop > 0
|
||||
const handleScroll = useCallback((e: Event) => {
|
||||
const target = e.target as HTMLElement
|
||||
const nextScrolled = target.scrollTop > 0
|
||||
setScrolled((prev) => (prev === nextScrolled ? prev : nextScrolled))
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-y-auto scrollbar-thin px-3 py-3">
|
||||
<ScrollArea className="h-full px-3 py-3">
|
||||
{hasBranches && (
|
||||
<BranchSelector
|
||||
branchList={branchList}
|
||||
@@ -923,13 +924,13 @@ export function GitLogTab() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-y-auto scrollbar-thin px-3 py-3">
|
||||
<ScrollArea className="h-full px-3 py-3">
|
||||
{hasBranches && (
|
||||
<BranchSelector
|
||||
branchList={branchList}
|
||||
@@ -953,13 +954,14 @@ export function GitLogTab() {
|
||||
{t("retry")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-y-auto scrollbar-thin px-3 py-3">
|
||||
<ScrollArea className="h-full px-3 py-3">
|
||||
<div className="flex flex-col min-h-full">
|
||||
{hasBranches && (
|
||||
<BranchSelector
|
||||
branchList={branchList}
|
||||
@@ -976,6 +978,7 @@ export function GitLogTab() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -983,10 +986,11 @@ export function GitLogTab() {
|
||||
<div className="flex flex-col h-full">
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger asChild>
|
||||
<div
|
||||
<ScrollArea
|
||||
onScroll={handleScroll}
|
||||
className="flex-1 min-h-0 overflow-y-auto scrollbar-thin px-3 py-3 space-y-3"
|
||||
className="flex-1 min-h-0 px-3 py-3"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{hasBranches && (
|
||||
<div
|
||||
className={`sticky top-0 z-10 rounded-full bg-sidebar/85 supports-[backdrop-filter]:bg-sidebar/70 backdrop-blur ${scrolled ? "p-2 shadow-md" : "p-0"}`}
|
||||
@@ -1233,6 +1237,7 @@ export function GitLogTab() {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function isRemovedFileDiff(diff: string | null): boolean {
|
||||
@@ -290,9 +291,9 @@ export function SessionFilesTab() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1 min-h-0 overflow-y-auto scrollbar-thin px-2">
|
||||
<ScrollArea className="flex-1 min-h-0 px-2">
|
||||
<SessionFilesContent conversationId={conversationId} />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
25
src/components/overlay-scrollbars-init.tsx
Normal file
25
src/components/overlay-scrollbars-init.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import "overlayscrollbars/overlayscrollbars.css"
|
||||
import { useOverlayScrollbars } from "overlayscrollbars-react"
|
||||
|
||||
export function OverlayScrollbarsInit() {
|
||||
const [init] = useOverlayScrollbars({
|
||||
options: {
|
||||
scrollbars: {
|
||||
theme: "os-theme-codeg",
|
||||
autoHide: "leave",
|
||||
clickScroll: true,
|
||||
},
|
||||
overflow: { x: "hidden" },
|
||||
},
|
||||
defer: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
init(document.body)
|
||||
}, [init])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,55 +1,59 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
|
||||
import { useMemo } from "react"
|
||||
import {
|
||||
OverlayScrollbarsComponent,
|
||||
type OverlayScrollbarsComponentRef,
|
||||
} from "overlayscrollbars-react"
|
||||
import type { OverlayScrollbarsComponentProps } from "overlayscrollbars-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
type ScrollAreaProps = {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
x?: "scroll" | "hidden"
|
||||
y?: "scroll" | "hidden"
|
||||
onScroll?: (event: Event) => void
|
||||
ref?: React.Ref<OverlayScrollbarsComponentRef>
|
||||
}
|
||||
|
||||
function ScrollArea({
|
||||
className,
|
||||
const BASE_OPTIONS: OverlayScrollbarsComponentProps["options"] = {
|
||||
scrollbars: {
|
||||
theme: "os-theme-codeg",
|
||||
autoHide: "leave",
|
||||
clickScroll: true,
|
||||
},
|
||||
}
|
||||
|
||||
export function ScrollArea({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||
className,
|
||||
x = "hidden",
|
||||
y = "scroll",
|
||||
onScroll,
|
||||
ref,
|
||||
}: ScrollAreaProps) {
|
||||
const options = useMemo<OverlayScrollbarsComponentProps["options"]>(
|
||||
() => ({
|
||||
...BASE_OPTIONS,
|
||||
overflow: { x, y },
|
||||
}),
|
||||
[x, y]
|
||||
)
|
||||
|
||||
const events = useMemo<OverlayScrollbarsComponentProps["events"]>(
|
||||
() => (onScroll ? { scroll: (_instance, event) => onScroll(event) } : {}),
|
||||
[onScroll]
|
||||
)
|
||||
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
data-slot="scroll-area"
|
||||
className={cn("relative", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
data-slot="scroll-area-viewport"
|
||||
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
||||
<OverlayScrollbarsComponent
|
||||
ref={ref}
|
||||
className={className}
|
||||
options={options}
|
||||
events={events}
|
||||
defer
|
||||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
</OverlayScrollbarsComponent>
|
||||
)
|
||||
}
|
||||
|
||||
function ScrollBar({
|
||||
className,
|
||||
orientation = "vertical",
|
||||
...props
|
||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
||||
return (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
data-slot="scroll-area-scrollbar"
|
||||
data-orientation={orientation}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||
data-slot="scroll-area-thumb"
|
||||
className="rounded-full bg-border relative flex-1"
|
||||
/>
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
)
|
||||
}
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
||||
|
||||
Reference in New Issue
Block a user