diff --git a/docs/component-design-system.md b/docs/component-design-system.md index 5488df5..548b29d 100644 --- a/docs/component-design-system.md +++ b/docs/component-design-system.md @@ -4,19 +4,36 @@ ## 1. 视觉主题 -方向为浅色专业调度台。界面保持高信息密度,但使用稳定的标题、状态、操作、证据顺序,减少连续嵌套的彩色卡片。视觉参考取 Linear 的状态聚焦、Grafana 的数据对齐,以及 ArcGIS Experience Builder 的地图优先侧栏。 +方向为宝石蓝操作、银灰亚克力、少量香槟重点的浅色专业调度台。界面保持高信息密度,使用稳定的标题、状态、操作、证据顺序。沿用既有字体、布局、圆角和 acrylic 材质,以透底、色调和轻微背景色差区分导航、面板、分组与阅读区。 ## 2. 色彩角色 -- `--surface-reading`:主要阅读面。 -- `slate-50`:同一业务区内的次级数据面。 -- `--action-blue`:主操作、焦点和调查任务状态。 +采用同色相色阶与低彩度互补色规则。宝石蓝 `#4263C7` 转换为 OKLCH 后约为 `L=0.529317, C=0.160186, h=267.059148°`;中性材质和文字保持该色相,香槟辅助色使用 `(h + 180°) mod 360°`。色阶先计算后转换为 sRGB HEX,均处于 sRGB 色域。透明度不参与色板生成。 + +| 角色 | L | C | h | HEX | +|---|---:|---:|---:|---| +| 宝石蓝主色 | 0.529317 | 0.160186 | 267.06° | `#4263C7` | +| 银霜材质 | 0.935 | 0.012 | 267.06° | `#E6E9F2` | +| 冷瓷阅读面 | 0.975 | 0.005 | 267.06° | `#F5F7FA` | +| 香槟辅助色 | 0.820 | 0.065 | 87.06° | `#D6C294` | +| 深墨文字 | 0.280 | 0.025 | 267.06° | `#232935` | + +完整色板见 [宝石蓝配色](gem-blue-palette.svg)。 + +- `--surface-navigation`:宝石蓝 8% 与银霜在 OKLab 空间混合的导航色调。浅色混色使用 OKLab,避免低彩度颜色在色相插值时偏色。 +- `--surface-panel`:银霜色调;`--surface-well` 为其半透明次级面。 +- `--surface-subtle`:`L=0.950, C=0.010, h=267.06°`,得到 `#EBEEF5`,供桌面 dock、分组和普通控件使用。 +- `--surface-reading`:冷瓷正文、表单和报告阅读面,保持较高遮蔽度。 +- `--action-selection-soft`:`L=0.920, C=0.035, h=267.06°`,得到 `#DAE5FD`,文字使用深宝石蓝。 +- `--action-blue`:主操作 `#4263C7`;hover 将 L 降低 0.055、C 乘 0.95,得到 `#3653B0`;pressed 将 L 降低 0.105、C 乘 0.90,得到 `#2B469C`。 +- `--surface-border` / `--surface-divider`:保持同色相,分别使用 `(L=0.815, C=0.015)` 和 `(L=0.885, C=0.012)`,得到银灰边界 `#BEC2CD` / `#D5D9E1`。 +- `--action-focus-ring`:深宝石蓝焦点环;`--ink-muted`:`L=0.450, C=0.030, h=267.06°`,得到辅助文字 `#4E5566`。 - `cyan-700`:外围样板入口及其来源标识。 - `emerald`:已完成、已通过。 -- `amber`:需要确认、暂停、结果未知。 +- `--status-warning-soft`:香槟色 25% 与冷瓷混合的确认、暂停和结果未知提示底色;同互补色相的文字使用 `(L=0.430, C=0.070)`,得到 `#604D1D`;状态标记使用 `(L=0.600, C=0.090)`,得到 `#977D3C`。 - `rose`:失败、冲突或破坏性操作。 -彩色区域只表达语义。普通分组使用白色阅读面、灰色边界和轻微背景层级。 +宝石蓝用于操作和选中状态,银灰用于材质层级。香槟色只用于确认、警告与 Agent 欢迎标记的小面积点缀,不作为普通面板的整块底色。成功、失败、地图业务图层和数据曲线保留各自语义色。 ## 3. 排版规则 @@ -36,7 +53,18 @@ ## 6. 层级与深度 -侧栏使用既有 acrylic 容器。一级业务区使用不透明阅读面和 1px 边界。区内次级数据使用背景色差,不叠加投影。弹层可使用较强投影,普通卡片只保留极轻的 1px 投影。 +保持既有亚克力半透明材质,在透底、模糊、噪点和边缘高光上调整色调。桌面 Agent dock 延续既有实底布局,使用银灰色调。 + +| 材质 | 浅色底图 alpha | 影像底图 alpha | +|---|---:|---:| +| 导航 | 0.64 | 0.80 | +| 浮动业务面板 | 0.56 | 0.72 | +| 地图控件 | 0.74 | 0.86 | +| Agent 移动抽屉和历史 | 0.82 | 0.88 | +| Agent 浮动输入区和运行摘要 | 0.86 | 0.90 | +| 正文阅读面 | 0.96 | 0.985 | + +分组标题及次级数据使用银灰色差,不叠加投影。模糊仍为面板 24px、控件及 Agent 浮层 18px。地图和影像中的底色应能透过外层材质;报告正文保留更高遮蔽度,保证阅读。 ## 7. 使用约束 @@ -53,6 +81,12 @@ 支持 1280px 桌面和 320px、375px 窄屏。所有交互控件保留可见焦点,触控目标至少 40px。弹层受视口高度和宽度限制。hover 仅在支持 hover 的设备启用,按压使用轻微缩放,并尊重 reduced motion。 +表单字段使用共享 `.form-field` 规则:聚焦时只改变原有 1px 边框的颜色,不叠加外环或浏览器默认轮廓。组合输入由最外层字段容器承担焦点边界,内部输入保持无边框;无边框的亚克力输入区使用 1px 内描边,保持材质与布局不变。无效字段保留错误色,按钮与选择控件保留单独的键盘焦点提示。全局焦点样式仅作为基础层兜底,不能覆盖组件自身规则。 + +底图选择卡片的选中态使用 1px 主题边框和勾选标记,不附加常驻外环。键盘聚焦使用共享的 1px 内描边;鼠标选中、聚焦与按压均保持卡片尺寸及半透明表面不变。图层面板、独立底图面板与浮动底图控件使用同一规则。 + +普通文字与实际背景对比度至少 4.5:1,焦点和必要的非文字状态标记至少 3:1。透明区域按合成背景计算。不支持模糊或降低透明度时保留不透明的银霜、冷瓷层级;强制颜色模式使用系统颜色。主题由现有 Tailwind 与 CSS 变量统一提供,不新增切换入口。 + ## 9. 交互提示 任务面板先显示任务状态和下一步,再提供计划、目标、运行与证据。运行暂停时突出检查点校验或恢复中的一个主操作。外围面板先显示连接状态、历史回放和待提交来源,测试资源与原始请求回执默认按需展开。 diff --git a/docs/gem-blue-palette.svg b/docs/gem-blue-palette.svg new file mode 100644 index 0000000..f27e422 --- /dev/null +++ b/docs/gem-blue-palette.svg @@ -0,0 +1,27 @@ + + + +宝石蓝 · 银灰亚克力 +同色相色阶 + 低彩度互补色 · 色板为不透明基准,亚克力透明度独立设置 + +#4263C7 +宝石蓝 · 主操作 · 品牌 +L 0.529 C 0.160 h 267.06 + +#E6E9F2 +银霜 · 亚克力材质 +L 0.935 C 0.012 h 267.06 + +#F5F7FA +冷瓷 · 阅读 · 表单 +L 0.975 C 0.005 h 267.06 + +#D6C294 +香槟 · 互补暖色点缀 +L 0.820 C 0.065 h 87.06 + +#232935 +深墨 · 标题 · 正文 +L 0.280 C 0.025 h 267.06 +主色 h = 267.06°;同色色阶保持 h;香槟色 h = (267.06° + 180°) mod 360° = 87.06° + diff --git a/src/app/app.e2e.ts b/src/app/app.e2e.ts index 88640dc..88f5bc9 100644 --- a/src/app/app.e2e.ts +++ b/src/app/app.e2e.ts @@ -1,3 +1,4 @@ +import { readThemeColor } from "../../tests/browser/support/theme-colors"; import { expect, test } from "@playwright/test"; import { mockAgentApi } from "../../tests/browser/support/mock-agent-api"; import { mockWorkbenchContext } from "../../tests/browser/support/mock-workbench-context"; @@ -137,7 +138,7 @@ test("keeps menu origin and the solid Main Frame styling through Tailwind v4", a }; }); expect(navigationMaterial.backdropFilter).toBe("none"); - expect(navigationMaterial.backgroundColor).toBe("oklch(0.965 0.012 248)"); + expect(navigationMaterial.backgroundColor).toBe(await readThemeColor(navigation, "--agent-dock-surface")); await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "default"); await expect(page.getByRole("toolbar", { name: "工作区窗口任务栏" })).toHaveCount(0); }); diff --git a/src/features/agent/components/agent-collapsed-rail.tsx b/src/features/agent/components/agent-collapsed-rail.tsx index a88dc43..660a6c1 100644 --- a/src/features/agent/components/agent-collapsed-rail.tsx +++ b/src/features/agent/components/agent-collapsed-rail.tsx @@ -28,7 +28,7 @@ export function AgentCollapsedRail({ aria-label={expandLabel} title={expandLabel} onClick={onExpand} - className="agent-rail-item surface-control group relative flex h-14 w-full items-center justify-center rounded-xl border hover:bg-white" + className="agent-rail-item surface-control group relative flex h-14 w-full items-center justify-center rounded-xl border hover:bg-[var(--surface-reading)]" > diff --git a/src/features/agent/components/agent-command-panel.tsx b/src/features/agent/components/agent-command-panel.tsx index ed1ee18..4c9e1eb 100644 --- a/src/features/agent/components/agent-command-panel.tsx +++ b/src/features/agent/components/agent-command-panel.tsx @@ -373,7 +373,7 @@ export function AgentCommandPanel({
-

+

对话主题

{!hasConversation ? ( @@ -608,7 +608,7 @@ export function AgentCommandPanel({ key={suggestion} suggestion={suggestion} onClick={submitPrompt} - className="surface-reading max-w-full border border-slate-200 text-slate-600 hover:bg-blue-50 hover:text-blue-700" + className="surface-reading max-w-full border border-[var(--surface-divider)] text-slate-600 hover:bg-[var(--action-soft)] hover:text-[var(--action-blue-hover)]" > {suggestion} @@ -617,7 +617,7 @@ export function AgentCommandPanel({
) : null} { const nextPrompt = message.text.trim(); if (nextPrompt) { @@ -638,7 +638,7 @@ export function AgentCommandPanel({
- {!qaOnly ? : 任务协作问答} + {!qaOnly ? : 任务协作问答}
@@ -842,28 +842,28 @@ function ApprovalModeSelect({ sideOffset={8} className="agent-panel-overlay agent-panel-control w-[220px] rounded-xl p-2 shadow-lg" > - + 权限批准模式 onValueChange?.("request")} > - 始终允许 - 自动允许本轮权限请求 + 自动允许本轮权限请求 {!requestApproval ? ( 模型 + 模型 ); } @@ -1004,7 +1004,7 @@ function AgentModelSelect({ type="button" variant="outline" size="sm" - className="w-12 shrink-0 justify-between gap-1 border-transparent bg-transparent px-1 text-xs text-slate-600 shadow-none hover:bg-slate-50 sm:w-24 sm:gap-1.5 sm:px-1.5" + className="w-12 shrink-0 justify-between gap-1 border-transparent bg-transparent px-1 text-xs text-slate-600 shadow-none hover:bg-[var(--surface-reading)] sm:w-24 sm:gap-1.5 sm:px-1.5" disabled={!onValueChange} aria-label="选择 Agent 模型" aria-description={`当前模型:${selectedModel?.label ?? "模型"}`} @@ -1024,7 +1024,7 @@ function AgentModelSelect({ sideOffset={8} className="agent-panel-overlay agent-panel-control w-[230px] rounded-xl p-2 shadow-lg" > - + 模型选择
@@ -1034,10 +1034,10 @@ function AgentModelSelect({ onValueChange?.(model.id)} > @@ -1048,12 +1048,12 @@ function AgentModelSelect({ {model.label} - + {model.description} {selected ? ( - ); @@ -1099,7 +1099,7 @@ function ExpertModelIcon({ size }: { size: number }) { height={size} viewBox="0 0 24 24" fill="none" - className="text-violet-600" + className="text-[var(--action-blue-hover)]" aria-hidden="true" > ); @@ -1200,8 +1200,8 @@ function AgentEmptyState({ compact = false }: { compact?: boolean }) { key={label} className={cn( "agent-capability flex min-h-20 items-center gap-3 px-3.5 py-3 text-left", - index % 2 === 0 && "border-r border-slate-300/80", - index < 2 && "border-b border-slate-300/80" + index % 2 === 0 && "border-r border-[var(--surface-border)]/80", + index < 2 && "border-b border-[var(--surface-border)]/80" )} > @@ -1312,7 +1312,7 @@ function BackendMessageList({

{message.content}

) ) : streaming && index === messages.length - 1 ? ( - 正在生成... + 正在生成... ) : null} {message.role === "assistant" && !qaOnly ? (