-
拖拽排序 · 开关控制显隐
+
+
+
工具栏设置
+
拖动调整顺序,关闭不常用入口
+
+
+
+
+ 已显示 {visibleCount}/{items.length}
-
+
{items.map((item) => (
-
-
- {item.icon}
- {item.label}
- handleToggleVisible(item.id, checked)} />
-
+ item={item}
+ reducedMotion={Boolean(reducedMotion)}
+ theme={theme}
+ dragging={draggedItemId === item.id}
+ onToggleVisible={handleToggleVisible}
+ onDragStart={handleDragStart}
+ onDragEnter={handleDragEnter}
+ onDragEnd={handleDragEnd}
+ />
))}
-
+
);
}
+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 (
+ onDragEnter(item.id)}
+ onDragOver={(event) => event.preventDefault()}
+ >
+
+
+ onToggleVisible(item.id, checked)} aria-label={`${item.visible ? "隐藏" : "显示"}${item.label}`} />
+
+
+
+ {item.icon}
+
+ {item.label}
+
+
+ );
+}
+
function resolveLabel(tool: ToolDefinition, ctx: ToolContext): string {
return typeof tool.label === "function" ? tool.label(ctx) : tool.label;
}
diff --git a/web/src/components/ui/aceternity/floating-dock.tsx b/web/src/components/ui/aceternity/floating-dock.tsx
index f6442b4af..f16df275a 100644
--- a/web/src/components/ui/aceternity/floating-dock.tsx
+++ b/web/src/components/ui/aceternity/floating-dock.tsx
@@ -10,6 +10,8 @@ export type FloatingDockCommand = {
label: string;
displayLabel?: string;
icon: ReactNode;
+ wide?: boolean;
+ quiet?: boolean;
onClick?: (event: MouseEvent) => 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 (
-
+
{/* 放大项留在 Flex 流内,由布局推开邻项,保持 Aceternity Floating Dock 的空间关系。 */}
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}
>
-
+
{command.icon}
@@ -212,7 +216,7 @@ function DockCommandButton({ command, mouseX, metrics, motionEnabled, compact, s
diff --git a/web/src/pages/canvas/canvas-project-top-bar.tsx b/web/src/pages/canvas/canvas-project-top-bar.tsx
index c5da5d6e2..f5d028eec 100644
--- a/web/src/pages/canvas/canvas-project-top-bar.tsx
+++ b/web/src/pages/canvas/canvas-project-top-bar.tsx
@@ -294,29 +294,26 @@ function CanvasWorkspaceModeSwitch({ mode, onChange }: { mode: CanvasWorkspaceMo
setOpen((value) => !value)}
>
-
+
{simple ? : }
-
- 工作空间
- {simple ? "简洁模式" : "专业模式"}
-
-
-
+ {simple ? "简洁模式" : "专业模式"}
+
+
-
+
{open ? (
-
} title="简洁模式" description="保留核心创作路径,降低参数密度" theme={theme} onClick={() => selectMode("simple")} />
} title="专业模式" description="显示完整节点、导演台与生成控制" theme={theme} onClick={() => selectMode("professional")} />
@@ -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}
>
- {icon}
- {title}{description}
-
+ {icon}
+ {title}{description}
+
);
}
diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css
index 17178ea49..b7335fe01 100644
--- a/web/src/styles/globals.css
+++ b/web/src/styles/globals.css
@@ -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);
}