307 lines
8.4 KiB
Markdown
307 lines
8.4 KiB
Markdown
# Research: 统一左侧工具栏容器
|
|
|
|
**Feature**: 001-unified-toolbar | **Date**: 2025-12-01
|
|
|
|
## Overview
|
|
|
|
本文档记录统一工具栏实现的技术研究成果,包括架构决策、最佳实践和备选方案评估。
|
|
|
|
## Research Items
|
|
|
|
### 1. 响应式工具栏高度检测
|
|
|
|
**Decision**: 使用 ResizeObserver API 监听工具栏容器高度变化
|
|
|
|
**Rationale**:
|
|
- ResizeObserver 是现代浏览器原生支持的 API,性能优于基于事件监听的方案
|
|
- 支持精确检测元素尺寸变化,避免布局抖动
|
|
- 可在 useEffect 中清理,符合 React 最佳实践
|
|
- 浏览器兼容性良好 (Chrome 64+, Firefox 69+, Safari 13.1+)
|
|
|
|
**Alternatives Considered**:
|
|
- ❌ window.addEventListener('resize') - 仅检测窗口变化,无法精确监听容器高度
|
|
- ❌ IntersectionObserver - 设计用于可见性检测,不适合尺寸监控
|
|
- ❌ 基于 CSS media queries - 无法动态检测容器高度,仅能检测视口尺寸
|
|
|
|
**Implementation Pattern**:
|
|
```typescript
|
|
useEffect(() => {
|
|
const observer = new ResizeObserver((entries) => {
|
|
const height = entries[0].contentRect.height;
|
|
// 根据高度切换图标模式
|
|
});
|
|
observer.observe(containerRef.current);
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
```
|
|
|
|
---
|
|
|
|
### 2. 工具栏分区视觉分隔实现
|
|
|
|
**Decision**: 使用 CSS border-top 实现 1px 水平分割线
|
|
|
|
**Rationale**:
|
|
- 简单直接,无需额外 DOM 元素
|
|
- 使用设计系统变量 `var(--color-border)` 保持一致性
|
|
- 易于响应主题变化(浅色/深色模式)
|
|
- 性能优于使用单独的 divider 组件
|
|
|
|
**Alternatives Considered**:
|
|
- ❌ 单独的 `<div>` 分割线元素 - 增加 DOM 复杂度,影响可访问性
|
|
- ❌ 固定间距(margin/padding) - 无明显视觉分隔,不符合需求
|
|
- ❌ box-shadow 模拟分割线 - 渲染开销较大,不适合固定UI
|
|
|
|
**Implementation Pattern**:
|
|
```scss
|
|
.unified-toolbar__section {
|
|
&:not(:first-child) {
|
|
border-top: 1px solid var(--color-border);
|
|
padding-top: 8px;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. 桌面端/移动端条件渲染策略
|
|
|
|
**Decision**: 使用现有 `appState.isMobile` 状态控制渲染逻辑
|
|
|
|
**Rationale**:
|
|
- 复用现有 MobileDetect 检测逻辑,避免重复实现
|
|
- 符合项目现有架构模式
|
|
- 检测在应用初始化时完成,运行时无性能开销
|
|
- 避免使用 CSS media queries 导致的 DOM 冗余
|
|
|
|
**Alternatives Considered**:
|
|
- ❌ CSS `display: none` + media queries - 渲染两套DOM浪费资源
|
|
- ❌ useMediaQuery hook - 增加运行时检测开销,不如初始化检测
|
|
- ❌ 用户代理字符串解析 - 已由 MobileDetect 库处理
|
|
|
|
**Implementation Pattern**:
|
|
```tsx
|
|
{!appState.isMobile ? (
|
|
<UnifiedToolbar />
|
|
) : (
|
|
<>
|
|
<AppToolbar />
|
|
<CreationToolbar />
|
|
<ZoomToolbar />
|
|
<ThemeToolbar />
|
|
</>
|
|
)}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Icon-only 模式实现
|
|
|
|
**Decision**: 通过 className 切换 + CSS 隐藏文本标签
|
|
|
|
**Rationale**:
|
|
- 保持语义化 HTML,屏幕阅读器仍可访问标签
|
|
- CSS 控制显示/隐藏,性能优于条件渲染
|
|
- 支持平滑过渡动画
|
|
- 图标元素保持可点击,无需调整交互逻辑
|
|
|
|
**Alternatives Considered**:
|
|
- ❌ 条件渲染不同组件 - 导致状态重置,影响用户体验
|
|
- ❌ 动态调整 fontSize - 无法完全隐藏文本,布局计算复杂
|
|
- ❌ SVG icon 替换 - 需要维护两套图标资源
|
|
|
|
**Implementation Pattern**:
|
|
```scss
|
|
.tool-button__label {
|
|
display: inline-block;
|
|
margin-left: 8px;
|
|
|
|
.unified-toolbar--icon-only & {
|
|
display: none;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 5. 工具栏组件解耦策略
|
|
|
|
**Decision**: 保持现有工具栏组件独立,通过 props 控制是否应用定位样式
|
|
|
|
**Rationale**:
|
|
- 最小化代码修改,降低回归风险
|
|
- 支持独立测试工具栏组件
|
|
- 符合单一职责原则(容器负责布局,组件负责功能)
|
|
- 便于未来扩展和维护
|
|
|
|
**Alternatives Considered**:
|
|
- ❌ 合并所有工具栏到单一组件 - 违反单文件500行约束,难以维护
|
|
- ❌ 完全重写工具栏组件 - 重写风险高,测试成本大
|
|
- ❌ 使用 wrapper HOC - 增加抽象层次,不符合 React 现代模式
|
|
|
|
**Implementation Pattern**:
|
|
```tsx
|
|
interface ToolbarProps {
|
|
embedded?: boolean; // 是否嵌入统一容器
|
|
}
|
|
|
|
export const AppToolbar: React.FC<ToolbarProps> = ({ embedded = false }) => {
|
|
return (
|
|
<Island className={classNames('app-toolbar', {
|
|
'app-toolbar--embedded': embedded
|
|
})}>
|
|
{/* 工具栏内容 */}
|
|
</Island>
|
|
);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
### 6. 性能优化策略
|
|
|
|
**Decision**: 使用 React.memo + useCallback 优化重渲染
|
|
|
|
**Rationale**:
|
|
- 工具栏是固定UI,无需频繁更新
|
|
- memo 可避免父组件重渲染导致的不必要渲染
|
|
- useCallback 稳定回调引用,配合 memo 使用效果最佳
|
|
- 符合项目性能优化原则(宪章V)
|
|
|
|
**Alternatives Considered**:
|
|
- ❌ useMemo 缓存 JSX - 对于简单组件收益有限,代码复杂度增加
|
|
- ❌ 不做优化 - 工具栏在每次画布更新时重渲染,浪费资源
|
|
- ❌ PureComponent - 使用函数式组件,memo 是更现代的选择
|
|
|
|
**Implementation Pattern**:
|
|
```typescript
|
|
export const UnifiedToolbar = React.memo(() => {
|
|
const handleResize = useCallback(() => {
|
|
// 响应式逻辑
|
|
}, []);
|
|
|
|
// 组件实现
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### 7. 样式组织与BEM命名
|
|
|
|
**Decision**: 创建 `.unified-toolbar` BEM 块,子元素使用 `__` 连接符
|
|
|
|
**Rationale**:
|
|
- 符合项目 BEM 命名约定 (宪章 CSS/SCSS 标准)
|
|
- 清晰的命名层次,易于理解和维护
|
|
- 避免样式冲突,提升可读性
|
|
- 与现有样式系统一致
|
|
|
|
**Alternatives Considered**:
|
|
- ❌ CSS Modules - 项目未采用,引入新模式成本高
|
|
- ❌ styled-components - 项目使用SCSS,保持技术栈一致性
|
|
- ❌ Tailwind CSS - 违反项目设计系统原则
|
|
|
|
**Implementation Pattern**:
|
|
```scss
|
|
.unified-toolbar {
|
|
// 容器样式
|
|
position: absolute;
|
|
left: 36px;
|
|
top: 36px;
|
|
|
|
&__section {
|
|
// 分区样式
|
|
}
|
|
|
|
&__divider {
|
|
// 分割线样式
|
|
}
|
|
|
|
&--icon-only {
|
|
// 图标模式修饰符
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8. 可访问性(a11y)最佳实践
|
|
|
|
**Decision**: 保持现有 aria-label 和 title 属性,图标模式不移除
|
|
|
|
**Rationale**:
|
|
- 屏幕阅读器依赖 aria-label 理解按钮功能
|
|
- title 属性提供悬停提示,图标模式下更重要
|
|
- 符合 WCAG 2.1 AA 标准
|
|
- 无需额外开发,复用现有实现
|
|
|
|
**Alternatives Considered**:
|
|
- ❌ 图标模式移除文本标签后添加新 aria-label - 重复且不必要
|
|
- ❌ 使用 aria-hidden 隐藏文本 - 破坏可访问性
|
|
- ❌ 仅依赖视觉图标 - 违反可访问性标准
|
|
|
|
**Implementation Pattern**:
|
|
```tsx
|
|
<ToolButton
|
|
icon={HandIcon}
|
|
title={t('toolbar.hand')} // 保留
|
|
aria-label={t('toolbar.hand')} // 保留
|
|
// ...其他props
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Technology Stack Summary
|
|
|
|
| Category | Technology | Version | Purpose |
|
|
|----------|-----------|---------|---------|
|
|
| Language | TypeScript | 5.x | 类型安全,严格模式 |
|
|
| Framework | React | 18+ | UI组件框架 |
|
|
| UI Library | TDesign React | latest | 设计系统组件 |
|
|
| Board Framework | @plait/core | current | 白板核心框架 |
|
|
| Testing | Jest + RTL | current | 组件单元测试 |
|
|
| E2E Testing | Playwright | current | 端到端测试 |
|
|
| Styling | SCSS + BEM | N/A | 样式组织方法论 |
|
|
| Build Tool | Nx + Vite | current | Monorepo构建工具 |
|
|
|
|
---
|
|
|
|
## Risk Assessment
|
|
|
|
| Risk | Probability | Impact | Mitigation |
|
|
|------|-------------|--------|------------|
|
|
| 移动端布局意外受影响 | Low | High | 条件渲染隔离,E2E测试验证 |
|
|
| 响应式切换导致布局抖动 | Medium | Medium | 使用ResizeObserver精确检测,CSS transition平滑过渡 |
|
|
| 现有快捷键失效 | Low | High | 无修改快捷键逻辑,仅调整UI布局 |
|
|
| 性能回归(工具栏渲染慢) | Low | Medium | React.memo优化,性能基准测试 |
|
|
| 文件超过500行限制 | Low | Medium | 组件保持独立,拆分为多个文件 |
|
|
|
|
---
|
|
|
|
## Dependencies & Compatibility
|
|
|
|
**Browser Compatibility**:
|
|
- Chrome/Edge 90+ ✅
|
|
- Firefox 88+ ✅
|
|
- Safari 14+ ✅
|
|
- Mobile Safari/Chrome ✅
|
|
|
|
**Critical Dependencies**:
|
|
- ResizeObserver API (native browser API)
|
|
- CSS Flexbox (widely supported)
|
|
- React 18 concurrent features (已在项目中使用)
|
|
|
|
**No New External Dependencies Required** ✅
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
Phase 0 研究完成,所有技术决策已明确。继续进行:
|
|
|
|
1. ✅ Phase 1: 生成 data-model.md (本feature为UI重构,数据模型简化)
|
|
2. ✅ Phase 1: 生成 quickstart.md (开发者快速开始指南)
|
|
3. ✅ Phase 1: 更新 agent context (技术栈信息)
|
|
4. → Phase 2: 执行 `/speckit.tasks` 生成任务分解
|