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:
+50
-25
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user