主题
Lampio — UI 业务级组件使用全景
本文件定期扫描全仓
src/components/ui/组件实际调用,按业务语义分类索引。 与docs/ui-standards.md §13.2 实体→variant 映射表互补:规范说"应该用什么",本文档记录"当前用在哪里 / 怎么用"。 扫描时间以git log -1 -- docs/ui-component-inventory.md为准(手写"最后更新"必然漂移,已移除)。
0. 目录
- 列表组件全景(ListItem)
- 空态 / 占位(EmptyState)
- 结构 molecule(SectionHeader / Divider / Card / SectionCard)
- 交互 molecule(ConfirmDialog / Tooltip / toast)
- 表单 molecule(Input / Textarea / Select / Checkbox / Switch)
- 动作 atom(Button / IconButton / ChromeButton / Toggle)
- 视觉 atom(Icon / Spinner / ProgressBar)
- 非列表特殊模式(豁免清单)
- 低使用率 / 候选废弃
- 未抽象但重复的 pattern(S7 候选)
1. 列表组件全景
1.1 <ListItem> — 14 处调用(4 variant × 自由组合 slots)
1.1.1 variant="simple" dense — 紧凑同质词条流
| # | 位置 | 特点 | 业务实体 |
|---|---|---|---|
| 1 | VocabPanel Notebook | active 双态:selected=strong(bg/15) / expanded=soft(bg/10);leading = [checkbox?] + [mastery dot] 复合;trailing = [CEFR badge] + [✓掌握/🗑] 两操作 | 词汇 |
| 2 | VocabPanel Excluded | active=soft 单态;leading=[checkbox or placeholder];trailing=[SYS/USER badge] + [🗑 删除] | 排除词汇 |
| 3 | VocabPanel Browse | 无 active;leading=占位(与 Excluded 对齐);trailing=[CEFR] + [+添加/⊘排除] 或 [✓/⊘ badge] 互斥 | 核心词汇浏览 |
| 4 | SearchDropdown Related Articles | leading=[emoji 📰/🕐] | 搜索下拉文章项 |
1.1.2 variant="simple" normal — 单行数据流
| # | 位置 | 特点 | 业务实体 |
|---|---|---|---|
| 5 | RssPanel 订阅源列表 | leading=[Favicon 或 Rss fallback];meta=[●+未读数];trailing=[ChevronRight] | RSS 订阅源 |
1.1.3 variant="double" normal — 双行 stack
| # | 位置 | 特点 | 业务实体 |
|---|---|---|---|
| 6 | HistoryPanel 资源历史 | leading=[TypeIcon: Globe/BookOpen];subtitle = type + count + 日期(右对齐 ml-auto);trailing=[🗑 hover-reveal] | 资源访问记录 |
| 7 | MySitesPanel 站点列表 | leading=[Favicon 或 TypeIcon];subtitle=domain;trailing=[X 取消收藏 hover-reveal] | 收藏站点 |
| 8 | HomePage Recently Opened | 与 #6 同实体,同结构 | 资源访问记录 |
| 9 | HomePage Recommended 文章 | leading=[Favicon 方块];subtitle = CEFR badge + reason + source 内联 | 内容推荐 |
| 10 | RssPanel 文章列表 | tone="muted" 已读灰化;titleLines=2 长标题;leading=[● 未读/○ 已读];subtitle=日期;className="items-start" | RSS 文章 |
| 14 | NotesPanel 来源页面(笔记详情,2026-07-04 改版 + 二轮) | density=dense;subtitle=最近阅读相对时间(last_opened_at 兜底 created_at);meta="N 词";trailing=[ChevronRight];点行 drill-down 进来源详情页(打开页面动作移入 drill header) | 阅读来源页 |
1.1.4 variant="rich" normal — 卡片化独立实体(用 avatar slot)
| # | 位置 | 特点 | 业务实体 |
|---|---|---|---|
| 11 | NotesPanel 笔记本 | avatar(cover / fallback=Folder/Book,tone=primary/neutral);subtitle="N 词 · N 源";trailing=[ChevronRight](S6.5 对齐 ReviewOverview 视觉) | 笔记本 |
| 12 | ReviewOverview 复习分组 | avatar(cover / fallback=Calendar/BookOpen);meta=[due pill badge];subtitle=缓存页数+预览词;trailing=[ChevronRight] | 复习会话分组 |
1.1.5 variant="tree" dense — 层级缩进
| # | 位置 | 特点 | 业务实体 |
|---|---|---|---|
| 13 | TocPanel 目录 | depth(paddingLeft = 8 + depth×12);无 border;active → text-primary font-medium | 文档目录项 |
1.2 <Tile> — 已退役(2026-08-13)
<Tile>原 4 处调用(全在 HomePage)已迁到 ListItem variant="double",ui/Tile.tsx + tileVariants 一并删除。迁移理由:同一发现页里原先并存两套列表语法——「继续阅读 / 最近打开」 是 ListItem,站点/订阅四栏是 Tile 描边卡;而 double 变体的实体映射原文就写着 「history / sites / RSS / feeds」。
现由 HomePage.DiscoverRow 统一(ListItem double + 星标 trailing):
| # | 位置 | leading | subtitle | 星标 | 业务实体 |
|---|---|---|---|---|---|
| D1 | SiteCard(我的收藏站点) | SiteFavicon sm | domain[· category] | 实心,hover 显现 | 收藏站点 |
| D2 | 推荐站点 | SiteFavicon sm | domain · category · 难度档 | 空心,常显 | 推荐站点 |
| D3 | 我的订阅 | Rss 图标 | tag/域名 · 未读数 | 实心,hover 显现 | 订阅源 |
| D4 | 推荐订阅 | Rss 图标 | tag · 难度档 | 空心,常显 | 推荐订阅源 |
⚠️ 星标显隐刻意不对称:已收藏态星标是「取消」(hover 显现,同 MySitesPanel); 可收藏态星标是「收藏」= 该栏主要动作,常显,藏起来等于藏掉整栏用途。
1.2.5 <WordRow> — 4 处(Library 4 tab + sourceWords 子列表)
ListItem variant="simple" density="dense" 的固定槽位 wrapper:leading → word → CEFR badge(auto from cefrLevel) → stateBadges → actions。所有词汇行的统一入口。
| 位置 | leading | stateBadges | actions |
|---|---|---|---|
| Notebook | MasteryDot(+SelectCheckbox 批量) | — | ✓/Eye + 🗑 |
| Excluded | Ban(amber) | SYS/USER badge | 🗑(user only) |
| Browse(in-notebook) | Bookmark(emerald) | ✓ Notebook | — |
| Browse(excluded) | Ban(amber) | ⊘ Excluded | — |
| Browse(discoverable) | 空 placeholder | — | ⊘ chip |
1.2.6 <ListPanel> + <ListWithDetail> — 4 处(Library 4 tab)
<ListPanel>:封装 w-80 + 右 border + header + scroll body + footer,header 区放 filter pills / advanced popdown / batch toolbar(filter 紧贴 list,toolbar 极简化后的承载点)。
<ListWithDetail>:2-column 骨架(left + 右灰画布 + 内嵌白卡)。hasSelection=false 时跳过白卡只画 EmptyState。
1.3 ListItem 正交能力索引(按能力查列表)
| 能力 | prop / 模式 | 命中列表 |
|---|---|---|
| 左图标 14-20px | leading: ReactNode | History / MySites / RSS feeds / RSS articles / Search / Home Recently / Home Recommended |
| 圆角 avatar 28×28 | avatar: {src, fallback, tone} | NotesPanel / ReviewOverview |
| 主标题 + 副标题 | title + subtitle | 所有 double + rich |
| 双行标题 clamp | titleLines={2} | RssPanel articles |
| 已读/灰化 | tone="muted" | RssPanel 已读 |
| 右缘 meta | meta: ReactNode | RSS feeds unread / ReviewOverview due / History time |
| 右缘操作 | trailing: ReactNode | History 🗑 / MySites X / Notes 🗑 / Vocab ✓🗑 / Review ❯ |
| hover-reveal 操作 | trailing + className="group" | History / MySites 删除 |
| 选中 active | active + activeTone | VocabPanel / TocPanel |
| 多选 checkbox | leading={<Fragment>checkbox + dot</>} | VocabPanel 批量选择 |
| 层级缩进 | variant='tree' + depth | TocPanel |
| 整行可点 + 右缘 action | onClick + trailing + 子按钮 stopPropagation | MySites / HomePage DiscoverRow 四栏 |
1.4 可展开二级模式(目前仅一种)
VocabPanel Notebook 主行 + sibling 详情(mastery label + date):
tsx
<div>
<ListItem ... onClick={toggleExpand} active={isExpanded} />
{isExpanded && <div className="mx-3 mb-1 p-2 bg-surface-content rounded">...</div>}
</div>非侵入式:ListItem 不管展开,调用方渲染兄弟节点。适合简短 meta 展开。复杂详情(如 TranslationTimeline 展开 grammar/vocab/grammar_points 三段)→ 独立组件。
2. 空态 / 占位
2.1 <EmptyState> — 16 处调用(三大类)
| 类型 | 位置 | 关键 slots |
|---|---|---|
| Loading 占位 | NotesPanel "加载笔记本..." / RssPanel × 2 / ReviewOverview | primary + density="compact" |
| 空态无 CTA | HistoryPanel / DiscoveryPanel / NotesPanel "暂无来源" / VocabPanel Excluded / TranslationTimeline × 2 | primary [+ secondary] |
空态带 CTA(action slot,S6.3 启用) | NotesPanel + 新建笔记本 / RssPanel + 添加 RSS / VocabPanel 浏览核心词汇 | primary + secondary + action=Button variant="link" |
| 错误态 | RssPanel 网络失败 | icon=CircleAlert + primary(红色) + secondary 降级提示 |
3. 结构 molecule
3.1 <SectionHeader> — 5 处调用(S6.3 从 HomePage 升级)
Uppercase caption + 可选右缘 link action + nested(↳ 前缀缩进)。目前全在 HomePage,未来 NotesPanel / VocabPanel section headers 可复用。
tsx
<SectionHeader title="Recently Opened" action="View All" onAction={...} />
<SectionHeader title="Recommended Sites" nested />3.2 <Divider> — 2 处调用
Section 之间的硬分隔(border-t border-border/30)。调用少——大部分区块靠 mb-* 间距 + section header 已够。
3.3 <Card> / <SectionCard> — 均 0 调用 ⚠️
Pre-S5 残留。Card 的语义已被 ListItem variant='rich' 吸收,SectionCard 的语义被 Settings 独立 row 样式覆盖。候选废弃,见 §9。
3.4 <CollapsibleSection> — 句子工作台拆解/指代子块
自包含的折叠小节:header = 图标 + 标题 + 可选数量徽 + 右侧 ChevronDown(group-hover 时图标/标题/箭头三元素同步变色、箭头 rotate-180),body 经 useState 折叠(defaultOpen 默认 true)。视觉 idiom 抽取自 DiscoveryPanel 短语清单/词清单 header。a11y:button 可见标题即 accessible name + aria-expanded,无需额外 aria-label。当前调用:SentenceWorkbench 拆解段(主干/修饰/词汇/语法点)+ 指代段(指代/背景);NotesPanel 域名规则(低频配置项折叠收纳,defaultOpen=false + count=规则数,body 即扁平管理面:chips hover-× + 内联添加)+ 来源详情 drill-down 三节(单词/句子/跟读——句子节为按句聚合的管理卡,卡级+类型级删除,分组 key 复用 SentenceWorkbench.normalizeSentence;2026-07-06 三轮打磨)。与 DiscoveryPanel 的区别——那里 header/body 分在两个 PanelSection(面板级分隔+滚动耦合),本组件是卡内内联单元,故 DiscoveryPanel 暂未迁移。
4. 交互 molecule
4.1 <ConfirmDialog> — 1 处调用
基于 Modal(Radix Dialog)+ 固定 amber warning 或 destructive 配色。用于"删除/关闭前确认"。目前 App.tsx 只在标签驱逐触发。
4.2 <Tooltip> / <TooltipProvider> — 0 直接调用 ⚠️
S2.5 预备的 Radix Tooltip wrapper,原计划 S7 长尾把 HTML title="..." 属性批量迁过来。目前没有业务调用,但 <TooltipProvider> 已挂在 App 根节点等待使用。活跃基础设施,不算废弃。
4.3 <Modal> / <Popover> — 基础设施
Radix wrapper,分别有 1 个直接调用(Modal → ReportOverlay;Popover → AccountPopover)。数量少因为 ConfirmDialog 封装了 Modal 的 90% 场景。
4.4 lib/toast.ts(sonner 包装)— 3 处调用(S6)
ts
toast.success('已复制') // TranslationTimeline 剪贴板
toast.error('清理失败', { description: String(err) }) // StorageSettings
toast.promise(p, { loading, success, error })<Toaster position="bottom-center" richColors closeButton /> 已挂 App 根。
5. 表单 molecule
| 组件 | 调用 | 特点 |
|---|---|---|
<Input> | 7 | 4 variant(default / search / xs / soft)+ error prop + tone="readonly" |
<Textarea> | 0 ⚠️ | 带 error,但无调用 |
<Select> | 0 ⚠️ | 带 error,但无调用(Settings 用原生 <select>) |
<Checkbox> | 0 ⚠️ | Vocab 批量选择用内联 span + svg 而非 Checkbox |
<Switch> | 1 | Settings 开关 |
现象:VocabPanel 的批量选择 checkbox 是内联实现而不走 ui/Checkbox——因为 ListItem 的 leading slot 接受任意 ReactNode,作者自然选了最短路径。这是 API 宽松的副作用,但收紧难度高(Checkbox 样式在 ListItem leading 里需要更小的 14×14 尺寸)。S7 低优先级。
6. 动作 atom
| 组件 | 调用 | 特点 |
|---|---|---|
<Button> | 39 | 5 variant(primary/secondary/ghost/destructive/link)× 4 size(xs/sm/md/lg) |
<IconButton> | 19 | 3 size(xs/sm/md),纯 icon 按钮 |
<ChromeButton> | 9 | Chrome 专用(TitleBar / AddressBar / Toolbar 的灰黑风格按钮) |
<Toggle> | 0 ⚠️ | 3 variant × active 态,但无调用(都走内联 TOGGLE.* class 组合) |
现象:<Toggle> 是 S3 cva 化的 variant 系统,但无人用 React 组件,都直接贴 TOGGLE.navActive class 字符串。这是能用但没人用的状态——因为 TabBar / VocabPanel 内联 class 写起来比传 props 更短。
7. 视觉 atom
| 组件 | 调用 | 特点 |
|---|---|---|
<Icon> | 60 | lucide 包装;强制 ICON_SIZE scale(xs/sm/md/lg/xl),arch-check S17 守护 |
<Spinner> | 1 | 加载小转圈 |
<ProgressBar> | 1 | 复习进度条 |
Icon 是全仓调用最多的 ui/ 组件(60 处),说明 S2 的 lucide 迁移起到了预期效果。
8. 非列表特殊模式(明确豁免清单)
以下不应强套 ListItem:
| 位置 | 模式 | 豁免原因 |
|---|---|---|
WordPopup definitions | pos italic + def_zh 内联段落 | 段落排版,非 row |
TranslationTimeline TimelineCard | 可展开翻译详情(grammar+vocab+grammar_points 三段) | 展开后是完整详情视图 |
SearchDropdown words | 字典结果卡片(word + IPA + CEFR + mastery + def) | 字段过多 inline |
DiscoveryPanel | CEFR filter 横向 chip + 按 level 分组 3 列 grid | grid 结构 |
StatsPanel | 图表分组 + metric cards | 图表容器 |
CefrPanel | 文章难度 context strip(DiscoveryPanel 顶部摘要行 + 可展开 CEFR 分布条 + 个性化难度档) | 摘要/统计展示,非 row 列表 |
Settings GeneralSettings | CEFR_LEVELS / SHORTCUTS 横向 pill | 选项组 |
ReportOverlay | 2-col metric grid + 时段 tab | dashboard 布局 |
TabBar / TitleBar / EpubNavBar | 横向 tab / 操作按钮行 | chrome |
charts/* | SimpleBarChart / SimpleLineChart | 图表实现 |
ReviewCardFace | 复习卡翻转 | 单卡片 |
9. 低使用率 / 候选废弃
| 组件 | 调用 | 判定 |
|---|---|---|
Card | — | ✅ S6.4 删除(语义被 ListItem variant='rich' 吸收) |
SectionCard | — | ✅ S6.4 删除(Settings 用独立 row 样式) |
Textarea | 0 | 保留——未来表单场景可能用 |
Select | 0 | 保留——表单场景预备位 |
Checkbox | 0 | 保留——VocabPanel 若 S7 收紧会迁过来 |
Toggle | 0 | 保留——是 class 字符串的 React wrapper 备选(哲学:class string + 组件双入口) |
Tooltip | 0 直接调用 | 保留——S7 预备位,替代 HTML title 属性;TooltipProvider 已挂 App 根 |
清理策略:Textarea / Select / Checkbox / Toggle / Tooltip 五个 0 调用组件确认保留(2026-04-19 决策),各自有明确的未来预备位或哲学理由。
10. 未抽象但重复的 pattern
2026-04-19(S6.4)清理:
10.1 ✅ SiteFavicon — 已抽到 ui/SiteFavicon.tsx
两种尺寸(size='sm' = 16×16 列表行 leading / size='lg' = 28×28 wrapped,Tile 退役后 lg 暂无调用方)。自带 duckduckgo src + 首字母 fallback + useState failed React 化错误处理(替代原来两种不同的 onError 实现)。
迁移调用点:
HomePage.SiteCard→ 内部用HomeSiteLeading(epub 走 TypeIcon,其他委托SiteFavicon size="lg")HomePageRecommended Sites →SiteFavicon size="lg"MySitesPanel站点行 →SiteFavicon size="sm"(替代原 localFavicon组件)
Favicon Fallback pattern(10.5)已并入 SiteFavicon,不再单独列项。
10.2 ✅ TabButton — 已移到 ui/TabButton.tsx
从 src/components/TabButton.tsx 移入 ui/。5 处 import 路径更新(RssPanel / VocabPanel / ReviewOverview / TranslationTimeline / HistoryPanel)。
10.3 保留 class helper 形态(不组件化)
2026-04-19 决策:以下 pattern 不抽组件,保留 class helper 函数:
- Mastery Dot:
<span className="w-1.5 h-1.5 rounded-full ${MASTERY_COLORS[level]}">— 1 行样式 + 1 个 className 引用,组件化收益不覆盖导入成本。保留MASTERY_COLORS常量 + 内联使用。 - CEFR Badge:已有
cefrBadgeClass()/cefrBarClass()helper,调用处只需<span className={cefrBadgeClass(level)}>{level}</span>。保留 helper 形态。
判断标准:组件化门槛 = 结构 > 10 行 或 有条件分支 或 跨文件逻辑重复。单行 className 引用不够门槛——过度组件化本身是另一种漂移。
附录 A:刷新本文档的时机
- 新增
ui/组件 → 在对应分类下登记 - ListItem 新调用 → 在 §1 表中加行
- 组件 0 调用超 3 个月 → 候选废弃(§9)
- 发现新重复 pattern → 加到 §10
附录 B:与其他文档的关系
| 这里有 | 别处有 | 关系 |
|---|---|---|
| §1.1 ListItem 13 处详单 | docs/ui-standards.md §13.2 实体→variant 映射表 | 规范规定"应该用什么",本文档记录"用在哪里" |
| §2 EmptyState 16 处 | docs/ui-standards.md §15.1 | 同上 |
| §8 豁免清单 | docs/ui-standards.md §20 违规速查 | 同上 |
| §10 未抽象 pattern | 无 | 本文档是唯一记录点,S7 长尾规划参考 |