# dsh-opendesign-usage —— UI 设计规范 > 本文规范本插件**浏览器侧**的外观与交互。它是 > [`dsh-plugin-standards/docs/UI-STANDARDS.md`](~/Projects/dsh-plugin-standards/docs/UI-STANDARDS.md) > 在本项目的落地版本,并保留本项目实测得到的补充结论。 > > ⚠️ **本文在 2026-10-16 被整体重写过。** 旧版本的核心结论 > 「要统一外观就把官方原语的 markup / CSS 抄进插件,不要 `require` 官方包」 > **已被推翻** —— 见 §0「旧结论错在哪」。 --- ## 0. 首要原则:用 `dsh-client-ui-primitives`,不要自己写 **DSH 原生组件的唯一共享入口是 `@deepseek-ai/dsh-client-ui-primitives`。** 点击菜单、下拉选择、开关、输入框、弹窗、Tooltip、折叠行、状态点、tabs —— 一律从它取。 官方 README 原文: > 「在功能包里写控件之前,先查这张表。**插件无法导入另一个插件的组件,因此本包是控件 > 唯一可以共享的地方**:合适的就复用,有意的视觉差异提升成 prop,而不是另起一份拷贝。」 ### 为什么(内核事实,不是风格偏好) 1. **它是冻结基座模块,`require` 必定成功。** 前端 boot 的 seed 表(`dsh-web-frontend` 的 `index-*.js` 里 `function gM()`): ```js { react, "react/jsx-runtime", "react-dom", "react-dom/client", "@deepseek-ai/cordis", "@deepseek-ai/dsh-client-store", "@deepseek-ai/dsh-client-ui-slots", "@deepseek-ai/dsh-client-ui-primitives", // ← 本包 "@deepseek-ai/dsh-client-ui-dockkit" } ``` 它被传给 `__ModuleLoader__.create({ staticModules: gM() })`。 **这 9 个之外的 `@deepseek-ai/*` 一律不可 require。** 2. **官方自己就是这么做的** —— 内核里 10 个官方 `dsh-client-ui-settings-*` 有 **9 个** `require` 了它(唯一的例外 `settings-plugins` 是纯壳、不渲染控件)。 把内核里所有官方 `dsh-client-*` 插件的 require 去重后,**没有一个落在 seed 表 9 个之外**。 3. **自己抄的组件必然与宿主漂移。** primitives 消费 `--dsw-*` 设计 token,并处理了 `focusWithoutRing` / `useModalLayer` / `observeComposition`(IME)等细节 —— 抄一份等于把这些坑重新踩一遍。 ### ⚠️ 旧结论错在哪(必须记住,别重蹈) 旧版本引用官方 `references/practices.md`「UI」节的原文: > *"Do not `require('@deepseek-ai/dsh-client-ui-primitives')` or load any other Harness > Client package as a module … a throwing component blanks your slot entry."* 并据此定下「抄不要 require」。**那是把白屏事故归错了因。** - 该原文是**面向独立第三方包的跨版本风险警告**(宿主升级后 props 可能变), 不是"技术上不可行"。 - 本项目两次白屏的真实根因是**漏声明 `inject` 服务名** (`cannot get property "X" without inject` → fiber FAILED → web boot 白屏), 与 `require` primitives **无关**。详见 [`docs/plugin-development.md`](plugin-development.md) 的 inject 与启动机制两节。 - 结论:**用 primitives**。唯一的红线是 **seed 表 9 模块白名单**(构建期强制)。 --- ## 1. 可用组件一览 来自 primitives 官方 README 的「组件目录」表 —— **该表是 props 的权威来源,用前先查**。 | 类别 | 导出 | |---|---| | 操作 | `Button`(`variant`: primary/ghost/outline/toolbar)、`InlineEditor` | | 选择 | `Switch`(36×20 双态,**`label` 必填**)、`Checkbox`、`SegmentedControl`、`SegmentedTabs` | | 输入 | `Input`、`SettingsForm`、`SettingsFormModel`、`SettingsValueField`、`SettingsSecretField` | | 菜单浮层 | `Menu`、`MenuItemButton`、`MenuGroup`、`MenuSurface`、`Tooltip`、`HoverCard`、`Modal`、`RiskConfirmation` | | 几何 | `useAnchoredPosition`、`useAnchoredMaxHeight`、`useDismissOnOutsidePointer` | | 展示 | `Pill`、`Tag`、`PathLabel`、`StateDot`、`ConnectionIndicator`、`DisclosureRow`、`TextShimmer` | | 内容 | `JsonTree`、`JsonBlock`、`MarkdownText`、`CodeBlock`、`CommandText`、`TerminalBlock` | | 图标 | `icons/*`(Regular/Medium 两种尺寸)、`PermissionIcon*`、`PluginArtwork*`、`GuideArtwork*`、`FileTypeIcon` | | 其他 | `ImageLightbox`、`Toast`、`ShortcutKeys`、`writeClipboard` | 本插件实际用到的: | 位置 | 组件 | |---|---| | 输入框读数(折叠态) | `Button`(圆钮)+ 内部 `span` 结构 | | 读数展开面板 | `MenuSurface`(材质)+ `useAnchoredPosition`(定位)+ `useDismissOnOutsidePointer`(收起) | | 插件详情页设置卡片 | `SettingsForm` + `SettingsFormModel` + `SettingsValueField` + `SettingsSecretField` + `Switch` | > **读数不要用 `Pill`**:`Pill` 是可选中胶囊(视图切换/筛选器语义), > 而读数是一个"点开看明细"的操作,`Button` 语义正确。 ### 不要套 `Menu` 装只读信息 `Menu` 的每一行都是 `