feat: drag to resize the input sidebar (#80)#180
Conversation
Adds a grab strip on the sidebar's trailing edge. Dragging it resizes the input sidebar, capped at half the screen width as the issue asks. How it works: Mantine's AppShell drives both the navbar box and the main pane's offset from two CSS variables it writes inline on the shell (--app-shell-navbar-width / --app-shell-navbar-offset), and builds the collapse transform out of the width var. The drag writes those two vars, so the sidebar and the output pane move together, the collapse animation stays correct, and there is no server round-trip while dragging. dmc recomputes those vars from the navbar prop whenever the shell re-renders, and toggle_sidebar returns a fresh dict on every collapse/expand -- which would snap a resized sidebar back to the default. On release the width is persisted into a dcc.Store that toggle_sidebar now reads, so a dragged width survives a collapse/expand cycle (same "width lives in two places" constraint as v0.5.5 / #143). A missing or nonsensical stored width falls back to the default. The handle is keyboard operable (focusable, role=separator, arrow keys nudge, shift for a coarser step) and hidden below the AppShell breakpoint, where the navbar is a full-width overlay with no edge to drag. Also flattens the navbar children in both layouts: generate_input_component returns a list, and Dash does not flatten a nested one -- appending the handle naively dropped the chat sidecar's components (caught by test_chat). Closes #80 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…h var Real-browser verification (Playwright) caught the keyboard path being broken: the first ArrowRight jumped the sidebar 300 -> 210 instead of stepping to 310. The handler read the current width out of --app-shell-navbar-width, but dmc supplies that var from a stylesheet as a rem expression, `calc(18.75rem * 1)`, not as inline pixels. parseInt returned NaN, so the nudge fell back to MIN_WIDTH. Reading the computed value instead did not help either -- it is the same rem expression. Measure the rendered navbar box (getBoundingClientRect) instead, which is always real pixels and true no matter how the width was set. The drag path was unaffected because it derives the width from pointer position, which is why this only showed up once a browser actually drove the keyboard. Verified in Chromium: ArrowRight +10, Shift+ArrowRight +50, ArrowLeft -10, and the drag/cap/collapse-survival behaviours all still hold. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Verified in a real browser now (Playwright + Chromium), which changed the outcome — it caught a bug the headless tests could not. The bug it caughtThe keyboard path was broken: the first The handler read the current width out of The drag path was unaffected because it derives width from pointer position — which is exactly why only a browser driving the keyboard surfaced it. Verified behaviour (viewport 1400px)So the earlier "could not verify the drag feel" caveat is now closed: the drag, the 50% cap, the main-pane tracking, collapse/expand survival, keyboard stepping, and the hover affordance are all confirmed against a live app. |
Closes #80 — "Users should be able to drag along the width of the input component sidebar in the sidebar layout up to 50% of the width of the screen."
Adds a grab strip on the sidebar's trailing edge. Drag it to resize the input sidebar, capped at half the viewport as the issue asks.
How it works
Mantine's AppShell drives both the navbar box and the main pane's offset from two CSS variables it writes inline on the shell (
--app-shell-navbar-width/--app-shell-navbar-offset), and it builds the collapse transform out of the width var. The drag writes those two vars, so:The persistence problem this had to solve
dmc recomputes those vars from the
navbarprop on every shell re-render, andtoggle_sidebarreturns a fresh dict on each collapse/expand — which would snap a resized sidebar back to the default. So on release the width is persisted into adcc.Storethattoggle_sidebarnow reads back. This is the same "width lives in two places" constraint as v0.5.5 / #143. A missing or nonsensical stored value falls back to the default.Details
role="separator"; arrow keys nudge it (shift = coarser step), so resizing isn't pointer-only.A bug this surfaced
generate_input_component()returns a list, and Dash does not flatten a nested list inchildren— so naively appending the handle as[navbar_content, handle]silently dropped the chat sidecar's components. Caught bytest_chat(4 failures) and fixed by flattening the navbar children in both layouts.Tests
New
tests/test_sidebar_resize.py(8): handle + store present in both the sidebar and chat layouts, the default width when never dragged, a dragged width preserved, survival across a collapse/expand cycle, bogus-value fallback, keyboard reachability, and the 50%-cap contract in the drag script. Driven through the realtoggle_sidebarcallback over Dash's update wire, not by calling the function directly.426 passed across the non-browser suites; Selenium runs in CI.
What I could not verify here
The drag interaction itself is clientside and needs a real browser — Playwright isn't available in this environment, so I verified the wiring, the persistence over Dash's update wire, and the CSS/JS contracts, but not the visual feel of dragging. Worth a quick manual check before release.
🤖 Generated with Claude Code