DropdownMenu
功能特性
- 支持受控和非受控模式。
- 支持子菜单,并可配置阅读方向。
- 支持菜单项、标签和菜单项组。
- 支持可勾选项(单选或多选),包含可选的不确定状态。
- 支持模态和非模态模式。
- 可自定义侧面位置、对齐方式、偏移量和碰撞处理。
- 可选渲染指向箭头。
- 完全管理焦点。
- 完整的键盘导航。
- 支持类型搜索(Typeahead)。
- 高度可定制的关闭和层级行为。
安装
通过命令行安装该组件。
$ npm add reka-ui结构
导入所有部件并将它们组合在一起。
<script setup lang="ts">
import {
DropdownMenuArrow,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuItemIndicator,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuRoot,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger />
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuLabel />
<DropdownMenuItem />
<DropdownMenuGroup>
<DropdownMenuItem />
</DropdownMenuGroup>
<DropdownMenuCheckboxItem>
<DropdownMenuItemIndicator />
</DropdownMenuCheckboxItem>
<DropdownMenuRadioGroup>
<DropdownMenuRadioItem>
<DropdownMenuItemIndicator />
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger />
<DropdownMenuPortal>
<DropdownMenuSubContent />
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuArrow />
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>API 参考
Root
包含下拉菜单的所有部件。
| Prop | Default | Type |
|---|---|---|
defaultOpen | booleanThe open state of the dropdown menu when it is initially rendered. Use when you do not need to control its open state. | |
dir | 'ltr' | 'rtl'The reading direction of the combobox when applicable. If omitted, inherits globally from | |
modal | true | booleanThe modality of the dropdown menu. When set to |
open | booleanThe controlled open state of the menu. Can be used as |
| Emit | Payload |
|---|---|
update:open | [payload: boolean]Event handler called when the open state of the submenu changes. |
| Slots (default) | Payload |
|---|---|
open | booleanCurrent open state |
Trigger
用于切换下拉菜单的按钮。默认情况下,DropdownMenuContent 将相对于触发器定位自身。
| Prop | Default | Type |
|---|---|---|
as | 'button' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-disabled] | 存在时表示已禁用 |
Portal
使用时,将内容部件传送到 body 中。
| Prop | Default | Type |
|---|---|---|
defer | booleanDefer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+) | |
disabled | booleanDisable teleport and render the component inline | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
to | string | HTMLElementVue native teleport component prop |
Content
当下拉菜单打开时弹出的组件。
| Prop | Default | Type |
|---|---|---|
align | 'start' | 'center' | 'end'The preferred alignment against the trigger. May change when collisions occur. | |
alignFlip | booleanFlip alignment when colliding with boundary.
May only occur when | |
alignOffset | numberAn offset in pixels from the | |
arrowPadding | numberThe padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. | |
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
avoidCollisions | booleanWhen | |
collisionBoundary | Element | (Element | null)[] | nullThe element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. | |
collisionPadding | number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { top: 20, left: 20 }. | |
disableUpdateOnLayoutShift | booleanWhether to disable the update position for the content when the layout shifted. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
hideWhenDetached | booleanWhether to hide the content when the trigger becomes fully occluded. | |
loop | booleanWhen | |
positionStrategy | 'fixed' | 'absolute'The type of CSS position property to use. | |
prioritizePosition | booleanForce content to be position within the viewport. Might overlap the reference element, which may not be desired. | |
reference | ReferenceElementThe custom element or virtual element that will be set as the reference to position the floating element. If provided, it will replace the default anchor element. | |
side | 'top' | 'right' | 'bottom' | 'left'The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled. | |
sideFlip | booleanFlip to the opposite side when colliding with boundary. | |
sideOffset | numberThe distance in pixels from the trigger. | |
sticky | 'partial' | 'always'The sticky behavior on the align axis. | |
updatePositionStrategy | 'always' | 'optimized'Strategy to update the position of the floating element on every animation frame. |
| Emit | Payload |
|---|---|
closeAutoFocus | [event: Event]Event handler called when auto-focusing on close. Can be prevented. |
escapeKeyDown | [event: KeyboardEvent]Event handler called when the escape key is down. Can be prevented. |
focusOutside | [event: FocusOutsideEvent]Event handler called when the focus moves outside of the |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent]Event handler called when an interaction happens outside the |
pointerDownOutside | [event: PointerDownOutsideEvent]Event handler called when a |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
[data-orientation] | "vertical" | "horizontal" |
| CSS Variable | Description |
|---|---|
--reka-dropdown-menu-content-transform-origin | 根据内容和箭头的位置/偏移计算出的 transform-origin |
--reka-dropdown-menu-content-available-width | 触发器与边界边缘之间的剩余宽度 |
--reka-dropdown-menu-content-available-height | 触发器与边界边缘之间的剩余高度 |
--reka-dropdown-menu-trigger-width | 触发器的宽度 |
--reka-dropdown-menu-trigger-height | 触发器的高度 |
Arrow
一个可选的箭头元素,与下拉菜单一起渲染。这可用于帮助在视觉上将触发器与 DropdownMenuContent 联系起来。必须在 DropdownMenuContent 内部渲染。
| Prop | Default | Type |
|---|---|---|
as | 'svg' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
height | 5 | numberThe height of the arrow in pixels. |
rounded | booleanWhen | |
width | 10 | numberThe width of the arrow in pixels. |
Item
包含下拉菜单项的组件。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the |
| Emit | Payload |
|---|---|
select | [event: Event]Event handler called when the user selects an item (via mouse or keyboard). |
| Data Attribute | Value |
|---|---|
[data-orientation] | "vertical" | "horizontal" |
[data-highlighted] | 存在时表示已高亮 |
[data-disabled] | 存在时表示已禁用 |
Group
用于对多个 DropdownMenuItem 进行分组。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Label
用于渲染标签。它将无法使用箭头键聚焦。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
CheckboxItem
一个可以像复选框一样控制和渲染的项。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
modelValue | false | true | 'indeterminate'The controlled checked state of the item. Can be used as | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the |
| Emit | Payload |
|---|---|
select | [event: Event]Event handler called when the user selects an item (via mouse or keyboard). |
update:modelValue | [payload: boolean]Event handler called when the value changes. |
| Data Attribute | Value |
|---|---|
[data-state] | "checked" | "unchecked" | "indeterminate" |
[data-highlighted] | 存在时表示已高亮 |
[data-disabled] | 存在时表示已禁用 |
RadioGroup
用于对多个 DropdownMenuRadioItem 进行分组。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
modelValue | stringThe value of the selected item in the group. |
| Emit | Payload |
|---|---|
update:modelValue | [payload: string]Event handler called when the value changes. |
RadioItem
一个可以像单选按钮一样控制和渲染的项。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the | |
value* | stringThe unique value of the item. |
| Emit | Payload |
|---|---|
select | [event: Event]Event handler called when the user selects an item (via mouse or keyboard). |
| Data Attribute | Value |
|---|---|
[data-state] | "checked" | "unchecked" | "indeterminate" |
[data-highlighted] | 存在时表示已高亮 |
[data-disabled] | 存在时表示已禁用 |
ItemIndicator
当父级 DropdownMenuCheckboxItem 或 DropdownMenuRadioItem 被勾选时渲染。您可以直接样式化此元素,或将其用作包装器来放入图标,或两者兼而有之。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| Data Attribute | Value |
|---|---|
[data-state] | "checked" | "unchecked" | "indeterminate" |
Separator
用于在下拉菜单中视觉上分隔项。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Sub
包含子菜单的所有部件。
| Prop | Default | Type |
|---|---|---|
defaultOpen | booleanThe open state of the dropdown menu when it is initially rendered. Use when you do not need to control its open state. | |
open | booleanThe controlled open state of the menu. Can be used as |
| Emit | Payload |
|---|---|
update:open | [payload: boolean]Event handler called when the open state of the submenu changes. |
| Slots (default) | Payload |
|---|---|
open | booleanCurrent open state |
SubTrigger
打开子菜单的项。必须在 DropdownMenuSub 内部渲染。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
textValue | stringOptional text used for typeahead purposes. By default the typeahead behavior will use the |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-highlighted] | 存在时表示已高亮 |
[data-disabled] | 存在时表示已禁用 |
| CSS Variable | Description |
|---|---|
--reka-dropdown-menu-content-transform-origin | 根据内容和箭头的位置/偏移计算出的 transform-origin |
--reka-dropdown-menu-content-available-width |
触发器与边界边缘之间的剩余宽度
|
--reka-dropdown-menu-content-available-height | 触发器与边界边缘之间的剩余高度 |
--reka-dropdown-menu-trigger-width | 触发器的宽度 |
--reka-dropdown-menu-trigger-height | 触发器的高度 |
SubContent
当子菜单打开时弹出的组件。必须在 DropdownMenuSub 内部渲染。
| Prop | Default | Type |
|---|---|---|
alignFlip | booleanFlip alignment when colliding with boundary.
May only occur when | |
alignOffset | numberAn offset in pixels from the | |
arrowPadding | numberThe padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. | |
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
avoidCollisions | booleanWhen | |
collisionBoundary | Element | (Element | null)[] | nullThe element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. | |
collisionPadding | number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { top: 20, left: 20 }. | |
disableUpdateOnLayoutShift | booleanWhether to disable the update position for the content when the layout shifted. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
hideWhenDetached | booleanWhether to hide the content when the trigger becomes fully occluded. | |
loop | booleanWhen | |
positionStrategy | 'fixed' | 'absolute'The type of CSS position property to use. | |
prioritizePosition | booleanForce content to be position within the viewport. Might overlap the reference element, which may not be desired. | |
reference | ReferenceElementThe custom element or virtual element that will be set as the reference to position the floating element. If provided, it will replace the default anchor element. | |
sideFlip | booleanFlip to the opposite side when colliding with boundary. | |
sideOffset | numberThe distance in pixels from the trigger. | |
sticky | 'partial' | 'always'The sticky behavior on the align axis. | |
updatePositionStrategy | 'always' | 'optimized'Strategy to update the position of the floating element on every animation frame. |
| Emit | Payload |
|---|---|
closeAutoFocus | [event: Event]Event handler called when auto-focusing on close. Can be prevented. |
entryFocus | [event: Event]Event handler called when container is being focused. Can be prevented. |
escapeKeyDown | [event: KeyboardEvent]Event handler called when the escape key is down. Can be prevented. |
focusOutside | [event: FocusOutsideEvent]Event handler called when the focus moves outside of the |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent]Event handler called when an interaction happens outside the |
openAutoFocus | [event: Event]Event handler called when auto-focusing on open. Can be prevented. |
pointerDownOutside | [event: PointerDownOutsideEvent]Event handler called when a |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
[data-orientation] | "vertical" | "horizontal" |
示例
带子菜单
您可以通过组合使用 DropdownMenuSub 及其部件来创建子菜单。
<script setup lang="ts">
import {
DropdownMenuArrow,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem>…</DropdownMenuItem>
<DropdownMenuItem>…</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger>子菜单 →</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>子菜单项</DropdownMenuItem>
<DropdownMenuItem>子菜单项</DropdownMenuItem>
<DropdownMenuArrow />
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>…</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>带禁用项
您可以通过 data-disabled 属性为禁用的项添加特殊样式。
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem class="DropdownMenuItem" disabled>
…
</DropdownMenuItem>
<DropdownMenuItem class="DropdownMenuItem"> … </DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>/* styles.css */
.DropdownMenuItem[data-disabled] {
color: gainsboro;
}带分隔符
使用 Separator 部件在项之间添加分隔符。
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem>…</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>…</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>…</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>带标签
使用 Label 部件来帮助标记一个区域。
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuLabel>标签</DropdownMenuLabel>
<DropdownMenuItem>…</DropdownMenuItem>
<DropdownMenuItem>…</DropdownMenuItem>
<DropdownMenuItem>…</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>带复选框项
使用 CheckboxItem 部件添加一个可以勾选的项。
<script setup lang="ts">
import { Icon } from "@iconify/vue";
import {
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuItemIndicator,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "reka-ui";
import { ref } from "vue";
const checked = ref(false);
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem>…</DropdownMenuItem>
<DropdownMenuItem>…</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem v-model="checked">
<DropdownMenuItemIndicator>
<Icon icon="radix-icons:check" />
</DropdownMenuItemIndicator>
复选框项
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>带单选按钮项
使用 RadioGroup 和 RadioItem 部件添加一个可以在其他项中被选中的项。
<script setup lang="ts">
import { Icon } from "@iconify/vue";
import {
DropdownMenuContent,
DropdownMenuItemIndicator,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuRoot,
DropdownMenuTrigger,
} from "reka-ui";
import { ref } from "vue";
const color = ref(false);
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuRadioGroup v-model="color">
<DropdownMenuRadioItem value="red">
<DropdownMenuItemIndicator>
<Icon icon="radix-icons:check" />
</DropdownMenuItemIndicator>
红色
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="blue">
<DropdownMenuItemIndicator>
<Icon icon="radix-icons:check" />
</DropdownMenuItemIndicator>
蓝色
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="green">
<DropdownMenuItemIndicator>
<Icon icon="radix-icons:check" />
</DropdownMenuItemIndicator>
绿色
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>带复杂项
您可以在 Item 部件中添加额外的装饰性元素,例如图像。
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem>
<img src="…" />
Adolfo Hess
</DropdownMenuItem>
<DropdownMenuItem>
<img src="…" />
Miyah Myles
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>限制内容/子内容的大小
您可能希望限制内容(或子内容)的宽度,使其与触发器(或子触发器)的宽度匹配。您可能还希望限制其高度,使其不超过视口。
我们暴露了几个 CSS 自定义属性,例如 --reka-dropdown-menu-trigger-width 和 --reka-dropdown-menu-content-available-height 来支持这一点。使用它们来约束内容的尺寸。
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent class="DropdownMenuContent" :side-offset="5">
…
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>/* styles.css */
.DropdownMenuContent {
width: var(--reka-dropdown-menu-trigger-width);
max-height: var(--reka-dropdown-menu-content-available-height);
}感知起始点的动画
我们暴露了一个 CSS 自定义属性 --reka-dropdown-menu-content-transform-origin。使用它可以根据 side、sideOffset、align、alignOffset 以及任何碰撞情况,从计算出的起始点对内容进行动画处理。
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent class="DropdownMenuContent"> … </DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>/* styles.css */
.DropdownMenuContent {
transform-origin: var(--reka-dropdown-menu-content-transform-origin);
animation: scaleIn 0.5s ease-out;
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}感知碰撞的动画
我们暴露了 data-side 和 data-align 属性。它们的值将在运行时根据碰撞情况而变化。使用它们来创建感知碰撞和方向的动画。
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuTrigger,
} from "reka-ui";
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>…</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent class="DropdownMenuContent"> … </DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>/* styles.css */
.DropdownMenuContent {
animation-duration: 0.6s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.DropdownMenuContent[data-side="top"] {
animation-name: slideUp;
}
.DropdownMenuContent[data-side="bottom"] {
animation-name: slideDown;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}可访问性
遵循 菜单按钮 WAI-ARIA 设计模式,并使用 roving tabindex 来管理菜单项之间的焦点移动。
键盘交互
| Key | Description |
|---|---|
Space |
当焦点在 DropdownMenuTrigger 上时,打开下拉菜单并聚焦第一个项。
当焦点在某个项上时,激活聚焦的项。 |
Enter |
当焦点在 DropdownMenuTrigger 上时,打开下拉菜单并聚焦第一个项。
当焦点在某个项上时,激活聚焦的项。 |
ArrowDown |
当焦点在 DropdownMenuTrigger 上时,打开下拉菜单。
当焦点在某个项上时,将焦点移动到下一个项。 |
ArrowUp |
当焦点在某个项上时,将焦点移动到上一个项。
|
ArrowRightArrowLeft |
当焦点在 DropdownMenuSubTrigger 上时,根据阅读方向打开或关闭子菜单。
|
Esc |
关闭下拉菜单并将焦点移动到 DropdownMenuTrigger。
|
自定义 API
通过将原始部件抽象到您自己的组件中来创建您自己的 API。
抽象箭头和项指示器
此示例抽象了 DropdownMenuArrow 和 DropdownMenuItemIndicator 部件。它还包装了 CheckboxItem 和 RadioItem 的实现细节。
用法
<script setup lang="ts">
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "./your-dropdown-menu";
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger>DropdownMenu 触发器</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>项</DropdownMenuItem>
<DropdownMenuLabel>标签</DropdownMenuLabel>
<DropdownMenuGroup>组</DropdownMenuGroup>
<DropdownMenuCheckboxItem>复选框项</DropdownMenuCheckboxItem>
<DropdownMenuSeparator>分隔符</DropdownMenuSeparator>
<DropdownMenuRadioGroup>
<DropdownMenuRadioItem>单选按钮项</DropdownMenuRadioItem>
<DropdownMenuRadioItem>单选按钮项</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</template>实现
export { default as DropdownMenuCheckboxItem } from "DropdownMenuCheckboxItem.vue";
// your-dropdown-menu.ts
export { default as DropdownMenuContent } from "DropdownMenuContent.vue";
export { default as DropdownMenuRadioItem } from "DropdownMenuRadioItem.vue";
export {
DropdownMenuRoot as DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "reka-ui";<!-- DropdownMenuContent.vue -->
<script setup lang="ts">
import type {
DropdownMenuContentEmits,
DropdownMenuContentProps,
} from "reka-ui";
import {
DropdownMenuContent,
DropdownMenuPortal,
useForwardPropsEmits,
} from "reka-ui";
const props = defineProps<DropdownMenuContentProps>();
const emits = defineEmits<DropdownMenuContentEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<DropdownMenuPortal>
<DropdownMenuContent v-bind="forwarded">
<slot />
</DropdownMenuContent>
</DropdownMenuPortal>
</template><!-- DropdownMenuCheckboxItem.vue -->
<script setup lang="ts">
import type {
DropdownMenuCheckboxItemEmits,
DropdownMenuCheckboxItemProps,
} from "reka-ui";
import { CheckIcon } from "@radix-icons/vue";
import {
DropdownMenuCheckboxItem,
DropdownMenuItemIndicator,
useForwardPropsEmits,
} from "reka-ui";
const props = defineProps<DropdownMenuCheckboxItemProps>();
const emits = defineEmits<DropdownMenuCheckboxItemEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<DropdownMenuCheckboxItem v-bind="forwarded">
<span>
<DropdownMenuItemIndicator>
<CheckIcon />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuCheckboxItem>
</template><!-- DropdownMenuRadioItem.vue -->
<script setup lang="ts">
import type {
DropdownMenuRadioItemEmits,
DropdownMenuRadioItemProps,
} from "reka-ui";
import { DotFilledIcon } from "@radix-icons/vue";
import {
DropdownMenuItemIndicator,
DropdownMenuRadioItem,
useForwardPropsEmits,
} from "reka-ui";
const props = defineProps<DropdownMenuRadioItemProps>();
const emits = defineEmits<DropdownMenuRadioItemEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<DropdownMenuRadioItem v-bind="forwarded">
<span>
<DropdownMenuItemIndicator>
<DotFilledIcon />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuRadioItem>
</template>