fix(ui): unify scrollbar styles across scrollable containers
Add .scrollbar-thin and .scrollbar-thin-edge utility classes in globals.css and apply them to sidebar, file tree, git changes, git log, session files, diff preview, and message thread panels. Replace scattered inline webkit-scrollbar overrides with the shared classes for consistent appearance and gutter behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Unified scrollbar style for scrollable containers.
|
||||
Matches StickToBottom's internal scrollbar-gutter so all scroll areas
|
||||
behave the same: symmetric gutter, no layout shift. */
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
|
||||
/* Single-edge variant: only reserves space on the scrollbar side */
|
||||
.scrollbar-thin-edge {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -482,11 +482,8 @@ export function SidebarConversationList({
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className={cn(
|
||||
"flex-1 min-h-0 overflow-y-auto px-1.5",
|
||||
"[overflow-anchor:none]",
|
||||
"[&::-webkit-scrollbar]:w-1.5",
|
||||
"[&::-webkit-scrollbar-thumb]:rounded-full",
|
||||
"[&::-webkit-scrollbar-thumb]:bg-border"
|
||||
"flex-1 min-h-0 overflow-y-auto scrollbar-thin",
|
||||
"[overflow-anchor:none]"
|
||||
)}
|
||||
>
|
||||
<Virtualizer ref={virtualizerRef} itemSize={CARD_HEIGHT}>
|
||||
|
||||
@@ -546,7 +546,7 @@ export function UnifiedDiffPreview({
|
||||
|
||||
if (files.length === 0) {
|
||||
return (
|
||||
<div className={cn("h-full overflow-auto", className)}>
|
||||
<div className={cn("h-full overflow-auto scrollbar-thin", className)}>
|
||||
<pre className="font-mono text-[11px] leading-5 whitespace-pre-wrap text-muted-foreground p-3">
|
||||
{diffText}
|
||||
</pre>
|
||||
@@ -555,7 +555,7 @@ export function UnifiedDiffPreview({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("h-full overflow-auto", className)}>
|
||||
<div className={cn("h-full overflow-auto scrollbar-thin", className)}>
|
||||
<div className="space-y-3">
|
||||
{files.map((file) => {
|
||||
const newFile = isNewFileOnly(file)
|
||||
@@ -586,7 +586,7 @@ export function UnifiedDiffPreview({
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="overflow-auto">
|
||||
<div className="overflow-auto scrollbar-thin">
|
||||
<div className="inline-block min-w-full">
|
||||
{newFile
|
||||
? file.hunks.map((hunk) => (
|
||||
|
||||
@@ -693,7 +693,7 @@ function DiffFileList({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||
<div className="flex-1 min-h-0 overflow-y-auto scrollbar-thin">
|
||||
<div className="py-1">
|
||||
{diffOutline.files.map((file) => (
|
||||
<ContextMenu key={file.key}>
|
||||
|
||||
@@ -2167,7 +2167,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-gutter:stable] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border">
|
||||
<div className="flex-1 min-h-0 overflow-auto pb-1 scrollbar-thin-edge">
|
||||
<FileTree
|
||||
key={folder?.path ?? "file-tree-empty"}
|
||||
className="border-0 rounded-none bg-transparent w-max min-w-full"
|
||||
|
||||
@@ -1287,7 +1287,7 @@ export function GitChangesTab() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full min-h-0 overflow-y-auto">
|
||||
<div className="h-full min-h-0 overflow-y-auto scrollbar-thin-edge">
|
||||
{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">
|
||||
|
||||
@@ -903,7 +903,7 @@ export function GitLogTab() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-y-auto p-2">
|
||||
<div className="flex flex-col h-full overflow-y-auto scrollbar-thin px-1 py-3">
|
||||
{hasBranches && (
|
||||
<BranchSelector
|
||||
branchList={branchList}
|
||||
@@ -929,7 +929,7 @@ export function GitLogTab() {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-y-auto p-2">
|
||||
<div className="flex flex-col h-full overflow-y-auto scrollbar-thin px-1 py-3">
|
||||
{hasBranches && (
|
||||
<BranchSelector
|
||||
branchList={branchList}
|
||||
@@ -959,7 +959,7 @@ export function GitLogTab() {
|
||||
|
||||
if (entries.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-y-auto p-2">
|
||||
<div className="flex flex-col h-full overflow-y-auto scrollbar-thin px-1 py-3">
|
||||
{hasBranches && (
|
||||
<BranchSelector
|
||||
branchList={branchList}
|
||||
@@ -985,7 +985,7 @@ export function GitLogTab() {
|
||||
<ContextMenuTrigger asChild>
|
||||
<div
|
||||
onScroll={handleScroll}
|
||||
className="flex-1 min-h-0 overflow-y-auto p-2 space-y-2"
|
||||
className="flex-1 min-h-0 overflow-y-auto scrollbar-thin px-1 py-3 space-y-3"
|
||||
>
|
||||
{hasBranches && (
|
||||
<div
|
||||
|
||||
@@ -103,7 +103,7 @@ function SessionFilesContent({ conversationId }: { conversationId: number }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3 p-3">
|
||||
<div className="space-y-3 px-1 py-3">
|
||||
{groups.map((group, groupIndex) => {
|
||||
const groupKey = `${group.userTurnId}-${group.timestamp}-${groupIndex}`
|
||||
const isOpen = openGroups[groupKey] ?? false
|
||||
@@ -290,7 +290,7 @@ export function SessionFilesTab() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||
<div className="flex-1 min-h-0 overflow-y-auto scrollbar-thin">
|
||||
<SessionFilesContent conversationId={conversationId} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ export function VirtualizedMessageThread<T>({
|
||||
return (
|
||||
<MessageThreadContent
|
||||
className={cn("mx-0 max-w-none p-0", contentClassName)}
|
||||
scrollClassName="[overflow-anchor:none]"
|
||||
scrollClassName="scrollbar-thin [overflow-anchor:none]"
|
||||
{...contentProps}
|
||||
>
|
||||
{items.length === 0 ? (
|
||||
@@ -88,7 +88,7 @@ export function VirtualizedMessageThread<T>({
|
||||
key={getItemKey(item, index)}
|
||||
style={itemStyle(index, items.length)}
|
||||
>
|
||||
<div className={cn("mx-auto max-w-3xl px-4", className)}>
|
||||
<div className={cn("mx-auto max-w-3xl px-2", className)}>
|
||||
{renderItem(item, index)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user