fix(web): align the sidebar Cordis panel with menu and footer conventions
The Cordis popup now uses the standard menu surface (layer-3 fill, inverted border, lv3 shadow) so it stays visible in dark mode, closes on outside pointerdown, and anchors to its trigger through a measured fixed position that escapes the sidebar overflow clip. The Cordis and Settings footer triggers share the same 42px row, icon size, padding, and hover fill, and the panel's header, heading, and card spacing is rebalanced. Refs #2526
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
/* Settings shell (figma 501:29904 mask context / 501:29947 panel): sidebar
|
||||
foot trigger row + centered 1080x700 modal panel. The trigger uses the
|
||||
sidebar's 34px compact row / 36px rail circle rhythm; the
|
||||
sidebar foot's 42px row / 36px rail circle rhythm; the
|
||||
panel is a two-column layout — 188px nav rail + content column with a
|
||||
54px header and the 24px-padded options area. */
|
||||
|
||||
/* Trigger row: match the other wide sidebar controls' compact vertical rhythm. */
|
||||
/* Trigger row: the 42px foot-row box shared with the Cordis footer action. */
|
||||
.trigger {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: calc(100% + 8px);
|
||||
height: 34px;
|
||||
margin: 4px -4px 4px;
|
||||
padding: 6px 2px 6px 10px;
|
||||
width: calc(100% + 4px);
|
||||
height: 42px;
|
||||
margin: 4px -2px;
|
||||
padding: 0 10px 0 8px;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 49px;
|
||||
height: 42px;
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 49px;
|
||||
padding: 0 8px 0 6px;
|
||||
width: calc(100% + 4px);
|
||||
height: 42px;
|
||||
margin: 0 -2px;
|
||||
padding: 0 10px 0 8px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
@@ -34,7 +35,7 @@
|
||||
}
|
||||
|
||||
.badge:hover {
|
||||
background: var(--dsw-alias-interactive-bg-hover-solid);
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
|
||||
.badge[data-active] {
|
||||
@@ -77,10 +78,10 @@
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* Fixed so the sidebar's overflow clip cannot cut the 420px surface; the
|
||||
left/bottom offsets are measured from the trigger before paint. */
|
||||
.panel {
|
||||
position: fixed;
|
||||
left: 12px;
|
||||
bottom: 128px;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -88,10 +89,10 @@
|
||||
max-width: calc(100vw - 24px);
|
||||
max-height: 60vh;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--dsw-alias-border-l1);
|
||||
border: 1px solid var(--dsw-alias-border-inverted);
|
||||
border-radius: 12px;
|
||||
background: var(--dsw-alias-bg-base);
|
||||
box-shadow: var(--dsw-shadow-lv2);
|
||||
background: var(--dsw-specific-menu);
|
||||
box-shadow: var(--dsw-shadow-lv3);
|
||||
/* Only `.body` scrolls; the header remains fixed above it. */
|
||||
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
||||
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
||||
@@ -105,19 +106,17 @@
|
||||
min-height: 44px;
|
||||
padding: 10px 12px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid var(--dsw-alias-border-l2);
|
||||
background: var(--dsw-alias-bg-base);
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 4px 12px 12px;
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
@@ -137,7 +136,7 @@
|
||||
|
||||
/* Session headings replace per-row ownership markers. */
|
||||
.group {
|
||||
margin: 8px 0 4px;
|
||||
margin: 8px 0 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
@@ -159,10 +158,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
padding: 14px 12px 10px;
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 12px;
|
||||
background: var(--dsw-alias-bg-base);
|
||||
}
|
||||
|
||||
.row[data-cordis-awaiting] {
|
||||
@@ -252,7 +250,7 @@
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 7px;
|
||||
background: var(--dsw-alias-bg-base);
|
||||
background: transparent;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Frame-wide dynamic Plugin inventory, approvals, versions, and lifecycle actions. */
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import type { ButtonHTMLAttributes, ReactNode } from 'react'
|
||||
import {
|
||||
IconCheckOutline16, IconCloseOutline16, IconCordisPluginOutline14, IconPlayOutline16,
|
||||
@@ -119,6 +119,34 @@ export function CordisPanel({
|
||||
const [pending, setPending] = useState<ReadonlySet<CordisDynamicPluginId>>(new Set())
|
||||
const [actionErrors, setActionErrors] = useState<ReadonlyMap<CordisDynamicPluginId, string>>(new Map())
|
||||
const visibleRequests = useRef<Set<ApprovalRequestId>>(new Set())
|
||||
const rootRef = useRef<HTMLDivElement>(null)
|
||||
const [anchor, setAnchor] = useState<{ left: number; bottom: number }>()
|
||||
|
||||
// The panel is position: fixed (the sidebar clips overflow), so it hugs the
|
||||
// trigger through a measured offset instead of document flow.
|
||||
useLayoutEffect(() => {
|
||||
if (!open) return
|
||||
const place = (): void => {
|
||||
const rect = rootRef.current?.getBoundingClientRect()
|
||||
if (rect !== undefined) {
|
||||
setAnchor({ left: rect.left, bottom: window.innerHeight - rect.top + 8 })
|
||||
}
|
||||
}
|
||||
place()
|
||||
window.addEventListener('resize', place)
|
||||
return () => { window.removeEventListener('resize', place) }
|
||||
}, [open])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
const closeOutside = (event: PointerEvent): void => {
|
||||
if (event.target instanceof Node && !rootRef.current?.contains(event.target)) {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('pointerdown', closeOutside)
|
||||
return () => { document.removeEventListener('pointerdown', closeOutside) }
|
||||
}, [open])
|
||||
|
||||
useEffect(() => {
|
||||
const now = new Set<ApprovalRequestId>()
|
||||
@@ -420,9 +448,9 @@ export function CordisPanel({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={wide ? css.layer : `${css.layer} ${css.rail}`}>
|
||||
{open && (
|
||||
<section className={css.panel} data-cordis-panel aria-label={t('panel.title')}>
|
||||
<div ref={rootRef} className={wide ? css.layer : `${css.layer} ${css.rail}`}>
|
||||
{open && anchor !== undefined && (
|
||||
<section className={css.panel} style={anchor} data-cordis-panel aria-label={t('panel.title')}>
|
||||
<header className={css.header}>
|
||||
<span className={css.title}>{t('panel.title')}</span>
|
||||
</header>
|
||||
@@ -458,7 +486,7 @@ export function CordisPanel({
|
||||
aria-expanded={open}
|
||||
onClick={() => { setOpen(value => !value) }}
|
||||
>
|
||||
<IconCordisPluginOutline14 />
|
||||
<IconCordisPluginOutline14 size={wide ? 16 : 18} />
|
||||
{wide && (
|
||||
<>
|
||||
<span className={css.badgeLabel}>{t('panel.trigger')}</span>
|
||||
|
||||
Reference in New Issue
Block a user