integrate train/css: panel z-index + overflow clip + mobile sidebar

This commit is contained in:
HuanLinOTO
2026-08-23 20:27:37 +08:00
8 changed files with 80 additions and 25 deletions
+4 -2
View File
@@ -1030,18 +1030,20 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
}, [ctx, sessionId, cwd])
if (state === undefined || sessionId === undefined) {
// Keep the unavailable controls focusable: touch users have no hover, so
// focus is the only way the existing Tooltip can explain what is missing.
return (
<div data-dsh-panel-host {...osFileDragShield}>
<div className={css.toggleCluster} data-dsh-toggle-cluster>
{!narrow && (
<Tooltip label={t('noSession')} side="bottom" delayMs={500}>
<button type="button" className={css.toggleButton} disabled aria-label={t('noSession')}>
<button type="button" className={css.toggleButton} aria-disabled="true" aria-label={t('noSession')}>
<IconPanelBottomOutline16 />
</button>
</Tooltip>
)}
<Tooltip label={t('noSession')} side="bottom" delayMs={500}>
<button type="button" className={css.toggleButton} disabled aria-label={t('noSession')}>
<button type="button" className={css.toggleButton} aria-disabled="true" aria-label={t('noSession')}>
<IconPanelRightOutline16 />
</button>
</Tooltip>
+39 -17
View File
@@ -24,16 +24,28 @@
containing block for every panel element. Panels are ABSOLUTE within it
(not fixed), so a desktop shell's intermediate wrapper transform can
never hijack their containing block — the host itself is the only fixed
element, appended directly to document.body. The layer sits at z-index 40
(below the app's overlay stack at 100+); each panel re-enables pointer
element, appended directly to document.body. The layer sits at z-index 25:
above the app's shell overlay (AppFrame overlayLayer, 20) and in-flow
content, below DSH's ui-cordis dynamic-plugin panel (fixed, 30 — the
inventory/surfacing surface must never be hidden behind the workbench),
and below the app's overlay stack at 100+; each panel re-enables pointer
events on itself. Degraded mode ([data-dsh-panel-host-degraded]) covers
the rare shell that transforms <html>/<body> itself: the host becomes
absolute and the mount self-check pins it to the viewport every frame. */
:global([data-dsh-panel-host]) {
position: fixed;
inset: 0;
z-index: 40;
z-index: 25;
pointer-events: none;
/* Clip at the viewport edge: collapsed panels slide off-screen with
`transform: translate(102%)`, and a transformed element still
contributes to its ancestors' scrollable overflow — without this the
hidden panels extended the document's scrollable area, making the whole
page draggable left-right and up-down while any panel was collapsed.
The host is exactly viewport-sized (inset: 0), so the clip is invisible
until a panel slides in from the edge; internal scroll containers and
the app's fixed overlay stack are unaffected. */
overflow: hidden;
}
:global([data-dsh-panel-host][data-dsh-panel-host-degraded]) {
@@ -46,11 +58,13 @@
controls side by side (bottom panel's glyph LEFT of the right panel's).
Always pinned to the viewport corner — inside the right panel's top-right
while it is open, sitting flush in the 34px tab strip (28px buttons at
top: 3) whose right end it really squeezes. z-index 45: above the panels
(40) but below the app's overlay stack (menus/tooltips/modals sit at
100+), so a cordis approval popup is never hidden behind the workbench
(issue #52). Absolute within the panel host (the host's inset: 0 makes
viewport coordinates identical). */
top: 3) whose right end it really squeezes. Host-internal z-index 45
(above the panels' 40); the host's global layer is z-index 25, so the
whole cluster stays below DSH's ui-cordis dynamic-plugin panel (30) and
the app's overlay stack (menus/tooltips/modals sit at 100+), so a cordis
approval popup is never hidden behind the workbench (issue #52). Absolute
within the panel host (the host's inset: 0 makes viewport coordinates
identical). */
.toggleCluster {
position: absolute;
/* env() resolves to 0 on devices without a top inset (desktops). */
@@ -91,27 +105,33 @@
color var(--ds-transition-duration-slow) var(--ds-ease-in-out);
}
.toggleButton:hover:not(:disabled) {
.toggleButton:hover:not(:disabled):not([aria-disabled='true']) {
background: var(--dsw-alias-interactive-bg-hover);
color: var(--dsw-alias-label-primary);
}
.toggleButton:disabled {
.toggleButton:disabled,
.toggleButton[aria-disabled='true'] {
opacity: 0.4;
cursor: default;
}
.panel {
box-sizing: border-box;
position: absolute;
top: 0;
right: 0;
/* Full height: the bottom panel squeezes only the center column, so the
right panel never gives up any vertical position. */
bottom: 0;
/* z-index 40: above the app's in-flow content, below the app's overlay
stack (100+), so menus/tooltips/modals always win over the panels
(issue #52). The toggle cluster (45) sits above it; the corner handle
is a child and stacks within the panel. */
/* Host-internal z-index 40 (sibling of the bottom panel's 40; the two
never overlap — the bottom panel's right edge is this panel's left
edge — and both sit below the toggle cluster's 45). The absolute
global layer comes from the host ([data-dsh-panel-host], 25) — the
whole stack stays above the app's in-flow content and below DSH's
ui-cordis dynamic-plugin panel (30) and the app's overlay stack (100+),
so menus/tooltips/modals always win over the panels (issue #52). The
corner handle is a child and stacks within the panel. */
z-index: 40;
display: flex;
flex-direction: column;
@@ -178,9 +198,11 @@
column only (the Sidebar shell writes left/right inline: from the app's
own left sidebar to the right panel's left edge) — the sidebars never
give up any position. Same surface as the right panel, with a top
hairline instead of the left one. z-index 40 mirrors the right panel
(issue #52: below the app's overlay stack so popups win over the
workbench). */
hairline instead of the left one. Host-internal z-index 40 mirrors the
right panel; the absolute global layer comes from the host (25), below
DSH's ui-cordis dynamic-plugin panel (30) so the cordis inventory
surface floats above the workbench, and below the app's overlay stack
(100+) so popups win over the workbench (issue #52). */
.bottomPanel {
position: absolute;
bottom: 0;