跳到主要内容

新增 Segmented 组件 (WEB-1382)

版本: 0.10.0 · 类型: ✨ 新功能

WEB-1382 · 实现 issue WEB-1501

问题​

设计系统缺少一个「分段控制器」组件,用于在少量互斥选项间切换(如视图模式切换、筛选维度切换),语义上始终保持一项选中,不存在"全部未选中"的中间态。Tabs 会把选中状态绑定到内容面板,并不适合纯值选择场景。

改动文件​

  • packages/design/src/components/Segmented/Segmented.tsx
  • packages/design/src/components/Segmented/styles.ts
  • packages/design/src/components/Segmented/index.ts
  • packages/design/src/components/Segmented/SegmentedDesignSpec.md
  • packages/design/src/components/Segmented/__tests__/Segmented.test.tsx
  • packages/design/src/components/index.ts
  • packages/design-site/docs/components/segmented.mdx
  • packages/design-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/components/segmented.mdx
  • packages/design-site/src/theme/ReactLiveScope/index.tsx

改动内容​

  1. 新增 Segmented 组件,直接基于 @radix-ui/react-toggle-group(type="single")封装,不经过 ui/toggle-group——其默认 toggleVariants 视觉与 Segmented 冲突,复用前需先给整个 toggle 系列补齐 unstyledVisual 支持,不在本次范围。
  2. 声明式 options API,支持裸字符串简写('a' 等价于 { value: 'a', label: 'a' }),通过 getSegmentedItemTokenClass 覆盖三种内容形态(纯文字 / 图标+文字 / 纯图标),图标 wrapper 采用居中布局(inline-flex items-center justify-center),使图标与 Item、同排文字保持垂直居中。纯图标选项在类型层面(判别联合)强制要求同时提供 icon 与 aria-label,避免纯图标按钮上线后缺少可访问名。
  3. 同时支持受控(value)与非受控(defaultValue,缺省取第一项)两种模式,并提供 block(撑满父容器,选项通过 flex-1 等分空间)与整体 / 单项 disabled。
  4. Radix type="single" 在点击已选中项时会以空字符串触发 onValueChange(其原生「清空选中」行为);Segmented 在 handleValueChange 中拦截空值,既不更新内部状态,也不触发外部 onChange。
  5. 选中态使用 Foregrounds-White / Foregrounds-Black,而非 Backgrounds-Primary / Labels-Primary 等语义 token——Foregrounds 系列在明暗主题下均为纯白/纯黑,对应 Figma 中「选中药丸不随主题翻转」的设计意图。
  6. 未选中态文字使用 Labels/Secondary(light #3D3D3D / dark #D6D6D6),而不用视觉上更浅的 Labels/Tertiary(#6B6B6B):Tertiary 实测未选中默认 4.47:1、hover 3.67:1,均低于 WCAG 2.1 AA 要求的 4.5:1;Secondary 实测未选中默认 light 9.11:1 / dark 8.69:1,hover 明暗均 7.47:1,选中态(Foregrounds-Black on Foregrounds-White)明暗均 21:1。

未实现​

  • antd 的 size、shape、vertical 均未实现——Figma 目前没有对应的变体。
  • 容器 padding: 2px 与 radius: 7px 是硬编码 fallback 值,design-tokens 中无匹配 token(只有 Radius_5 / Radius_12 / Radius_20 / Rounded)。