mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
feat: snap-collapse the sidebar by dragging it to the left edge
This commit is contained in:
@@ -142,10 +142,11 @@
|
||||
// same way the old `w-52`/`w-12` classes did — `:root` jumps to 18px past 1760px
|
||||
// wide (app.css), which grows the rem-based button content; a fixed-px rail would
|
||||
// not grow with it and the content would overflow. SIDEBAR_MIN_REM is the default
|
||||
// expanded width (the old w-52); the handle only resizes when expanded and only
|
||||
// widens from there — collapsing is the toggle button's job, not the drag's.
|
||||
// expanded width (the old w-52); the handle only widens from there. Dragging the
|
||||
// pointer into the snap zone at the screen's left edge collapses the rail.
|
||||
const SIDEBAR_MIN_REM = 13
|
||||
const SIDEBAR_COLLAPSED_REM = 3
|
||||
const SIDEBAR_SNAP_COLLAPSE_REM = 6
|
||||
// Root font-size in px, used to convert the pointer's clientX (px) into rem.
|
||||
function rootFontPx(): number {
|
||||
if (!BROWSER) return 16
|
||||
@@ -168,11 +169,14 @@
|
||||
// Width (in rem) the content offset must track: the icon strip when collapsed,
|
||||
// the user-chosen width otherwise.
|
||||
let railWidth = $derived(isCollapsed ? SIDEBAR_COLLAPSED_REM : sidebarWidth)
|
||||
// Width transition shared by the rail and the content offset: none for the whole
|
||||
// drag (the rail tracks the pointer 1:1 and hits the min as a hard wall, no
|
||||
// friction), a plain ease only for the collapse/expand toggle.
|
||||
// True for one transition's length after a drag crosses the snap zone's edge, so
|
||||
// the collapse/expand eases before the rail goes back to tracking the pointer.
|
||||
let sidebarSnapping = $state(false)
|
||||
let sidebarSnapTimer: ReturnType<typeof setTimeout> | undefined
|
||||
// Width transition shared by the rail and the content offset: none while a drag
|
||||
// tracks the pointer 1:1, a plain ease for the toggle and the snap.
|
||||
let sidebarTransitionClass = $derived(
|
||||
resizingSidebar ? '' : 'transition-all duration-200 ease-in-out'
|
||||
resizingSidebar && !sidebarSnapping ? '' : 'transition-all duration-200 ease-in-out'
|
||||
)
|
||||
// Set while a drag is live so it can be torn down if the layout unmounts
|
||||
// mid-drag (otherwise the window listeners would leak).
|
||||
@@ -191,12 +195,22 @@
|
||||
handle.setPointerCapture(e.pointerId)
|
||||
} catch {}
|
||||
resizingSidebar = true
|
||||
// Re-expanding a snap-collapsed rail restores the width it had before the drag.
|
||||
const widthAtStart = sidebarWidth
|
||||
const collapsedAtStart = isCollapsed
|
||||
// The rail is fixed at left:0, so the pointer's clientX is the width — in px.
|
||||
// Convert to rem (the unit the rail is sized in) via the root font-size. Pure
|
||||
// resize: clamp at the min so the rail stops there like a wall (dragging left
|
||||
// never collapses — that's the toggle button's job).
|
||||
// Convert to rem (the unit the rail is sized in) via the root font-size. The
|
||||
// rail clamps at the min like a wall, until the pointer reaches the snap zone.
|
||||
const onMove = (ev: PointerEvent) => {
|
||||
sidebarWidth = Math.max(SIDEBAR_MIN_REM, ev.clientX / rootFontPx())
|
||||
const x = ev.clientX / rootFontPx()
|
||||
const collapse = x < SIDEBAR_SNAP_COLLAPSE_REM
|
||||
if (collapse !== isCollapsed) {
|
||||
isCollapsed = collapse
|
||||
sidebarSnapping = true
|
||||
clearTimeout(sidebarSnapTimer)
|
||||
sidebarSnapTimer = setTimeout(() => (sidebarSnapping = false), 200)
|
||||
}
|
||||
sidebarWidth = collapse ? widthAtStart : Math.max(SIDEBAR_MIN_REM, x)
|
||||
}
|
||||
// pointercancel (and unmount, via onDestroy) must clear the state too, or
|
||||
// `resizingSidebar` sticks true — the overlay and handle highlight stay up
|
||||
@@ -204,7 +218,10 @@
|
||||
const stop = () => {
|
||||
if (!resizingSidebar) return
|
||||
resizingSidebar = false
|
||||
clearTimeout(sidebarSnapTimer)
|
||||
sidebarSnapping = false
|
||||
widthPref.val = sidebarWidth
|
||||
if (isCollapsed !== collapsedAtStart) collapsePref.val = isCollapsed
|
||||
window.removeEventListener('pointermove', onMove)
|
||||
window.removeEventListener('pointerup', stop)
|
||||
window.removeEventListener('pointercancel', stop)
|
||||
@@ -1168,22 +1185,19 @@
|
||||
class="flex-1 flex flex-col min-h-0 h-screen shadow-[inset_-1px_0_0_0_rgb(var(--color-border-light))] dark:shadow-[inset_-1px_0_0_0_#374151] [html.github-dark_&]:shadow-[inset_-1px_0_0_0_rgb(var(--color-border-light))]"
|
||||
style:background-color={darkMode ? SIDEBAR_BG_DARK : SIDEBAR_BG}
|
||||
>
|
||||
{#if !isCollapsed}
|
||||
<!-- Resize handle straddling the right edge, only while expanded:
|
||||
drag to widen (clamped at the min). Collapsing is the toggle
|
||||
button's job. -->
|
||||
<div
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize sidebar"
|
||||
title="Drag to resize"
|
||||
class={classNames(
|
||||
'absolute inset-y-0 -right-0.5 w-1.5 cursor-col-resize z-50 transition-colors',
|
||||
resizingSidebar ? '' : 'hover:bg-surface-hover'
|
||||
)}
|
||||
onpointerdown={startSidebarResize}
|
||||
></div>
|
||||
{/if}
|
||||
<!-- Resize handle straddling the right edge: drag to resize, into the
|
||||
left snap zone to collapse, or out of it to expand. -->
|
||||
<div
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize sidebar"
|
||||
title="Drag to resize"
|
||||
class={classNames(
|
||||
'absolute inset-y-0 -right-0.5 w-1.5 cursor-col-resize z-50 transition-colors',
|
||||
resizingSidebar ? '' : 'hover:bg-surface-hover'
|
||||
)}
|
||||
onpointerdown={startSidebarResize}
|
||||
></div>
|
||||
<!-- Workspace picker as the sidebar header (replaces the Windmill logo).
|
||||
Kept in both modes: it scopes which workspace family's sessions
|
||||
the sessions sidebar shows. -->
|
||||
|
||||
Reference in New Issue
Block a user