168 lines
9.9 KiB
Markdown
168 lines
9.9 KiB
Markdown
---
|
||
name: dropdown-component
|
||
overview: 在 fms-vue 组件库中新增 Dropdown 下拉菜单组件:基于现有 Popover 浮层派生,采用数据驱动 items(label/icon/shortcut/disabled/danger/divider/子菜单)+ 默认插槽逃生舱,支持 click/hover/contextmenu 触发与键盘导航,样式参考 shadcn(白卡片、圆角、hover 高亮、分隔线、危险项、快捷键),并补充 demo 与 App.vue 导航注册。
|
||
design:
|
||
architecture:
|
||
framework: vue
|
||
styleKeywords:
|
||
- 卡片式浮层
|
||
- 紧凑列表
|
||
- hover 高亮
|
||
- 危险变体
|
||
- 精致微交互
|
||
fontSystem:
|
||
fontFamily: Noto Sans SC, PingFang SC
|
||
heading:
|
||
size: 14px
|
||
weight: 500
|
||
subheading:
|
||
size: 12px
|
||
weight: 500
|
||
body:
|
||
size: 14px
|
||
weight: 400
|
||
colorSystem:
|
||
primary:
|
||
- "#0f172b"
|
||
- "#1677ff"
|
||
background:
|
||
- "#ffffff"
|
||
- "#f5f6f8"
|
||
- "#1a1d24"
|
||
text:
|
||
- "#1f2329"
|
||
- "#86909c"
|
||
functional:
|
||
- "#ef4444"
|
||
- "#e5e6eb"
|
||
todos:
|
||
- id: popover-contextmenu
|
||
content: 用 [skill:antdv-next] 确认右键触发交互,扩展 Popover 支持 trigger=contextmenu(validator + 右键坐标定位)
|
||
status: completed
|
||
- id: dropdown-tokens
|
||
content: tokens.css 新增 --fms-dropdown-* 尺寸 token(min-width/max-height/item padding/radius/divider margin)
|
||
status: completed
|
||
- id: dropdown-style
|
||
content: 用 [skill:ui-ux-pro-max] 参考设计,创建 dropdown/index.scss(shadcn 风格菜单卡片/项/分隔线/危险态/子菜单箭头)
|
||
status: completed
|
||
dependencies:
|
||
- dropdown-tokens
|
||
- id: dropdown-component
|
||
content: 创建 dropdown/dropdown.vue(items 数据驱动、键盘导航、子菜单、插槽逃生舱、ARIA)
|
||
status: completed
|
||
dependencies:
|
||
- popover-contextmenu
|
||
- dropdown-style
|
||
- id: dropdown-demo
|
||
content: 创建 demo/dropdown.vue 并注册到 App.vue「数据展示」分类与 demoMap
|
||
status: completed
|
||
dependencies:
|
||
- dropdown-component
|
||
- id: static-check
|
||
content: 静态检查全部改动文件(oxlint)并汇报变更清单与已知风险
|
||
status: completed
|
||
dependencies:
|
||
- dropdown-demo
|
||
---
|
||
|
||
## 产品概述
|
||
|
||
在 fms-vue 组件库中新增 **Dropdown 下拉菜单**组件:功能与交互参考 antdv-next(数据驱动 items、多种触发方式、子菜单、键盘操作),样式与设计参考 shadcn DropdownMenu(卡片式浮层、hover 高亮、危险项、快捷键、图标、勾选指示),并严格遵循 `fms-vue/开发规范.md`。
|
||
|
||
## 核心功能
|
||
|
||
- **数据驱动菜单**:`items` prop 定义菜单项,结构支持 `key / label / icon / shortcut / disabled / danger / divider / type: 'label'(分组标题)/ children(子菜单)`;不传 items 时提供默认插槽逃生舱(对齐 Select 模式)
|
||
- **多种触发方式**:`click`(默认)/ `hover` / `contextmenu`(右键,需扩展 Popover 以支持右键坐标定位)
|
||
- **子菜单**:`children` 嵌套渲染,hover 展开右侧子菜单
|
||
- **键盘导航**:打开后聚焦菜单容器,方向键/Home/End 移动高亮,Enter 选择,Esc 关闭,`aria-activedescendant` 无障碍
|
||
- **受控/非受控**:`open` / `default-open`,事件 `update:open` / `openChange` / `click`
|
||
- **视觉要素**(shadcn 风格):卡片浮层(圆角/边框/阴影)、菜单项 hover/focus 高亮、禁用灰、danger 红、分隔线、分组小标签、快捷键右侧、图标左侧
|
||
- **演示接入**:新增 `demo/dropdown.vue` 并在 App.vue「数据展示」分类注册,演示基础/图标+快捷键/分隔线/禁用/危险/子菜单/hover/右键/受控
|
||
|
||
## 技术栈
|
||
|
||
- Vue 3 `<script setup>`、Sass、`@lucide/vue` 图标按需导入
|
||
- 复用现有 `Popover`(Teleport 定位、外部点击关闭、Esc 关闭、滚动/缩放跟随)与 `utils/position.js` 纯函数
|
||
- 样式复用 `--fms-*` token,缺失 token 补到 `src/theme/tokens.css`
|
||
|
||
## 实现方案
|
||
|
||
### 1. 扩展 Popover 支持 contextmenu 触发
|
||
|
||
- `popover.vue`:`trigger` validator 增加 `'contextmenu'`;新增 `onContextMenu` 处理——`preventDefault`,用事件 `clientX/clientY` 构造零尺寸锚点 rect(`{left, top, width:0, height:0, right:clientX, bottom:clientY}`)存入变量,`setOpen(true)` 后 `updatePosition()`
|
||
- `updatePosition()`:锚点 rect 存在时优先使用,否则回退 `triggerEl.getBoundingClientRect()`(`position.js` 纯函数不改,仅传参变化)
|
||
- 关闭时清空锚点 rect;模板触发器元素绑定 `@contextmenu`
|
||
|
||
### 2. Dropdown 主组件(dropdown.vue)
|
||
|
||
- **props**:`items`(Array)、`open`/`default-open`、`trigger`(click/hover/contextmenu,validator)、`placement`(默认 `bottom-start`)、`disabled`、`mouse-enter-delay`/`mouse-leave-delay`、`arrow=false`
|
||
- **结构**:`<Popover :trigger="trigger" :placement="placement" :arrow="false">`,`#trigger` 插槽透传默认插槽;`#content` 渲染菜单容器
|
||
- **菜单渲染**:`items` 循环——`divider` 渲染分隔线;`type==='label'` 渲染分组标签;`children` 渲染嵌套 `Popover`(hover、`right-start`、offset 4)作为子菜单,父项右侧 ChevronRight 箭头;常规项含 icon/label/shortcut
|
||
- **键盘导航**(参照 Select 模式):菜单容器 `role="menu"` `tabindex="-1"`,打开后 `nextTick` focus;`activeIndex` 仅遍历可交互项(跳过 divider/label/disabled),方向键/Home/End 移动并滚动到可视,Enter 触发 `click` 事件,Esc 关闭;`aria-activedescendant` 指向高亮项
|
||
- **事件**:点击项 `emit('click', { key, item })` 后关闭;disabled/danger 项均可触发(danger 是视觉变体);子菜单项事件冒泡由嵌套 Popover 隔离
|
||
- **插槽逃生舱**:无 `items` 时渲染默认插槽(透出 `close` 函数)
|
||
|
||
### 3. 样式(参考 shadcn style-nova.css 的 cn-dropdown-menu-*)
|
||
|
||
- 菜单容器:`min-width: 160px`、`padding: 4px`、`background: --fms-card`、`border: 1px solid --fms-border`、`border-radius: --fms-control-radius`、`box-shadow: --fms-popover-shadow`、`max-height` 溢出滚动
|
||
- 菜单项:flex、`gap: 8px`、`padding: 5px 8px`、`border-radius: 4px`、hover/focus 背景 `--fms-secondary-hover`;icon/svg `1em`;shortcut `margin-left: auto`、`12px`、`--fms-text-secondary`;danger 文字 `--fms-danger`;disabled 用 `--fms-disabled-text` + `not-allowed`
|
||
- 分隔线:`height: 1px`、`background: --fms-border`、`margin: 4px 0`;分组标签:`12px`、`--fms-text-secondary`
|
||
- 过渡复用 popover 的 scale+fade transition(低开销、支持 reduced-motion)
|
||
|
||
### 4. Token 新增(tokens.css)
|
||
|
||
```css
|
||
/* 下拉菜单(dropdown) */
|
||
--fms-dropdown-min-width: 160px;
|
||
--fms-dropdown-max-height: 256px;
|
||
--fms-dropdown-item-padding-y: 5px;
|
||
--fms-dropdown-item-padding-x: 8px;
|
||
--fms-dropdown-item-radius: 4px;
|
||
--fms-dropdown-divider-margin: 4px 0;
|
||
```
|
||
|
||
### 5. 演示接入
|
||
|
||
- `src/components/ui/demo/dropdown.vue`:基础菜单、图标+快捷键、分隔线+分组标签、禁用项、危险项、子菜单、hover 触发、右键触发、受控模式
|
||
- `App.vue`:import DropdownDemo,`demoMap` 注册,分类「数据展示」加 `{ key: 'dropdown', label: 'Dropdown 下拉菜单' }`
|
||
|
||
## 性能与可靠性
|
||
|
||
- 全局监听(外部点击/Esc/scroll/resize)由 Popover 打开时注册、关闭时清理,无长期监听
|
||
- 键盘导航仅维护 `activeIndex` 单一状态,`scrollIntoView({ block: 'nearest' })` 惰性滚动,无高频计算
|
||
- 子菜单使用嵌套 Popover,复用其移入浮层不关闭、Esc 关闭能力;多层 Esc 一次性全部关闭(MVP 行为,后续可逐层)
|
||
- 右键定位基于事件坐标构造零尺寸 rect,`computePosition` 的 flip/shift 兜底防溢出
|
||
|
||
## 风险与边界
|
||
|
||
- contextmenu 锚点为鼠标点,浮层大小变化时由 ResizeObserver 重新定位(Popover 已实现)
|
||
- `trigger` 数组组合(如 click+hover 并存)本期不支持,保持单一枚举(antdv 亦以单一触发为主),API 最小
|
||
- 不提供 size 枚举、不做旧 API 兼容分支(遵循规范 7.1/4.2)
|
||
|
||
## 设计风格
|
||
|
||
Dropdown 为浮层类菜单组件,视觉参考 shadcn DropdownMenu,与项目现有 Popover/Select 下拉视觉体系统一。
|
||
|
||
- **浮层卡片**:与 Popover 一致的白/深色卡片(`--fms-card`),1px 边框(`--fms-border`)、6px 圆角、柔和投影(`--fms-popover-shadow`),内容内边距 4px,最小宽度 160px,超长列表滚动
|
||
- **菜单项**:紧凑单行布局——图标(1em 灰色)在左,文字居中偏左,快捷键右对齐浅灰小字;hover/聚焦时整行浅灰高亮(`--fms-secondary-hover`),选中态主色;禁用项灰字不可点;危险项(如删除/退出)文字为红色 `--fms-danger`
|
||
- **分组与分隔**:分组小标签(12px 浅灰小字)、1px 分隔线,信息层级清晰
|
||
- **子菜单**:父项右侧 ChevronRight 小箭头指示,hover 展开右侧次级菜单卡片,视觉同主菜单
|
||
- **动效**:淡入 + 轻微缩放(150ms,复用 popover 过渡),hover 高亮 150ms 过渡,支持 `prefers-reduced-motion`
|
||
- **交互反馈**:打开时菜单项方向键可达、Enter 确认、Esc 关闭;触发器打开态可用 popover 现有高亮语义
|
||
|
||
## Agent Extensions
|
||
|
||
### Skill
|
||
|
||
- **antdv-next**
|
||
- Purpose: 查询 antdv-next Dropdown 的 props/events/slots API 文档与触发交互细节(trigger、placement、子菜单),验证 API 设计一致性
|
||
- Expected outcome: 确认 Dropdown 的 trigger 枚举、事件签名、子菜单数据结构,确保与参考实现对齐
|
||
- **ui-ux-pro-max**
|
||
- Purpose: 为下拉菜单浮层样式与交互细节(hover 态、危险项、分组分隔、动效时长)提供设计参考
|
||
- Expected outcome: 产出与 shadcn DropdownMenu 风格一致的视觉规范,落地到 index.scss
|
||
|
||
### SubAgent
|
||
|
||
- **code-explorer**
|
||
- Purpose: 实施前确认 Popover 触发机制、Select 键盘导航细节与 App.vue demoMap 注册模式,避免遗漏调用点
|
||
- Expected outcome: 提供精确的修改目标清单与插入位置 |