{toolButtons.map((button) => {
const Icon = button.icon;
const isActive = button.mode && state.activeMode === button.mode;
// 带 Hover 菜单的按钮
return (
}
/>
);
})}
);
}
```
---
## 4. Hover 二级菜单
### 4.1 布局示意
```
┌───────────────────┐
│ 📏 线性捕捉 1 │ ← 菜单项 (hover 高亮)
│ 📐 垂直净高 2 │
│ 📈 高程测量 3 │
│ 🗑️ 清除测量 4 │
└───────────────────┘
↑
┌──────────┐
│ 📏 │ ← 触发按钮
└──────────┘
```
### 4.2 容器规范
| 属性 | 值 | Tailwind CSS |
| ---------- | ---------- | ------------------------------------------- |
| **背景** | 白色半透明 | `bg-white/95 dark:bg-[rgba(30,41,59,0.98)]` |
| **毛玻璃** | 10px 模糊 | `backdrop-blur-[10px]` |
| **圆角** | 12px | `rounded-xl` |
| **阴影** | 较强阴影 | `shadow-[0_8px_24px_rgba(0,0,0,0.12)]` |
| **边框** | 半透明边框 | `border border-slate-200/60` |
| **内边距** | 6px | `px-1.5 py-1.5` |
| **布局** | 垂直列表 | `flex flex-col gap-0` |
### 4.3 菜单项规范
| 属性 | 值 | Tailwind CSS |
| -------------- | ------------------- | ------------------------------------------------------- |
| **布局** | 水平弹性布局 | `flex items-center gap-2.5` |
| **内边距** | 水平 12px, 垂直 8px | `px-3 py-2` |
| **圆角** | 8px | `rounded-lg` |
| **字体** | 13px 普通字重 | `text-[13px] font-normal` |
| **文字颜色** | 深灰色 | `text-slate-700 dark:text-[rgba(255,255,255,0.85)]` |
| **Hover 背景** | 浅灰色 | `hover:bg-slate-100 dark:hover:bg-[rgba(51,65,85,0.8)]` |
| **过渡** | 100ms 平滑 | `transition-all duration-100 ease-out` |
### 4.4 触发时机
| 事件 | 延迟时间 | 说明 |
| -------------- | -------- | ------------------------------------------ |
| **Hover 进入** | 100ms | 鼠标进入按钮后 100ms 显示菜单 |
| **Hover 离开** | 250ms | 鼠标离开「按钮+菜单」区域后 250ms 隐藏菜单 |
### 4.5 键盘快捷键
- **1-9 键**: 菜单展开时,按数字键触发对应菜单项
- **Esc 键**: 关闭菜单并退出当前模式
### 4.6 参考代码片段
```tsx
// 文件: HoverMenu.tsx
export function HoverMenu({
trigger,
items,
placement = 'top',
openDelay = 100,
closeDelay = 250,
}: HoverMenuProps) {
const [isOpen, setIsOpen] = useState(false);
const handleMouseEnter = () => {
openTimerRef.current = setTimeout(() => {
setIsOpen(true);
}, openDelay);
};
const handleMouseLeave = () => {
closeTimerRef.current = setTimeout(() => {
setIsOpen(false);
}, closeDelay);
};
return (