feat(webui): pin sidebar collapse button to the resize divider

The collapse control now hangs off the top of the ResizableHandle so it
tracks the divider as the sidebar is resized, instead of sitting in the
header far from the boundary it controls. The header toggle remains for
the collapsed and mobile-overlay states. ResizableHandle now renders
passed children (previously shadowed by the withHandle element).
This commit is contained in:
m4
2026-08-10 23:44:28 +08:00
parent 17271d6ebd
commit adc59ba9a9
2 changed files with 52 additions and 25 deletions
+50 -25
View File
@@ -315,30 +315,36 @@ function HomePageInner({ authEnabled }: HomePageInnerProps) {
)}
</div>
<div className="flex items-center gap-0.5">
<Button
variant="ghost"
size="icon"
onClick={toggleSidebar}
aria-label={sidebarToggleLabel}
className="relative size-8"
>
{sidebar ? (
<PanelLeftClose
className="size-5"
aria-hidden="true"
/>
) : (
<PanelLeft
className="size-5"
aria-hidden="true"
/>
)}
{interruptCount > 0 && (
<span className="absolute right-0 top-0 inline-flex min-h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] text-destructive-foreground">
{interruptCount}
</span>
)}
</Button>
{/* On desktop the expanded sidebar's collapse button lives on the
resize handle (it tracks the divider as the panel is resized);
the header keeps the toggle only for the collapsed and mobile
(overlay) states. */}
{(!sidebar || isDesktopLayout === false) && (
<Button
variant="ghost"
size="icon"
onClick={toggleSidebar}
aria-label={sidebarToggleLabel}
className="relative size-8"
>
{sidebar ? (
<PanelLeftClose
className="size-5"
aria-hidden="true"
/>
) : (
<PanelLeft
className="size-5"
aria-hidden="true"
/>
)}
{interruptCount > 0 && (
<span className="absolute right-0 top-0 inline-flex min-h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] text-destructive-foreground">
{interruptCount}
</span>
)}
</Button>
)}
{!sidebar && (
<Button
variant="ghost"
@@ -520,7 +526,26 @@ function HomePageInner({ authEnabled }: HomePageInnerProps) {
onInterruptCountChange={setInterruptCount}
/>
</ResizablePanel>
<ResizableHandle />
<ResizableHandle>
<Button
variant="ghost"
size="icon"
onClick={toggleSidebar}
onPointerDown={(event) => event.stopPropagation()}
aria-label={sidebarToggleLabel}
className="absolute left-1/2 top-0 z-10 size-8 -translate-x-1/2 rounded-none rounded-b-md border border-t-0 border-border bg-background shadow-sm"
>
<PanelLeftClose
className="size-5"
aria-hidden="true"
/>
{interruptCount > 0 && (
<span className="absolute right-0 top-0 inline-flex min-h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] text-destructive-foreground">
{interruptCount}
</span>
)}
</Button>
</ResizableHandle>
</>
)}
+2
View File
@@ -23,6 +23,7 @@ const ResizablePanel = ResizablePrimitive.Panel;
const ResizableHandle = ({
withHandle,
className,
children,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean;
@@ -39,6 +40,7 @@ const ResizableHandle = ({
<GripVertical className="h-2.5 w-2.5" />
</div>
)}
{children}
</ResizablePrimitive.PanelResizeHandle>
);