Dropdown 下拉菜单
下拉菜单 —— Popover + 列表 + 键盘导航。支持分组标题、分隔线、危险操作语义。
基础用法
items 数组式配置,按顺序渲染菜单项;divider: true 渲染分隔线,header: true 渲染分组标题。键盘 ↑↓ 切换、Enter / 空格 选中、Esc 关闭、Home / End 跳到首/末项。
背景 视口
<script setup lang="ts">
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';
const items: DropdownItem[] = [
{ key: 'profile', label: '个人资料' },
{ key: 'settings', label: '设置' },
{ divider: true, label: '' },
{ header: true, label: '操作' },
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档', disabled: true },
{ key: 'delete', label: '删除', tone: 'danger' },
];
function onSelect(item: DropdownItem) {
console.log('selected:', item.key);
}
</script>
<template>
<CfDropdown :items="items" placement="bottom" @select="onSelect">
<CfButton variant="secondary">操作菜单 ▾</CfButton>
</CfDropdown>
</template> <script setup>
import { CfButton, CfDropdown } from '@chufix-design/vue';
const items= [
{ key: 'profile', label: '个人资料' },
{ key: 'settings', label: '设置' },
{ divider: true, label: '' },
{ header: true, label: '操作' },
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档', disabled: true },
{ key: 'delete', label: '删除', tone: 'danger' },
];
function onSelect(item) {
console.log('selected:', item.key);
}
</script>
<template>
<CfDropdown :items="items" placement="bottom" @select="onSelect">
<CfButton variant="secondary">操作菜单 ▾</CfButton>
</CfDropdown>
</template> import { CfButton, CfDropdown } from '@chufix-design/react';
import type { DropdownItem } from '@chufix-design/react';
const items: DropdownItem[] = [
{ key: 'profile', label: '个人资料' },
{ key: 'settings', label: '设置' },
{ divider: true, label: '' },
{ header: true, label: '操作' },
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档', disabled: true },
{ key: 'delete', label: '删除', tone: 'danger' },
];
export default function Demo() {
return (
<CfDropdown
items={items}
placement="bottom"
onSelect={(item) => console.log('selected:', item.key)}
>
<CfButton variant="secondary">操作菜单 ▾</CfButton>
</CfDropdown>
);
} import { CfButton, CfDropdown } from '@chufix-design/react';
const items= [
{ key: 'profile', label: '个人资料' },
{ key: 'settings', label: '设置' },
{ divider: true, label: '' },
{ header: true, label: '操作' },
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档', disabled: true },
{ key: 'delete', label: '删除', tone: 'danger' },
];
export default function Demo() {
return (
<CfDropdown
items={items}
placement="bottom"
onSelect={(item) => console.log('selected:', item.key)}
>
<CfButton variant="secondary">操作菜单 ▾</CfButton>
</CfDropdown>
);
} Placement 方向
placement 决定菜单相对触发元素的位置:top / bottom(默认)/ left / right。空间不足时浮层会自动翻转到对侧。
背景 视口
<script setup lang="ts">
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';
const items: DropdownItem[] = [
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档' },
{ divider: true, label: '' },
{ key: 'delete', label: '删除', tone: 'danger' },
];
</script>
<template>
<div class="adm-grid">
<CfDropdown :items="items" placement="top">
<CfButton variant="secondary">placement = top</CfButton>
</CfDropdown>
<CfDropdown :items="items" placement="bottom">
<CfButton variant="secondary">placement = bottom</CfButton>
</CfDropdown>
<CfDropdown :items="items" placement="left">
<CfButton variant="secondary">placement = left</CfButton>
</CfDropdown>
<CfDropdown :items="items" placement="right">
<CfButton variant="secondary">placement = right</CfButton>
</CfDropdown>
</div>
</template>
<style scoped>
.adm-grid {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 16px;
padding: 40px 0;
justify-content: center;
}
</style> <script setup>
import { CfButton, CfDropdown } from '@chufix-design/vue';
const items= [
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档' },
{ divider: true, label: '' },
{ key: 'delete', label: '删除', tone: 'danger' },
];
</script>
<template>
<div class="adm-grid">
<CfDropdown :items="items" placement="top">
<CfButton variant="secondary">placement = top</CfButton>
</CfDropdown>
<CfDropdown :items="items" placement="bottom">
<CfButton variant="secondary">placement = bottom</CfButton>
</CfDropdown>
<CfDropdown :items="items" placement="left">
<CfButton variant="secondary">placement = left</CfButton>
</CfDropdown>
<CfDropdown :items="items" placement="right">
<CfButton variant="secondary">placement = right</CfButton>
</CfDropdown>
</div>
</template>
<style scoped>
.adm-grid {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 16px;
padding: 40px 0;
justify-content: center;
}
</style> import { CfButton, CfDropdown } from '@chufix-design/react';
export default function Demo() {
const items: DropdownItem[] = [
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档' },
{ divider: true, label: '' },
{ key: 'delete', label: '删除', tone: 'danger' },
];
return (
<>
<div className="adm-grid">
<CfDropdown items={items} placement="top">
<CfButton variant="secondary">placement = top</CfButton>
</CfDropdown>
<CfDropdown items={items} placement="bottom">
<CfButton variant="secondary">placement = bottom</CfButton>
</CfDropdown>
<CfDropdown items={items} placement="left">
<CfButton variant="secondary">placement = left</CfButton>
</CfDropdown>
<CfDropdown items={items} placement="right">
<CfButton variant="secondary">placement = right</CfButton>
</CfDropdown>
</div>
</>
);
} import { CfButton, CfDropdown } from '@chufix-design/react';
export default function Demo() {
const items= [
{ key: 'rename', label: '重命名' },
{ key: 'archive', label: '归档' },
{ divider: true, label: '' },
{ key: 'delete', label: '删除', tone: 'danger' },
];
return (
<>
<div className="adm-grid">
<CfDropdown items={items} placement="top">
<CfButton variant="secondary">placement = top</CfButton>
</CfDropdown>
<CfDropdown items={items} placement="bottom">
<CfButton variant="secondary">placement = bottom</CfButton>
</CfDropdown>
<CfDropdown items={items} placement="left">
<CfButton variant="secondary">placement = left</CfButton>
</CfDropdown>
<CfDropdown items={items} placement="right">
<CfButton variant="secondary">placement = right</CfButton>
</CfDropdown>
</div>
</>
);
} 禁用状态
disabled 让 Dropdown 整体不可触发;通常同时给触发元素加 disabled 让视觉反馈一致。
背景 视口
<script setup lang="ts">
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';
const items: DropdownItem[] = [
{ key: 'one', label: '选项一' },
{ key: 'two', label: '选项二' },
{ key: 'three', label: '选项三' },
];
</script>
<template>
<div class="demo-row">
<CfDropdown :items="items">
<CfButton variant="secondary">正常 ▾</CfButton>
</CfDropdown>
<CfDropdown :items="items" disabled>
<CfButton variant="secondary" disabled>禁用 ▾</CfButton>
</CfDropdown>
</div>
</template> <script setup>
import { CfButton, CfDropdown } from '@chufix-design/vue';
const items= [
{ key: 'one', label: '选项一' },
{ key: 'two', label: '选项二' },
{ key: 'three', label: '选项三' },
];
</script>
<template>
<div class="demo-row">
<CfDropdown :items="items">
<CfButton variant="secondary">正常 ▾</CfButton>
</CfDropdown>
<CfDropdown :items="items" disabled>
<CfButton variant="secondary" disabled>禁用 ▾</CfButton>
</CfDropdown>
</div>
</template> import { CfButton, CfDropdown } from '@chufix-design/react';
export default function Demo() {
const items: DropdownItem[] = [
{ key: 'one', label: '选项一' },
{ key: 'two', label: '选项二' },
{ key: 'three', label: '选项三' },
];
return (
<>
<div className="demo-row">
<CfDropdown items={items}>
<CfButton variant="secondary">正常 ▾</CfButton>
</CfDropdown>
<CfDropdown items={items} disabled>
<CfButton variant="secondary" disabled>禁用 ▾</CfButton>
</CfDropdown>
</div>
</>
);
} import { CfButton, CfDropdown } from '@chufix-design/react';
export default function Demo() {
const items= [
{ key: 'one', label: '选项一' },
{ key: 'two', label: '选项二' },
{ key: 'three', label: '选项三' },
];
return (
<>
<div className="demo-row">
<CfDropdown items={items}>
<CfButton variant="secondary">正常 ▾</CfButton>
</CfDropdown>
<CfDropdown items={items} disabled>
<CfButton variant="secondary" disabled>禁用 ▾</CfButton>
</CfDropdown>
</div>
</>
);
} 受控模式
通过 v-model:open / open + onOpenChange 让父组件接管展开状态。
常用于由外部事件触发(如键盘快捷键、右键菜单、引导提示)。
背景 视口
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';
const open = ref<boolean>(false);
const items: DropdownItem[] = [
{ key: 'duplicate', label: '复制' },
{ key: 'move', label: '移动' },
{ key: 'delete', label: '删除', tone: 'danger' },
];
</script>
<template>
<div class="demo-row">
<CfButton variant="tertiary" @click="open = !open">
{{ open ? '从外部关闭' : '从外部打开' }}
</CfButton>
<CfDropdown v-model:open="open" :items="items">
<CfButton variant="secondary">受控触发 ▾</CfButton>
</CfDropdown>
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfButton, CfDropdown } from '@chufix-design/vue';
const open = ref<boolean>(false);
const items= [
{ key: 'duplicate', label: '复制' },
{ key: 'move', label: '移动' },
{ key: 'delete', label: '删除', tone: 'danger' },
];
</script>
<template>
<div class="demo-row">
<CfButton variant="tertiary" @click="open = !open">
{{ open ? '从外部关闭' : '从外部打开' }}
</CfButton>
<CfDropdown v-model:open="open" :items="items">
<CfButton variant="secondary">受控触发 ▾</CfButton>
</CfDropdown>
</div>
</template> import { useState } from 'react';
export default function Demo() {
const [open, setOpen] = useState(false);
return (
<>
<CfButton onClick={() => setOpen(!open)}>{open ? '关闭' : '打开'}</CfButton>
<CfDropdown open={open} onOpenChange={setOpen} items={items}>
<CfButton variant="secondary">受控触发 ▾</CfButton>
</CfDropdown>
</>
);
} import { useState } from 'react';
export default function Demo() {
const [open, setOpen] = useState(false);
return (
<>
<CfButton onClick={() => setOpen(!open)}>{open ? '关闭' : '打开'}</CfButton>
<CfDropdown open={open} onOpenChange={setOpen} items={items}>
<CfButton variant="secondary">受控触发 ▾</CfButton>
</CfDropdown>
</>
);
} 带图标的菜单项
DropdownItem.icon 接收一段文本(emoji / 符号),会被原样渲染在 label 左侧。
适合简洁标识;如果需要绘制矢量图标,建议改用嵌套组件方案,把整个菜单项写成自定义 trigger + content 的形式。
背景 视口
<script setup lang="ts">
import { CfButton, CfDropdown } from '@chufix-design/vue';
import type { DropdownItem } from '@chufix-design/vue';
const items: DropdownItem[] = [
{ key: 'edit', label: '编辑', icon: '✏️' },
{ key: 'copy', label: '复制', icon: '📄' },
{ key: 'download', label: '下载', icon: '⬇️' },
{ divider: true, label: '' },
{ key: 'delete', label: '删除', icon: '🗑', tone: 'danger' },
];
</script>
<template>
<CfDropdown :items="items">
<CfButton variant="secondary">带图标的菜单 ▾</CfButton>
</CfDropdown>
</template> <script setup>
import { CfButton, CfDropdown } from '@chufix-design/vue';
const items= [
{ key: 'edit', label: '编辑', icon: '✏️' },
{ key: 'copy', label: '复制', icon: '📄' },
{ key: 'download', label: '下载', icon: '⬇️' },
{ divider: true, label: '' },
{ key: 'delete', label: '删除', icon: '🗑', tone: 'danger' },
];
</script>
<template>
<CfDropdown :items="items">
<CfButton variant="secondary">带图标的菜单 ▾</CfButton>
</CfDropdown>
</template> import { CfButton, CfDropdown } from '@chufix-design/react';
export default function Demo() {
const items: DropdownItem[] = [
{ key: 'edit', label: '编辑', icon: '✏️' },
{ key: 'copy', label: '复制', icon: '📄' },
{ key: 'download', label: '下载', icon: '⬇️' },
{ divider: true, label: '' },
{ key: 'delete', label: '删除', icon: '🗑', tone: 'danger' },
];
return (
<>
<CfDropdown items={items}>
<CfButton variant="secondary">带图标的菜单 ▾</CfButton>
</CfDropdown>
</>
);
} import { CfButton, CfDropdown } from '@chufix-design/react';
export default function Demo() {
const items= [
{ key: 'edit', label: '编辑', icon: '✏️' },
{ key: 'copy', label: '复制', icon: '📄' },
{ key: 'download', label: '下载', icon: '⬇️' },
{ divider: true, label: '' },
{ key: 'delete', label: '删除', icon: '🗑', tone: 'danger' },
];
return (
<>
<CfDropdown items={items}>
<CfButton variant="secondary">带图标的菜单 ▾</CfButton>
</CfDropdown>
</>
);
} API · Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
items | DropdownItem[] | [] | 菜单项配置数组 |
placement | 'top' | 'bottom' | 'left' | 'right' | 'bottom' | 首选位置 |
offset | number | 6 | 与触发元素的间距(px) |
closeOnSelect | boolean | true | 选中后关闭 |
disabled | boolean | false | 禁用触发 |
width | number | string | — | 固定宽度,省略则自适应 |
open | boolean | — | 受控时使用 |
API · DropdownItem
| 属性 | 类型 | 说明 |
|---|---|---|
key | string | 唯一标识(建议提供,便于 select 事件分流) |
label | string | 显示文本 |
icon | string | 可选前置图标(emoji 或符号) |
disabled | boolean | 禁用项 |
divider | boolean | 渲染为分隔线 |
header | boolean | 渲染为分组标题(不可点) |
tone | 'default' | 'danger' | 语义色,danger 用于不可逆操作 |
API · Events
| 事件 | 参数 | 说明 |
|---|---|---|
select (Vue) / onSelect (React) | (item, index) | 选中某一项时触发 |
update:open (Vue) / onOpenChange (React) | (open: boolean) | 展开状态变化 |
反馈与讨论
Dropdown 下拉菜单 的讨论