feat:组件框架

This commit is contained in:
董海洋
2026-05-31 09:36:23 +08:00
parent b371a5341a
commit ba3b3ad5c8
483 changed files with 134708 additions and 1627 deletions
+697
View File
@@ -0,0 +1,697 @@
# Pika 组件库结构优化分析
> 基于当前代码库(`src/components` 约 77 个组件目录、80 个组件 TSX、74 个测试文件)的静态分析。
> 生成日期:2026-05-30
---
## 目录
1. [现状概览](#1-现状概览)
2. [目录与命名规范](#2-目录与命名规范)
3. [重复代码与可抽取的公共层](#3-重复代码与可抽取的公共层)
4. [ConfigProvider 与主题系统](#4-configprovider-与主题系统)
5. [CSS 与 Design Token 策略](#5-css-与-design-token-策略)
6. [类型系统](#6-类型系统)
7. [Props 诚实性(接口 vs 实现)](#7-props-诚实性接口-vs-实现)
8. [共享 Hooks 与工具](#8-共享-hooks-与工具)
9. [测试覆盖](#9-测试覆盖)
10. [文档与 DX](#10-文档与-dx)
11. [构建与导出](#11-构建与导出)
12. [优先级路线图](#12-优先级路线图)
13. [附录:文件清单速查](#13-附录文件清单速查)
---
## 1. 现状概览
### 1.1 分层结构(已成型,方向正确)
```
src/
├── components/
│ ├── common/ # 基础通用(Button、ConfigProvider、Icon…)
│ ├── layout/ # 布局(Layout、Flex、Grid、Space…)
│ ├── nav/ # 导航(Menu、Tabs、Breadcrumb…)
│ ├── entry/ # 数据录入(Input、Select、Form…)
│ ├── feedback/ # 反馈(Modal、Message、Alert…)
│ ├── display/ # 数据展示(Table、Card、Tooltip…)
│ └── shared/ # 内部共享(hooks、types、utils
├── theme/ # JS Token 定义
└── global.css # CSS 变量(--nv-*
```
六大分类与 Ant Design 的组件分区思路一致,有利于 AI 生成代码时的语义检索。`shared/` 作为内部基础设施层也已建立。
### 1.2 做得好的地方
| 方面 | 说明 |
|------|------|
| **组件粒度** | 每个组件独立目录,含 `*.tsx` + `*.module.css` + `index.ts` + `*.test.tsx`,结构清晰 |
| **Barrel 导出** | 分类 `index.ts` → 根 `components/index.ts``src/index.ts`,对外 API 集中 |
| **测试基线** | 主组件几乎都有 Vitest + Testing Library 测试(74 个 test 文件) |
| **forwardRef** | 布局/表单类组件普遍支持 ref 透传 |
| **data-* 属性** | 组件用 `data-*` 标记状态,利于测试与 CSS 选择器 |
| **共享 Hooks** | `useClickOutside``useScrollListener` 等已在 overlay 类组件中复用 |
### 1.3 主要问题摘要
| 类别 | 严重度 | 一句话描述 |
|------|--------|-----------|
| 遗留重复路径 | 🔴 高 | `src/components/Button/``common/Button/` 并存 |
| ConfigProvider 未接线 | 🔴 高 | 实现了 context,但业务组件几乎不消费 |
| Token 三套并行 | 🔴 高 | JS tokens / CSS `--nv-*` / 组件 `--_*` 混用 |
| Overlay 重复实现 | 🟠 中 | Tooltip 与 Popover 各 ~360 行,逻辑高度相似 |
| 类型别名泛滥 | 🟠 中 | `PikaSize` 几乎无人用,各组件自建 `*Size` |
| Props 接口超前 | 🟠 中 | 多个 prop 在类型里声明但实现缺失 |
| 文档空白 | 🟠 中 | 80 个组件仅 1 个 `index.md` |
| 构建 subpath 可能无效 | 🟡 低 | `package.json exports "./*"` 与源码结构不匹配 |
---
## 2. 目录与命名规范
### 2.1 文件夹命名两套并存
| 风格 | 示例 | 数量 |
|------|------|------|
| **PascalCase** | `common/Button/``common/ConfigProvider/` | ~10 个 |
| **kebab-case** | `display/avatar/``entry/tree-select/` | ~67 个 |
**建议:** 统一为 **kebab-case 目录 + PascalCase 文件名**,与绝大多数组件一致:
```
display/avatar/Avatar.tsx ✅ 推荐
common/Button/Button.tsx ⚠️ 目录应改为 common/button/
```
迁移成本:仅 `common/` 下 10 个目录,可一次性批量 rename + 更新 import。
### 2.2 导出模式不一致
**现状:**
```ts
// 组件文件 — 普遍 default export
export default Button
// barrel — named export
export { Button } from './Button'
```
**建议:** 组件 TSX 改为 **named export only**,与 barrel 和 tree-shaking 更一致:
```ts
// Button.tsx
export const Button = forwardRef(...)
export type { ButtonProps }
```
### 2.3 复合组件组织方式不统一
| 模式 | 示例 |
|------|------|
| 同目录多文件 | `display/avatar/Avatar.tsx` + `AvatarGroup.tsx` |
| 单目录聚合 | `entry/choice/Checkbox.tsx` + `Radio.tsx` + `Switch.tsx` |
| Object.assign | `Layout.Header``Card.Meta` |
**建议:** 制定简单规则写入 `CONVENTIONS.md`(当前已删除):
- 强关联子组件 → 同目录 + `Object.assign` 或 compound export
- 独立可选组件 → 同目录分文件
- 录入类「形态变体」→ 可聚合(如 choice)
### 2.4 遗留路径:Button 双份
| 路径 | 状态 |
|------|------|
| `src/components/common/Button/` | ✅ 现行源码,根 index 导出 |
| `src/components/Button/` | ⚠️ Git 索引中仍存在,含 `index.md`、测试 |
**行动:** 删除 `src/components/Button/`,文档迁移至 `common/Button/index.md`(或 `docs/components/button.md`)。
---
## 3. 重复代码与可抽取的公共层
### 3.1 Tooltip / Popover — 最高优先级抽取
两者共享:
- `GAP``PLACEMENT_POSITION_MAP``FLIP_MAP` 常量
- Portal 渲染 + 定位计算
- `useScrollListener` + `useClickOutside`
- 显隐状态机
```
shared/overlay/
├── Overlay.tsx # 定位 + portal + 显隐
├── placement.ts # PLACEMENT_MAP, FLIP_MAP
├── useOverlay.ts # open/close/trigger 逻辑
└── Overlay.module.css
```
预估可减少 **~300 行**重复代码,后续 Popconfirm、Dropdown 也可复用。
### 3.2 `getSemantic` 内联重复(22 处)
以下组件各自定义相同模式的 `getSemantic` helper
```
common/Button/Button.tsx
display/badge/Badge.tsx
display/card/Card.tsx
display/table/Table.tsx
display/tooltip/Tooltip.tsx
… 等 22 个文件
```
**建议:** 抽到 `shared/utils/semantic.ts`
```ts
export function getSemanticClass(
styles: Record<string, string>,
semantic?: Record<string, string>,
part: string,
): string | undefined
```
### 3.3 Portal 工具已写但未用
`shared/utils/portal.ts` 提供 `getPortalContainer` / `renderPortal`**零引用**。
Tooltip、Popover、Tour、Modal 均直接 `createPortal(..., document.body)`
**建议:** overlay 重构时统一接入,并读取 ConfigProvider 的 `getPopupContainer`
### 3.4 Table 内联分页 vs nav/Pagination
`display/table/Table.tsx` 内置 `PaginationInline`,功能仅为 prev/next + 页码,不支持:
- `showSizeChanger`
- `showQuickJumper`
- `pageSizeOptions`
**建议:** Table 直接组合 `nav/pagination/Pagination`,或抽取 `shared/PaginationMini`
### 3.5 className 拼接模式重复
大量组件使用:
```ts
[className, styles.root].filter(Boolean).join(' ')
```
**建议:** `shared/utils/classNames.ts`(或引入轻量 `clsx` 依赖):
```ts
export function cn(...parts: (string | false | undefined | null)[]): string
```
---
## 4. ConfigProvider 与主题系统
### 4.1 现状:基础设施已建,消费方缺失
`ConfigProvider` 提供:
| Context 字段 | 是否被组件消费 |
|-------------|---------------|
| `prefixCls` | ❌ CSS 硬编码 `nv-*` |
| `iconPrefixCls` | ❌ Icon 用全局 `.nv-icon` |
| `colorPrimary` / `theme.token` | ⚠️ 仅注入 wrapper inline style |
| `theme.components` | ❌ 接口存在,从未应用 |
| `locale` | ❌ 无组件读取 |
| `zIndex` | ❌ Modal/Drawer 各自硬编码 |
| `getPopupContainer` | ❌ 仅 prop 级,不读 context |
`useConfig()` 仅出现在 `ConfigProvider.tsx` 及其测试中。
### 4.2 建议接线顺序
**Phase 1 — 最小可用全局配置:**
1. 创建 `shared/hooks/usePikaContext.ts`,封装 `useConfig()` + 默认值合并
2. Overlay 组件(Tooltip、Popover、Dropdown、Modal)读取 `getPopupContainer``zIndex`
3. 所有 portal 组件 z-index 从 context 叠加(base + offset
**Phase 2 — 前缀与国际化:**
4. CSS Module 改为 `[data-nv-prefix]` 或 runtime class prefix(成本较高,可延后)
5. `locale` 接入 DatePicker、Pagination、Modal 按钮文案
**Phase 3 — 组件级主题:**
6. 实现 `theme.components.Button` 等 token 覆盖
### 4.3 Token 源统一
当前三套 token 并行:
```
┌─────────────────────────────────────────────────────────┐
│ theme/tokens.ts (JS 对象) │
│ → entry/tokens/index.ts → inline style 注入 │
├─────────────────────────────────────────────────────────┤
│ global.css (:root --nv-*) │
│ → feedback 组件 CSS var fallback │
├─────────────────────────────────────────────────────────┤
│ 组件私有 --_* (TSX inline style → CSS Module) │
│ → Button, Layout, Progress 等 │
└─────────────────────────────────────────────────────────┘
```
**问题:**
- `theme/tokens.ts``color.primary = '#6C5CE7'``global.css` 暗色模式值可能不同步
- Entry 组件 inline style **不响应** ConfigProvider 运行时改色
- `global.css` 首行 `@import './theme/dumi.css'` 把文档站样式耦合进库
**建议目标架构:**
```
theme/
├── tokens.css # 唯一 token 源(--nv-*
├── tokens.ts # 从 CSS 变量读取或生成类型(可选)
├── dark.css
└── compact.css
组件 CSS:只用 var(--nv-*),组件级 --_* 仅用于 prop 驱动的动态值(width、height
ConfigProvider:通过 style 覆盖 --nv-color-primary 等根变量
```
---
## 5. CSS 与 Design Token 策略
### 5.1 当前模式评估
| 模式 | 用途 | 评价 |
|------|------|------|
| `*.module.css` | 组件样式隔离 | ✅ 主流,正确 |
| `--nv-*` + fallback | 设计 token | ✅ 正确方向,但 fallback 硬编码过多 |
| `--_*` inline 注入 | prop 驱动动态值 | ✅ 适合 Layout/Button 的高度、宽度 |
| 全局 `Icon/style.css` | 图标字体 | ⚠️ 应改为 CSS Module 或 CSS-in-JS |
| inline style 色值 | Entry 组件 | ❌ 应迁移到 CSS var |
### 5.2 硬编码色值示例(需逐步清理)
以下文件存在与 token 并行的硬编码:
- `display/popover/Popover.module.css``#fff``rgba(0,0,0,0.85)`
- `display/collapse/Collapse.module.css` — 边框/背景硬编码
- `entry/tokens/index.ts` — 直接从 JS 对象读色,不经过 CSS 变量
### 5.3 CSS 变量命名规范建议
| 层级 | 前缀 | 示例 | 谁设置 |
|------|------|------|--------|
| 全局设计 token | `--nv-` | `--nv-color-primary` | global.css / ConfigProvider |
| 组件内部变量 | `--_{part}-` | `--_height``--_padding` | 组件 TSX inline style |
| 语义 DOM | `data-*` | `data-collapsed``data-size` | 组件 TSX |
避免 `--_*``--nv-*` 语义混淆:前者是**实例级**动态值,后者是**主题级**静态 token。
---
## 6. 类型系统
### 6.1 重复 Size / Status 类型
`shared/types/common.ts` 已定义:
```ts
export type PikaSize = 'small' | 'middle' | 'large'
export type PikaStatus = 'default' | 'success' | 'warning' | 'error' | 'info'
```
**几乎无组件引用**。各模块自建:
| 类型 | 位置 |
|------|------|
| `DataEntrySize` | `entry/common.ts` |
| `ButtonSize` | `common/Button/Button.tsx` |
| `AvatarSizeType` | `display/avatar/Avatar.tsx` |
| `TagSize`, `CardSize`, `TableSize`, `BadgeSize`… | 各 display 组件 |
**建议:**
```ts
// shared/types/common.ts — canonical types
export type PikaSize = 'small' | 'middle' | 'large'
export type PikaStatus = 'default' | 'success' | 'warning' | 'error' | 'info'
// entry/common.ts
import type { PikaSize, PikaStatus } from '../../shared/types'
export type DataEntrySize = PikaSize
export type DataEntryStatus = PikaStatus
// 组件 props
export interface ButtonProps {
size?: PikaSize
}
```
### 6.2 公共 Props 基类利用不足
`entry/common.ts` 定义了良好的基类:
- `DataEntryBaseProps`
- `DataEntryInputProps`
- `DataEntryTextualProps`
- `DataEntrySelectableProps`
**建议:** display / feedback 组件也建立类似基类:
```ts
// shared/types/component.ts
export interface PikaComponentProps {
className?: string
style?: CSSProperties
}
export interface PikaInteractiveProps extends PikaComponentProps {
disabled?: boolean
}
```
### 6.3 `CSSCustomProperties` 使用率低
`shared/types/css.ts` 仅 5 个组件 import,其余用 `Record<string, string>` 或无类型。
**建议:** 统一 cssVars 类型:
```ts
type CSSVars = CSSProperties & Record<`--${string}`, string>
```
---
## 7. Props 诚实性(接口 vs 实现)
> 接口声明了但实现缺失的 prop,会误导 AI 和使用者。应 **实现** 或 **从类型中移除**(或标注 `@deprecated` / 文档说明「即将支持」)。
### 7.1 已确认未实现的 Props
| 组件 | Prop | 现状 |
|------|------|------|
| **Layout.Sider** | `onBreakpoint` | 有 `breakpoint` data 属性,无 resize 监听 |
| **DatePicker** | `mode`, `format` | 固定 date 模式 + `YYYY-MM-DD` |
| **TimePicker** | `format`, `hourStep` | 固定格式与步进 |
| **Slider** | `range` | 仅单值滑块 |
| **TextArea** | `autoSize`, `onResize` | 未实现自动高度 |
| **Upload** | `directory`, `onPreview` | 未实现文件夹上传与预览 |
| **Popconfirm** | `okType` | 确认按钮未区分 danger/primary |
| **TreeSelect** | `treeCheckable` | 解构为 `_treeCheckable` 或未使用 |
| **Form** | `labelCol`, `wrapperCol` | 无栅格布局 |
| **Table** | `filters`, `filteredValue`, `filterMultiple` | Column 类型完整,无 filter UI |
| **Table** | `showSizeChanger`, `showQuickJumper` | PaginationConfig 有类型,内联分页未支持 |
| **Mention** | `showSearch`, `showClear` | 继承自基类但未解构 |
### 7.2 处理策略
```
优先级 A — 高频 APIAnt Design 对标)
→ 补实现:DatePicker.format、Slider.range、Layout.onBreakpoint
优先级 B — 低频 / 复杂
→ 从类型移除,CHANGELOG 记录,待迭代再加
优先级 C — 计划内
→ 保留类型,组件 JSDoc 标注 @experimental
```
### 7.3 类型笔误
`TooltipPlacement``'rightRight'`,疑似应为 `'rightBottom'``Tooltip.tsx`)。`FLIP_MAP` 映射也需核对。
---
## 8. 共享 Hooks 与工具
### 8.1 已有 Hooks 使用情况
| Hook | 路径 | 已使用 | 应使用未使用 |
|------|------|--------|-------------|
| `useClickOutside` | `shared/hooks/` | Select, Dropdown, Tooltip, Popover, Popconfirm, AutoComplete, RangePicker | **Cascader, DatePicker, TimePicker, TreeSelect, Mention** |
| `useScrollListener` | 同上 | Affix, Anchor, BackTop, Tooltip, Popover, Tour | — |
| `useEscapeKey` | 同上 | Modal, Drawer, Tour, Image | — |
| `useMatchMedia` | 同上 | Grid | **Layout.Sider**breakpoint |
### 8.2 重复实现需清理
| 组件 | 问题 | 建议 |
|------|------|------|
| `FloatButton` | 自定义 `document.addEventListener('click')` | 改用 `useClickOutside` |
| `Image` | `useEscapeKey` + 额外 `keydown` 监听 | 合并为单一 hook |
| `Tour` | `useEscapeKey` + 额外 `keydown` 处理方向键 | 扩展 hook 或统一 handler |
### 8.3 建议新增 Hooks
| Hook | 用途 |
|------|------|
| `useControllableState` | 统一 controlled/uncontrolled 模式(大量组件重复 `isControlled` 逻辑) |
| `useBreakpoint` | Layout.Sider、Grid 共用 responsive 逻辑 |
| `useMergedRef` | forwardRef + 内部 ref 合并 |
`useControllableState` 示例 — 当前 Layout、Sider、Modal、Tabs 等均有类似代码:
```ts
const isControlled = value !== undefined
const current = isControlled ? value : internal
```
---
## 9. 测试覆盖
### 9.1 覆盖率概况
- **主组件:** 几乎每个一级目录都有 `*.test.tsx`
- **子组件:** 以下 **7 个** 无独立测试:
| 子组件 | 路径 | 风险 |
|--------|------|------|
| `AvatarGroup` | `display/avatar/` | maxCount、overflow 逻辑 |
| `BadgeRibbon` | `display/badge/` | 定位、颜色 |
| `CardGrid` / `CardMeta` | `display/card/` | 栅格布局 |
| `ImagePreviewGroup` | `display/image/` | 多图预览切换 |
| `StatisticTimer` | `display/statistic/` | 倒计时逻辑 |
| `CheckableTag` | `display/tag/` | 选中态 |
### 9.2 测试质量建议
| 方向 | 说明 |
|------|------|
| **行为测试优先** | 少测 class 名,多测交互(open/close、keyboard、form submit |
| **未实现 prop 不测** | 避免测试「接口存在但行为不存在」造成 false positive |
| **ConfigProvider 集成测试** | 验证 theme/locale 注入后组件行为变化 |
| **a11y 基线** | overlay 组件补充 aria 属性断言 |
### 9.3 重复测试
`src/components/Button/Button.test.tsx``common/Button/Button.test.tsx` 可能重复 — 随遗留目录一并清理。
---
## 10. 文档与 DX
### 10.1 文档现状
| 类型 | 数量 | 路径 |
|------|------|------|
| 组件 co-located `index.md` | **1** | `components/Button/index.md`(遗留) |
| Dumi 指南 | 3 | `docs/index.md`, `getting-started.md`, `components/index.md` |
| 逐组件 API 文档 | **0** | — |
| 设计规范 | 1 | `DESIGN_SPEC.md`(完整) |
| 开发约定 | 0 | `CONVENTIONS.md` 已删除 |
### 10.2 文档建设建议
**短期(可脚本化):**
1. 为每个组件目录生成 `index.md` 模板(frontmatter + 基础 demo + Props 表占位)
2. Dumi 配置 `resolve.atomDirs` 指向 `src/components`
**中期:**
3. 从 TS 类型自动生成 Props 表(dumi-theme-Pika 或 typedoc
4. 每个组件至少 3 个 demo:基础用法、受控模式、禁用/错误态
**长期:**
5. AI 示例库 — 与 `DESIGN_SPEC.md` 的 AI-First 原则对齐,每组件提供「AI 友好」单行示例
### 10.3 开发者体验
| 改进项 | 说明 |
|--------|------|
| 恢复 `CONVENTIONS.md` | 目录命名、export 规范、CSS 约定、测试要求 |
| `npm run lint:fix` | 已有 lint-staged,可加 CI workflow |
| Storybook vs Dumi | 当前 Dumi 足够,保持单文档方案 |
| 组件生成 CLI | `pnpm gen component Button --category common` 脚手架 |
---
## 11. 构建与导出
### 11.1 当前构建链
```
father build → dist/cjs + dist/esm
src/index.ts → export * from './components' + tokens
```
### 11.2 问题
| 问题 | 详情 |
|------|------|
| **Subpath exports 可能无效** | `package.json` 声明 `"./*" → dist/esm/*/index.js`,但源码无对应分包 |
| **CSS 未作为包入口** | 消费者需自行引入 token CSS;`global.css` 未 export |
| **global.css 耦合 dumi** | `@import './theme/dumi.css'` 不应出现在库运行时入口 |
| **shared 不对外** | 第三方无法复用 hooks/utils |
| **手动维护导出面** | 新组件需改 3 处 index |
### 11.3 建议
**package.json exports 修正:**
```json
{
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
},
"./styles": "./dist/styles/global.css",
"./tokens": "./dist/esm/theme/tokens.js"
}
}
```
**CSS 构建:**
-`global.css` 拆为 `styles/tokens.css`(纯 token+ `styles/dumi.css`(文档专用)
- father 配置 `extraBabelPlugins` 或 postcss 复制 CSS 到 dist
**按需加载(可选):**
- 若需要 `@Pika/ui/button`,需 father 多 entry 配置 + 每组件独立 package path
---
## 12. 优先级路线图
### P0 — 基础一致性(1–2 周)
| # | 任务 | 影响 |
|---|------|------|
| 1 | 删除 `src/components/Button/` 遗留,统一至 `common/Button/` | 消除混淆 |
| 2 | 统一 `common/` 目录为 kebab-case | 命名一致 |
| 3 | Props 诚实性清理:移除或标注未实现 prop | AI 生成准确 |
| 4 | `global.css` 与 dumi.css 解耦 | 库可独立使用 |
| 5 | 新增 `shared/utils/classNames.ts` | 减少重复 |
### P1 — 架构增强(2–4 周)
| # | 任务 | 影响 |
|---|------|------|
| 6 | 抽取 `shared/overlay/` primitives | Tooltip/Popover 减 300+ 行 |
| 7 | 抽取 `getSemantic``shared/utils/semantic.ts` | 22 处重复消除 |
| 8 | ConfigProvider Phase 1 接线(zIndex、getPopupContainer | 全局配置可用 |
| 9 | 统一 Size/Status 类型为 `PikaSize` / `PikaStatus` | 类型一致 |
| 10 | Cascader/DatePicker/TreeSelect 接入 `useClickOutside` | 交互完整 |
| 11 | 新增 `useControllableState` hook | 减少状态逻辑重复 |
### P2 — 体验完善(4–8 周)
| # | 任务 | 影响 |
|---|------|------|
| 12 | Token 统一为 CSS `--nv-*` 单源 | 主题可运行时切换 |
| 13 | 补全 7 个子组件测试 | 测试完整 |
| 14 | 每组件 `index.md` + 基础 demo | 文档可用 |
| 15 | 恢复并完善 `CONVENTIONS.md` | 贡献者友好 |
| 16 | 实现高频未实现 propDatePicker.format、Slider.range | API 完整 |
| 17 | package.json exports + CSS 入口修正 | 发布可用 |
### P3 — 长期演进
| # | 任务 |
|---|------|
| 18 | 组件级 theme override`theme.components` |
| 19 | 按需 subpath import 分包构建 |
| 20 | 组件生成 CLI |
| 21 | a11y 审计与 WCAG 基线 |
| 22 | 可视化组件(AntV)与 UI 组件 Token 统一 |
---
## 13. 附录:文件清单速查
### 13.1 应删除/迁移
```
src/components/Button/ → 删除,保留 common/Button/
src/components/Button/index.md → 迁移至 docs 或 common/Button/
```
### 13.2 应统一命名(common/ PascalCase → kebab-case
```
common/Button/ → common/button/
common/ConfigProvider/ → common/config-provider/
common/FloatButton/ → common/float-button/
common/BackTop/ → common/back-top/
… (共 10 个)
```
### 13.3 共享层待建设
```
shared/overlay/Overlay.tsx
shared/utils/classNames.ts
shared/utils/semantic.ts
shared/hooks/useControllableState.ts
shared/hooks/useBreakpoint.ts
shared/hooks/usePikaContext.ts
```
### 13.4 应对接 ConfigProvider 的组件(优先)
```
display/tooltip/Tooltip.tsx
display/popover/Popover.tsx
nav/dropdown/Dropdown.tsx
feedback/modal/Modal.tsx
feedback/drawer/Drawer.tsx
feedback/message/Message.tsx
feedback/notification/Notification.tsx
display/tour/Tour.tsx
entry/select/Select.tsx
entry/date-picker/DatePicker.tsx
```
### 13.5 统计
| 指标 | 数量 |
|------|------|
| 组件目录 | ~77 |
| 组件 TSX(不含测试) | ~80 |
| 测试文件 | ~74 |
| 无独立测试的子组件 | 7 |
| getSemantic 重复 | 22 处 |
| 未实现 prop 的组件 | ~12 |
| co-located 文档 | 1 |
---
## 总结
Pika 的 **六大分类 + 单组件目录结构** 已经是一个健康的基础,测试覆盖和 forwardRef 等实践也到位。当前主要短板集中在:
1. **一致性** — 命名、导出、类型、token 三套并行
2. **诚实性** — 接口超前于实现,对 AI-First 目标伤害最大
3. **基础设施未接线** — ConfigProvider、shared utils 写了但没用起来
4. **重复** — Overlay、getSemantic、className 拼接、受控状态
按 P0 → P1 → P2 推进,可以在不推翻现有结构的前提下,显著提升可维护性和 AI 代码生成准确率。