fix(canvas): 画布工具栏 - 修复设置面板布局与命令标签遮挡

This commit is contained in:
ddCat
2026-08-05 13:51:52 +08:00
parent c8158b159e
commit 54ffdebbd9
6 changed files with 208 additions and 64 deletions

View File

@@ -27,17 +27,21 @@ export function CanvasCreateCommandGrid({ commands, variant = "node" }: { comman
whileTap={reducedMotion ? undefined : { scale: 0.96 }}
transition={aceternityMotion.spring.dock}
className={cn(
"group relative min-w-0 border border-black/10 bg-white/70 text-center outline-none transition-colors hover:border-black/20 hover:bg-black/5 focus-visible:ring-2 dark:border-white/10 dark:bg-white/[.04] dark:hover:border-white/20 dark:hover:bg-white/8",
variant === "node" ? "flex h-12 flex-col items-center justify-center gap-1 rounded-[var(--dock-item-radius)]" : "flex h-9 items-center justify-center gap-1.5 rounded-[var(--dock-item-radius)] px-2",
"group relative min-w-0 overflow-hidden border border-black/10 bg-white/70 text-center outline-none transition-colors hover:border-black/20 hover:bg-black/5 focus-visible:ring-2 dark:border-white/10 dark:bg-white/[.04] dark:hover:border-white/20 dark:hover:bg-white/8",
variant === "node" ? "flex h-[var(--dock-create-node-height)] flex-col items-center rounded-[var(--dock-item-radius)]" : "flex h-9 items-center justify-center gap-1.5 rounded-[var(--dock-item-radius)] px-2",
)}
style={{ color: theme.node.text, "--tw-ring-color": theme.node.muted } as CSSProperties}
title={command.label}
onMouseDown={(event) => event.stopPropagation()}
onClick={command.onClick}
>
<span className="grid size-5 shrink-0 place-items-center opacity-65 transition-opacity group-hover:opacity-100 [&_svg]:size-3.5">{command.icon}</span>
<span className="max-w-full truncate text-[var(--fs-micro)] font-semibold leading-none">{command.label}</span>
{command.badge ? <span className="absolute right-1 top-1 rounded-full border px-1 py-0.5 text-[var(--fs-nano)] font-bold leading-none" style={{ background: theme.toolbar.activeBg, borderColor: theme.toolbar.border, color: theme.node.muted }}>{command.badge}</span> : null}
{variant === "node" ? <span className="flex h-4 w-full shrink-0 items-start justify-end px-1 pt-1">
{command.badge ? <span className="inline-flex max-w-full items-center rounded-full border px-1 py-0.5 text-[var(--fs-nano)] font-bold leading-none" style={{ background: theme.toolbar.activeBg, borderColor: theme.toolbar.border, color: theme.node.muted }}>{command.badge}</span> : null}
</span> : null}
<span className="flex min-h-0 w-full min-w-0 flex-1 flex-col items-center justify-center gap-1 overflow-hidden px-1">
<span className="grid size-5 shrink-0 place-items-center opacity-65 transition-opacity group-hover:opacity-100 [&_svg]:size-3.5">{command.icon}</span>
<span className="block max-w-full truncate whitespace-nowrap text-[var(--fs-micro)] font-semibold leading-none" title={command.label}>{command.label}</span>
</span>
</motion.button>
))}
</div>

View File

@@ -76,7 +76,9 @@ export function CanvasZoomControls({ scale, onScaleChange, onReset, isMiniMapOpe
{
id: "zoom-precision",
label: "精确缩放",
icon: <span className="flex items-baseline text-[var(--fs-micro)] font-bold tabular-nums"><span ref={dockLabelRef}>{Math.round(scale * 100)}</span><span className="ml-px text-[var(--fs-nano)] opacity-55">%</span></span>,
wide: true,
quiet: true,
icon: <span className="inline-flex h-full items-center justify-center whitespace-nowrap text-[var(--fs-caption)] font-semibold leading-none tabular-nums"><span ref={dockLabelRef}>{Math.round(scale * 100)}</span><span className="ml-px text-[var(--fs-micro)] font-medium leading-none opacity-50">%</span></span>,
active: precisionOpen,
onClick: () => setPrecisionOpen((value) => !value),
},

View File

@@ -1,7 +1,7 @@
import { Modal, Switch } from "antd";
import { GripVertical, RotateCcw } from "lucide-react";
import { Reorder, useReducedMotion } from "motion/react";
import { useEffect, useState } from "react";
import { GripVertical, RotateCcw, X } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
import { useEffect, useRef, useState } from "react";
import { canvasThemes } from "@/lib/canvas-theme";
import { defaultToolbarPrefs, getToolbarTools, persistToolbarPrefs, readToolbarPrefs, type ToolbarId, type ToolbarPrefs, type ToolContext, type ToolDefinition } from "@/lib/canvas/tool-registry";
@@ -43,6 +43,10 @@ export function ToolbarSettingsModal({ open, onClose, toolbar }: ToolbarSettings
const reducedMotion = useReducedMotion();
const [items, setItems] = useState<SettingsItem[]>([]);
const [toolbarId, setToolbarId] = useState<ToolbarId>(toolbar);
const draggedItemIdRef = useRef<string | null>(null);
const dragTargetIdRef = useRef<string | null>(null);
const [draggedItemId, setDraggedItemId] = useState<string | null>(null);
const visibleCount = items.filter((item) => item.visible).length;
// 当 modal 打开或 toolbar 变化时,加载工具列表与偏好
useEffect(() => {
@@ -66,9 +70,34 @@ export function ToolbarSettingsModal({ open, onClose, toolbar }: ToolbarSettings
})));
}, [open, toolbar]);
const handleReorder = (newOrder: SettingsItem[]) => {
setItems(newOrder);
persistCurrent(newOrder);
const handleDragStart = (id: string) => {
draggedItemIdRef.current = id;
dragTargetIdRef.current = id;
setDraggedItemId(id);
};
const handleDragEnter = (targetId: string) => {
const sourceId = draggedItemIdRef.current;
if (!sourceId || dragTargetIdRef.current === targetId) return;
dragTargetIdRef.current = targetId;
setItems((current) => {
const sourceIndex = current.findIndex((item) => item.id === sourceId);
const targetIndex = current.findIndex((item) => item.id === targetId);
if (sourceIndex < 0 || targetIndex < 0) return current;
const next = [...current];
const [movedItem] = next.splice(sourceIndex, 1);
next.splice(targetIndex, 0, movedItem);
persistCurrent(next);
return next;
});
};
const handleDragEnd = () => {
draggedItemIdRef.current = null;
dragTargetIdRef.current = null;
setDraggedItemId(null);
};
const handleToggleVisible = (id: string, visible: boolean) => {
@@ -102,54 +131,103 @@ export function ToolbarSettingsModal({ open, onClose, toolbar }: ToolbarSettings
return (
<Modal
className="canvas-toolbar-settings-modal"
open={open}
onCancel={onClose}
footer={null}
title="工具栏设置"
width={360}
closable={false}
width={720}
centered
destroyOnClose
styles={{ body: { padding: 0 } }}
styles={{
container: { padding: 0, background: theme.spatial.elevated, border: 0, boxShadow: "none" },
body: { padding: 0, background: theme.spatial.elevated },
}}
>
<div className="flex items-center justify-between px-4 pb-2 pt-1">
<span className="text-[var(--fs-label)]" style={{ color: theme.node.muted }}>拖拽排序 · 开关控制显隐</span>
<div className="flex items-start justify-between gap-4 px-5 pb-3 pt-5">
<div className="min-w-0">
<h2 className="text-[var(--fs-heading)] font-semibold leading-none">工具栏设置</h2>
<p className="mt-2 text-[var(--fs-caption)] leading-none" style={{ color: theme.node.muted }}>拖动调整顺序,关闭不常用入口</p>
</div>
<button
type="button"
onClick={onClose}
className="grid size-8 shrink-0 place-items-center rounded-[var(--dock-item-radius)] outline-none transition-colors hover:bg-black/5 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:hover:bg-white/8"
style={{ color: theme.node.muted, outlineColor: theme.accent.primary }}
aria-label="关闭工具栏设置"
>
<X className="size-4" />
</button>
</div>
<div className="flex items-center justify-between gap-3 px-5 pb-2 pt-1">
<span className="text-[var(--fs-tiny)] font-medium" style={{ color: theme.node.muted }}>已显示 {visibleCount}/{items.length}</span>
<button
type="button"
onClick={handleReset}
className="inline-flex items-center gap-1 rounded-[var(--dock-item-radius)] px-2 py-1 text-[var(--fs-tiny)] font-semibold transition-colors hover:bg-current/5"
style={{ color: theme.node.muted }}
className="inline-flex h-7 items-center gap-1.5 rounded-[var(--dock-item-radius)] px-2 text-[var(--fs-tiny)] font-medium outline-none transition-colors hover:bg-black/5 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 dark:hover:bg-white/8"
style={{ color: theme.node.muted, outlineColor: theme.accent.primary }}
aria-label="恢复默认工具栏设置"
>
<RotateCcw className="size-3" />
恢复默认
</button>
</div>
<Reorder.Group
axis="y"
values={items}
onReorder={handleReorder}
className="thin-scrollbar max-h-[60vh] space-y-1 overflow-y-auto px-3 pb-4"
>
<div className="grid grid-cols-2 gap-2 px-5 pb-5 pt-2 md:grid-cols-5" aria-label="主工具栏顺序">
{items.map((item) => (
<Reorder.Item
<ToolbarSettingsItem
key={item.id}
value={item}
initial={false}
animate={reducedMotion ? undefined : { opacity: 1 }}
dragTransition={reducedMotion ? undefined : { bounceStiffness: 600, bounceDamping: 40 }}
className="flex items-center gap-2 rounded-[var(--dock-item-radius)] border px-2.5 py-2"
style={{ background: theme.spatial.surface, borderColor: theme.toolbar.border, color: theme.node.text }}
>
<GripVertical className="size-4 shrink-0 cursor-grab opacity-40 active:cursor-grabbing" />
<span className="grid size-5 shrink-0 place-items-center opacity-70 [&_svg]:size-3.5">{item.icon}</span>
<span className="min-w-0 flex-1 truncate text-[var(--fs-label)] font-medium">{item.label}</span>
<Switch size="small" checked={item.visible} onChange={(checked) => handleToggleVisible(item.id, checked)} />
</Reorder.Item>
item={item}
reducedMotion={Boolean(reducedMotion)}
theme={theme}
dragging={draggedItemId === item.id}
onToggleVisible={handleToggleVisible}
onDragStart={handleDragStart}
onDragEnter={handleDragEnter}
onDragEnd={handleDragEnd}
/>
))}
</Reorder.Group>
</div>
</Modal>
);
}
function ToolbarSettingsItem({ item, reducedMotion, theme, dragging, onToggleVisible, onDragStart, onDragEnter, onDragEnd }: { item: SettingsItem; reducedMotion: boolean; theme: (typeof canvasThemes)[keyof typeof canvasThemes]; dragging: boolean; onToggleVisible: (id: string, visible: boolean) => void; onDragStart: (id: string) => void; onDragEnter: (id: string) => void; onDragEnd: () => void }) {
return (
<motion.div
layout={!reducedMotion}
transition={reducedMotion ? { duration: 0 } : undefined}
className={`canvas-toolbar-settings-card flex h-24 min-w-0 flex-col rounded-[var(--r-md)] p-3 ${item.visible ? "" : "is-hidden"} ${dragging ? "is-dragging" : ""}`}
style={{ color: theme.node.text }}
onDragEnter={() => onDragEnter(item.id)}
onDragOver={(event) => event.preventDefault()}
>
<div className="flex items-center justify-between gap-2">
<button
type="button"
draggable
className="grid size-6 touch-none cursor-grab place-items-center rounded-[var(--r-sm)] outline-none opacity-35 transition-opacity hover:opacity-70 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 active:cursor-grabbing"
style={{ color: theme.node.muted, outlineColor: theme.accent.primary }}
onDragStart={(event) => {
event.dataTransfer.effectAllowed = "move";
onDragStart(item.id);
}}
onDragEnd={onDragEnd}
aria-label={`拖动调整${item.label}顺序`}
>
<GripVertical className="size-4" />
</button>
<Switch size="small" checked={item.visible} onChange={(checked) => onToggleVisible(item.id, checked)} aria-label={`${item.visible ? "隐藏" : "显示"}${item.label}`} />
</div>
<div className="canvas-toolbar-settings-card-content mt-auto flex min-w-0 flex-col items-center gap-1">
<span className="grid size-8 shrink-0 place-items-center rounded-[var(--r-md)]" style={{ background: theme.toolbar.itemHover, color: theme.node.muted }}>
<span className="grid size-4 place-items-center [&_svg]:size-4">{item.icon}</span>
</span>
<span className="max-w-full whitespace-nowrap text-[var(--fs-caption)] font-medium leading-5" title={item.label}>{item.label}</span>
</div>
</motion.div>
);
}
function resolveLabel(tool: ToolDefinition, ctx: ToolContext): string {
return typeof tool.label === "function" ? tool.label(ctx) : tool.label;
}

View File

@@ -10,6 +10,8 @@ export type FloatingDockCommand = {
label: string;
displayLabel?: string;
icon: ReactNode;
wide?: boolean;
quiet?: boolean;
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
active?: boolean;
disabled?: boolean;
@@ -159,6 +161,7 @@ function DockCommandButton({ command, mouseX, metrics, motionEnabled, compact, s
const iconTarget = useTransform(distance, (value) => proximitySize(value, metrics.icon, metrics.iconMagnified, metrics.distance, motionEnabled));
const itemSize = useSpring(itemTarget, aceternityMotion.spring.dock);
const iconSize = useSpring(iconTarget, aceternityMotion.spring.dock);
// 鼠标点击产生的 focus 不能阻塞提示收起,只有键盘可见焦点才持续显示提示。
const showTooltip = !showLabel && (hovered || focused) && !command.disabled;
if (showLabel) {
@@ -187,7 +190,7 @@ function DockCommandButton({ command, mouseX, metrics, motionEnabled, compact, s
}
return (
<motion.span ref={ref} className="relative block shrink-0" style={{ width: itemSize, height: itemSize }}>
<motion.span ref={ref} className={cn("relative block shrink-0", command.wide && "min-w-[var(--dock-precision-width)]")} style={{ width: itemSize, height: itemSize }}>
{/* 放大项留在 Flex 流内,由布局推开邻项,保持 Aceternity Floating Dock 的空间关系。 */}
<motion.button
type="button"
@@ -195,16 +198,17 @@ function DockCommandButton({ command, mouseX, metrics, motionEnabled, compact, s
aria-expanded={command.expands ? command.active || undefined : undefined}
aria-pressed={command.expands ? undefined : command.active || undefined}
disabled={command.disabled}
className={cn("aceternity-dock-command group relative grid size-full place-items-center rounded-full border outline-none", command.active && "is-active", command.danger && "is-danger")}
className={cn("aceternity-dock-command group relative grid size-full place-items-center rounded-full border outline-none", command.quiet && "is-quiet", command.active && "is-active", command.danger && "is-danger")}
whileTap={motionEnabled && !command.disabled ? { scale: 0.92 } : undefined}
transition={aceternityMotion.spring.dock}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
onFocus={() => setFocused(true)}
onFocus={(event) => setFocused(event.currentTarget.matches(":focus-visible"))}
onBlur={() => setFocused(false)}
onMouseDown={() => setFocused(false)}
onClick={command.onClick}
>
<motion.span className="grid place-items-center" style={{ width: iconSize, height: iconSize }}>
<motion.span className={cn("grid place-items-center", command.wide && "w-full")} style={command.wide ? { height: iconSize } : { width: iconSize, height: iconSize }}>
{command.icon}
</motion.span>
<AnimatePresence>
@@ -212,7 +216,7 @@ function DockCommandButton({ command, mouseX, metrics, motionEnabled, compact, s
<motion.span
initial={{ opacity: 0, y: 7, scale: 0.94 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 4, scale: 0.96 }}
exit={{ opacity: 0, y: 4, scale: 0.96, transition: { duration: 0 } }}
transition={{ duration: aceternityMotion.duration.instant, ease: aceternityMotion.easing.enter }}
className={cn("aceternity-dock-tooltip pointer-events-none absolute left-1/2 z-[140] -translate-x-1/2 whitespace-nowrap border font-medium shadow-xl backdrop-blur-xl", compact ? "-top-7 rounded-md px-1.5 py-0.5 text-[var(--fs-micro)]" : "-top-8 rounded-md px-2 py-1 text-[var(--fs-tiny)]")}
>

View File

@@ -294,29 +294,26 @@ function CanvasWorkspaceModeSwitch({ mode, onChange }: { mode: CanvasWorkspaceMo
<div ref={rootRef} className="aceternity-mode-switch pointer-events-auto absolute left-1/2 top-2 z-[var(--dock-z-popover)] -translate-x-1/2">
<motion.button
type="button"
whileHover={reducedMotion ? undefined : { y: -1, scale: 1.015 }}
whileHover={reducedMotion ? undefined : { y: -1 }}
whileTap={reducedMotion ? undefined : { scale: 0.97 }}
transition={aceternityMotion.spring.dock}
className="flex h-8 min-w-[112px] items-center gap-1.5 rounded-full border px-2 text-left outline-none backdrop-blur-2xl focus-visible:ring-2"
style={{ background: theme.spatial.elevated, borderColor: open ? theme.spatial.glowStrong : theme.toolbar.border, color: theme.node.text, boxShadow: `0 16px 44px ${theme.spatial.shadow}` }}
className="flex h-9 min-w-28 items-center gap-2 rounded-full px-2.5 text-left outline-none backdrop-blur-2xl focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
style={{ background: open ? theme.spatial.surface : theme.spatial.elevated, color: theme.node.text, boxShadow: "var(--workspace-overlay-shadow)", outlineColor: theme.accent.primary }}
aria-haspopup="listbox"
aria-expanded={open}
aria-label={`当前为${simple ? "简洁" : "专业"}模式,点击切换`}
onClick={() => setOpen((value) => !value)}
>
<span className="grid size-6 shrink-0 place-items-center rounded-full border" style={{ background: theme.spatial.surface, borderColor: theme.toolbar.border, color: theme.accent.primary }}>
<span className="grid size-6 shrink-0 place-items-center rounded-full" style={{ background: theme.toolbar.itemHover, color: theme.accent.primary }}>
{simple ? <Sparkles className="size-3" /> : <Settings2 className="size-3" />}
</span>
<span className="min-w-0 flex-1">
<span className="block text-[var(--fs-micro)] leading-none" style={{ color: theme.node.muted }}>工作空间</span>
<span className="mt-0.5 block text-[var(--fs-tiny)] font-semibold leading-none">{simple ? "简洁模式" : "专业模式"}</span>
</span>
<motion.span animate={{ rotate: open ? 180 : 0 }} transition={reducedMotion ? { duration: 0 } : aceternityMotion.spring.dock} className="grid size-5 place-items-center rounded-full" style={{ background: theme.toolbar.itemHover }}>
<ChevronDown className="size-2.5" />
<span className="min-w-0 flex-1 whitespace-nowrap text-[var(--fs-caption)] font-semibold leading-none">{simple ? "简洁模式" : "专业模式"}</span>
<motion.span animate={{ rotate: open ? 180 : 0 }} transition={reducedMotion ? { duration: 0 } : aceternityMotion.spring.dock} className="grid size-4 place-items-center" style={{ color: theme.node.muted }}>
<ChevronDown className="size-3" />
</motion.span>
</motion.button>
<div className="absolute left-1/2 top-[38px] w-[236px] -translate-x-1/2">
<div className="absolute left-1/2 top-11 w-72 -translate-x-1/2" style={{ maxWidth: "calc(100vw - var(--space-8))" }}>
<AnimatePresence>
{open ? (
<motion.div
@@ -326,10 +323,9 @@ function CanvasWorkspaceModeSwitch({ mode, onChange }: { mode: CanvasWorkspaceMo
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={reducedMotion ? { opacity: 0 } : { opacity: 0, y: -6, scale: 0.95 }}
transition={aceternityMotion.spring.panel}
className="aceternity-floating-panel w-full overflow-hidden rounded-[var(--panel-radius)] border p-1.5 backdrop-blur-2xl"
style={{ background: theme.spatial.elevated, borderColor: theme.toolbar.border, color: theme.node.text, boxShadow: `0 28px 80px ${theme.spatial.shadow}` }}
className="aceternity-floating-panel w-full overflow-hidden rounded-[var(--r-lg)] p-1.5 backdrop-blur-2xl"
style={{ background: theme.spatial.elevated, color: theme.node.text, boxShadow: "var(--workspace-overlay-shadow)" }}
>
<div className="absolute inset-x-10 top-0 h-px" style={{ background: `linear-gradient(90deg, transparent, ${theme.spatial.glowStrong}, transparent)` }} />
<ModeOption active={simple} motionEnabled={!reducedMotion} icon={<Sparkles className="size-4" />} title="简洁模式" description="保留核心创作路径,降低参数密度" theme={theme} onClick={() => selectMode("simple")} />
<ModeOption active={!simple} motionEnabled={!reducedMotion} icon={<Settings2 className="size-4" />} title="专业模式" description="显示完整节点、导演台与生成控制" theme={theme} onClick={() => selectMode("professional")} />
</motion.div>
@@ -348,16 +344,15 @@ function ModeOption({ active, motionEnabled, icon, title, description, theme, on
type="button"
role="option"
aria-selected={active}
whileHover={motionEnabled ? { x: 3 } : undefined}
whileTap={motionEnabled ? { scale: 0.98 } : undefined}
transition={aceternityMotion.spring.dock}
className="group flex min-h-11 w-full items-center gap-2 rounded-[var(--r-lg)] border px-2 py-1.5 text-left outline-none focus-visible:ring-2"
style={{ background: active ? theme.accent.primarySoft : "transparent", borderColor: active ? theme.spatial.glowStrong : "transparent", color: theme.node.text }}
className="group flex min-h-14 w-full items-center gap-3 rounded-[var(--r-md)] px-3 py-2 text-left outline-none transition-colors hover:bg-black/5 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-0 dark:hover:bg-white/8"
style={{ background: active ? theme.accent.primarySoft : "transparent", color: theme.node.text, outlineColor: theme.accent.primary }}
onClick={onClick}
>
<span className="grid size-8 shrink-0 place-items-center rounded-[var(--dock-item-radius)] border [&_svg]:size-3.5" style={{ background: theme.spatial.surface, borderColor: theme.toolbar.border, color: active ? theme.accent.primary : theme.node.muted }}>{icon}</span>
<span className="min-w-0 flex-1"><span className="block text-[var(--fs-tiny)] font-semibold">{title}</span><span className="mt-0.5 block text-[var(--fs-micro)]" style={{ color: theme.node.muted }}>{description}</span></span>
<span className="grid size-5 shrink-0 place-items-center rounded-full border transition-opacity" style={{ background: active ? theme.accent.primary : theme.spatial.surface, borderColor: active ? theme.accent.primary : theme.toolbar.border, color: active ? "white" : theme.node.muted, opacity: active ? 1 : 0.28 }}><Check className="size-3" /></span>
<span className="grid size-8 shrink-0 place-items-center rounded-[var(--dock-item-radius)] [&_svg]:size-3.5" style={{ background: theme.spatial.surface, color: active ? theme.accent.primary : theme.node.muted }}>{icon}</span>
<span className="min-w-0 flex-1"><span className="block text-[var(--fs-body)] font-semibold leading-none">{title}</span><span className="mt-1.5 block text-[var(--fs-caption)] leading-5" style={{ color: theme.node.muted }}>{description}</span></span>
<span className="grid size-5 shrink-0 place-items-center" style={{ color: theme.accent.primary, opacity: active ? 1 : 0 }}><Check className="size-3.5" /></span>
</motion.button>
);
}

View File

@@ -485,6 +485,8 @@
--dock-shadow: var(--shadow-lg);
--dock-item-radius: var(--r-md); /* 8px */
--dock-item-radius-labeled: var(--r-lg); /* 12px */
--dock-precision-width: var(--space-12);
--dock-create-node-height: calc(var(--space-12) + var(--space-2));
--dock-z-popover: 30; /* dock 子面板在 toolbar 根 stacking context 内的层级(见 ADR-001)*/
/* Node 组件(画布节点,对应 #97 节点视觉规范)*/
@@ -3387,6 +3389,52 @@
backdrop-filter: blur(var(--space-2));
}
.canvas-toolbar-settings-modal {
max-width: calc(100vw - var(--space-8));
}
.canvas-toolbar-settings-modal .ant-modal-container,
.canvas-toolbar-settings-modal .ant-modal-content {
overflow: hidden;
border: 0 !important;
border-radius: var(--r-xl);
padding: 0 !important;
background: var(--cn-spatial-elevated) !important;
}
.canvas-toolbar-settings-modal .ant-modal-content {
box-shadow: var(--workspace-overlay-shadow) !important;
}
.canvas-toolbar-settings-modal .ant-modal-container {
box-shadow: none !important;
}
.canvas-toolbar-settings-modal .ant-modal-body {
padding: 0 !important;
}
.canvas-toolbar-settings-card {
background: var(--cn-spatial-surface);
transition: background-color var(--motion-dur-fast) var(--motion-ease-out), opacity var(--motion-dur-fast) var(--motion-ease-out);
}
.canvas-toolbar-settings-card:hover {
background: color-mix(in srgb, var(--cn-spatial-surface) 72%, var(--cn-spatial-elevated));
}
.canvas-toolbar-settings-card.is-hidden {
background: color-mix(in srgb, var(--cn-spatial-surface) 52%, transparent);
}
.canvas-toolbar-settings-card.is-hidden .canvas-toolbar-settings-card-content {
opacity: var(--opa-subtle);
}
.canvas-toolbar-settings-card.is-dragging {
opacity: var(--opa-muted);
}
:where(.ant-select-dropdown, .ant-dropdown-menu, .ant-popover-inner) {
border: 1px solid var(--border);
box-shadow: 0 20px 56px rgba(15, 23, 42, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.08) !important;
@@ -3583,6 +3631,19 @@
opacity: 1;
}
.aceternity-dock-command.is-quiet {
border-color: transparent;
background: transparent;
box-shadow: none;
}
.aceternity-dock-command.is-quiet:hover:not(:disabled),
.aceternity-dock-command.is-quiet.is-active {
border-color: transparent;
background: var(--dock-command-hover);
box-shadow: none;
}
.aceternity-dock-command.is-danger {
color: var(--dock-command-danger);
}