sidebar: squeeze the conversation layout instead of floating over it

The open panel now occupies layout space: #root (the AppFrame grid) gives
up the panel width on the right via margin-right driven by a
--dsh-sidebar-width CSS variable, and only the center column is flexible,
so the squeeze lands exactly on the agent output and the input bar (VSCode
sidebar behavior). The fixed panel covers the vacated strip; expand/
collapse animates the margin and the slide on the same theme duration,
width drags disable the transition, and prefers-reduced-motion is honored.
This commit is contained in:
Menghuan1918
2026-08-08 14:51:05 +08:00
parent 0621504e1b
commit 302bd85e62
3 changed files with 42 additions and 0 deletions
+13
View File
@@ -101,6 +101,19 @@ export function Sidebar(props: { ctx: Context }) {
const widthDrag = useRef({ startX: 0, startWidth: 0 })
const [draggingWidth, setDraggingWidth] = useState(false)
// Layout push: the app shell gives up the panel's width while it is open
// (0 while collapsed), so the conversation and input bar are squeezed
// instead of covered. Dragging disables the layout transition.
useEffect(() => {
const width = snapshot.state?.panelOpen === true ? snapshot.state.width : 0
document.documentElement.style.setProperty('--dsh-sidebar-width', `${width}px`)
}, [snapshot.state?.panelOpen, snapshot.state?.width])
useEffect(() => {
if (draggingWidth) document.body.setAttribute('data-dsh-sidebar-dragging', '')
else document.body.removeAttribute('data-dsh-sidebar-dragging')
}, [draggingWidth])
// Whether the panel currently fills the viewport (fullscreen expansion).
const fullscreen = state !== undefined && window.innerWidth - state.width < 8
+1
View File
@@ -12,6 +12,7 @@ import { Sidebar } from './Sidebar.tsx'
import { registerTurnTailInterception } from './intercept.tsx'
import { t } from './locales.ts'
import css from './sidebar.module.css'
import './layout.css'
/** Services required before mounting (provided by the client runtime). */
export const inject = ['slots', 'sessions']
+28
View File
@@ -0,0 +1,28 @@
/**
* Layout push: when the sidebar is open it OCCUPIES the layout instead of
* floating over it — the app shell (#root, the AppFrame three-column grid)
* gives up the panel's width on the right. Only the center column is
* flexible (1fr), so the squeeze lands exactly on the conversation output
* and the input bar, like a VSCode sidebar. The fixed panel covers the
* vacated strip, which looks seamless because #root's background is the
* theme base.
*
* The width rides a CSS variable updated by the Sidebar shell (0 while
* collapsed); expand/collapse animates both the margin and the panel slide
* on the same theme duration. Width drags disable the transition so the
* layout tracks the pointer.
*/
#root {
margin-right: var(--dsh-sidebar-width, 0px);
transition: margin-right var(--ds-transition-duration-slow) var(--ds-ease-in-out);
}
body[data-dsh-sidebar-dragging] #root {
transition: none;
}
@media (prefers-reduced-motion: reduce) {
#root {
transition: none;
}
}