跳转到内容

Menu 菜单 ​

从触发器打开一组操作命令。

用法 ​

从按钮打开一组操作

组件结构 ​

加粗的是必需部件。

data-scope="menu":trigger · positioner · content · item · item-text · item-indicator · item-description · item-shortcut · item-suffix · separator · group · group-label · arrow

示例 ​

图标与快捷键 ​

为常用命令补充识别信息

分组 ​

使用标题与分隔线组织命令

子菜单 ​

将相关操作收进下一层

破坏性命令 ​

用语气把删除一类命令与其余区分开

说明与快捷键 ​

只交数据,副文本与按键提示自动落位

设计指引 ​

何时使用 ​

  • 收纳次要操作、账户命令或多级命令。

何时不用 ​

特性 ​

  • collection 可直接生成条目、分组、标记位、说明、快捷键提示和分隔线。
  • 条目可逐条声明语气,删除一类命令自带该族字色与高亮底。
  • 快捷键提示贴行尾,与说明同档同色;它是纯装饰,读屏从命令文字取意。
  • 支持方向键、首字符检索、禁用条目和多级子菜单。
  • 子菜单使用安全三角避免指针斜向移动时误关闭。
  • 条目可组合图标、文字、说明和快捷键提示。
  • 行首与行尾两格各有逐条钩子,只想加个图标不必把整条重搭;item 插槽仍是整条的接管口。
  • 选中命令后发出根级 select 并关闭菜单链。

组合 ​

  • 触发器通常使用中性按钮,避免与页面主操作争夺层级。

最佳实践 ​

  • 破坏性命令放在末尾、与普通命令分隔,并同时给出 danger 语气和图标。
  • 条目使用简短的动宾短语。
  • 仅为已注册的快捷键显示提示。

反模式 ​

  • 不要用命令菜单代替持久选择控件。
  • 不要在菜单中放置长段说明或复杂表单。

API 参考 ​

产物 ​

层值
自定义元素<xh-menu>
Vue 组件XhMenuArrow XhMenuContent XhMenuGroup XhMenuGroupLabel XhMenuItem XhMenuItemDescription XhMenuItemIndicator XhMenuItemShortcut XhMenuItemSuffix XhMenuItemText XhMenuPositioner XhMenuRoot XhMenuSeparator XhMenuSub XhMenuSubTrigger XhMenuTrigger
组合式函数useMenu
状态机menuMachine
皮肤@xihan-ui/styles/menu.css

Props ​

属性类型必填说明
collectionMenuNode[]条目数据,显示文本、禁用、逐条语气与分组的事实源。提供后条目部件只需声明 value。 未提供时回到这些事实都写在条目部件上的方式(语气写成条目的 data-tone)。
openboolean展开态,提供即受控;受控下内部不自行修改,只发 onOpenChange。
defaultOpenboolean
placementPlacement
offsetnumber
loopboolean方向键到达末尾是否回绕,默认 true。
dirDirection文字方向,默认 ltr。
toneTone整张菜单的语气:brand / neutral / success / warning / danger / info。 只为浮层与作者放进来的内容备好该族颜色,不下发给条目——条目保持中性档, 逐条的语气写在 collection 的 tone 上(见 MenuNode)。
sizeSize尺寸:sm / md / lg,决定条目高度、内边距与字号档位。
typeaheadboolean首字符连打检索,默认开启。
disabledboolean整张菜单禁用:触发器不再展开,条目全部为 aria-disabled。
translationsPartial<MenuTranslations>
submenuboolean本菜单是另一张菜单的子菜单:触发器渲染为父菜单的条目形态 (经 getSubmenuTriggerProps),默认落位改为侧向,悬停触发默认开启。
openOnHoverboolean悬停触发:进入触发器延时展开、经安全三角离开才收起。子菜单默认开启,普通菜单默认关闭。
hoverOpenDelaynumber悬停到展开的延时(ms),默认 100。
hoverCloseDelaynumber离开到收起的延时(ms),也是安全三角中的停滞上限,默认 300。
onOpenChange(details: MenuOpenChangeDetails) => voidopen 变化回调。
onSelect(details: MenuSelectDetails) => void条目被选中;菜单随之关闭。

collection 的元素。

字段类型必填说明
valuestring是
labelstring展示文本;默认回退为 value。
disabledboolean条目禁用:方向键跳过它,但它仍可聚焦、仍是导航起点。
toneTone该条命令自身动作的性质:删除写 danger、停用写 warning。不写即与其余条目同档。 只换字色与悬停 / 按下的面,不改字重与缩进,也不表达选中或校验;禁用压过它。 红字不是唯一通道,破坏性命令仍要配图标。整张菜单的 tone 不下发给条目。
indicatorstring标记位文字(勾选符号等装饰);未提供时本条不铺 item-indicator。
descriptionstring副文本,写入 item-description 部件;未提供时本条不铺该部件。
shortcutstring快捷键提示,写入 item-shortcut 部件;未提供时本条不铺该部件。 纯装饰:读屏从条目文字取意,不念它;只为真正注册了的组合写提示。
groupstring归属分组的身份值;相邻同值的条目收进同一个 group 部件。未提供时本条直接落在 content 上。
groupLabelstring分组标题文字,取本组首个提供它的条目;本组无人提供时不铺 group-label。
separatorBeforeboolean本条之前绘制一条分隔线;写在首条上不产出分隔线。本条领头一个分组时,分隔线绘制在分组外。

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
open-changeMenuOpenChangeDetailsopen 状态变化;detail 为 { open: boolean }
selectMenuSelectDetails条目被选中(菜单随之关闭);detail 为 { value: string }

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhMenuRootdefaultMenuRootSlotProps
XhMenuRoottrigger—
XhMenuRootitemMenuNodeMeta整条的接管口:写了它,代铺的各格一概不铺,作者自己放置部件
XhMenuRootitem-prefixMenuNodeMeta只接管行首那一格,其余槽照旧由数据铺
XhMenuRootitem-suffixMenuNodeMeta只接管行尾那一格(计数、徽标、次级图标),其余槽照旧由数据铺
XhMenuSubdefaultMenuSubSlotProps

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhMenuGroupvaluestring是
XhMenuItemvaluestring是
XhMenuItemdisabledboolean默认交给 connect 查询 collection,写死 false 会覆盖数据中的禁用。
XhMenuPositionercontainer() => Element | null浮层挂载的容器;未提供时按全局配置,再未提供时挂载到 body。
XhMenuRoottriggerReactNode触发器中放置的内容;只提供 collection 时由它承载。
XhMenuRoottriggerAsChildboolean只提供 collection 时,trigger 给出的节点直接作为触发器使用,不再外包一个 button。
XhMenuRootrenderItem(node: MenuNodeMeta) => ReactNode每个条目的自定义内容;未提供时使用 collection 中的 label。
XhMenuRootrenderItemPrefix(node: MenuNodeMeta) => ReactNode只接管条目行首那一格;其余槽仍由数据铺。
XhMenuRootrenderItemSuffix(node: MenuNodeMeta) => ReactNode只接管条目行尾那一格(计数、徽标、次级图标);其余槽仍由数据铺。
XhMenuRootchildrenSlotChildren<MenuRootSlotProps>
XhMenuSubvaluestring是它在父菜单中的条目身份。
XhMenuSubdisabledboolean
XhMenuSubcollectionMenuNode[]
XhMenuSubplacementPlacement
XhMenuSuboffsetnumber
XhMenuSubloopboolean
XhMenuSubopenOnHoverboolean
XhMenuSubhoverOpenDelaynumber
XhMenuSubhoverCloseDelaynumber
XhMenuSubdirDirection文字方向;默认继承父层。子层被迁移到浮层落点,无法继承父层的方向。
XhMenuSubtoneTone语气;默认继承父层。子层是浮层落点下的同级节点,CSS 私有槽无法继承。
XhMenuSubsizeSize尺寸;默认继承父层,理由同 tone。
XhMenuSubchildrenSlotChildren<MenuSubSlotProps>

状态 ​

公开状态写入 data-state。

部件取值
trigger'open' | 'closed'
positioner'open' | 'closed'
content'open' | 'closed'
submenu-trigger'open' | 'closed'

以下名称仅用于内部状态机。

状态:open · closed

事件:OPEN · TOGGLE · CLOSE · PRESS.START · PRESS.END · CONTROLLED.OPEN · CONTROLLED.CLOSE · ITEM.FOCUS · FOCUS.CLEAR · ITEM.LOST · ITEM.SELECT

判据:isOpenControlled · canPress

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
openboolean
disabledboolean整张菜单是否禁用。
collectionreadonly MenuNodeMeta[]由 collection 推导的条目元信息,按数据顺序排列;未提供 collection 时为空数组。
focusedValuestring | null焦点锚点;收起时为 null。
setOpen(next: boolean) => void
getTriggerProps() => T['button']
getPositionerProps() => T['element']
getContentProps() => T['element']
getItemProps(props: MenuItemProps) => T['element']
getItemTextProps(props: MenuItemProps) => T['element']
getItemIndicatorProps(props: MenuItemProps) => T['element']
getItemDescriptionProps(props: MenuItemProps) => T['element']
getItemShortcutProps(props: MenuItemProps) => T['element']
getItemSuffixProps(props: MenuItemProps) => T['element']
getSubmenuTriggerProps(props: MenuItemProps) => T['element']子菜单触发条目(submenu 模式):既是父菜单中的一条 item(value 是它在父菜单 中的身份,父层的方向键与高亮照常识别它),又是本子菜单的触发器(aria-haspopup、 悬停 / 点击 / 右方向键展开)。父层的选中会跳过带 aria-haspopup 的条目。
getSeparatorProps() => T['element']
getGroupProps(props: MenuGroupProps) => T['element']
getGroupLabelProps(props: MenuGroupProps) => T['element']
getArrowProps() => T['element']

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Enter / Space / ArrowDownfocus in trigger展开菜单并把焦点落到首个可用条目
ArrowUpfocus in trigger展开菜单并把焦点落到末个可用条目
ArrowDownopen, focus in content焦点移到下一个条目(禁用项跳过、尽头按 loop 回绕)
ArrowUpopen, focus in content焦点移到上一个条目(禁用项跳过、尽头按 loop 回绕)
Homeopen, focus in content焦点移到首个可用条目
Endopen, focus in content焦点移到末个可用条目
Enter / Spacefocus in item, not disabled派发选中详情并关闭菜单,焦点归还 trigger
Enter / Spaceheld in item, not disabled按住期间该条目投影 data-pressed,与指针 :active 同一副按压面;抬起、失焦或菜单收起撤下
Escapeopen关闭菜单并把焦点归还 trigger
Tab / Shift+Tabopen关闭菜单,焦点不归还 trigger,按 Tab 序列自然离开

ARIA ​

以下属性由 connect 生成。

部件属性值
triggeraria-controlscontent 部件的 id
triggeraria-expanded'true' | 'false'
triggeraria-haspopup'menu'
contentaria-hidden!open || undefined
contentaria-labelprops.translations.content
contentaria-labelledbytrigger 部件的 id | undefined
contentrole'menu'
itemaria-disabled'true' | 'false'
itemrole'menuitem'
item-indicatoraria-hidden'true'
item-shortcutaria-hidden'true'
separatoraria-orientation'horizontal'
separatorrole'separator'
grouparia-labelledbygroup-label 部件的 id
grouprole'group'
arrowaria-hidden'true'
submenu-triggeraria-controlscontent 部件的 id
submenu-triggeraria-disabled'true' | 'false'
submenu-triggeraria-expanded'true' | 'false'
submenu-triggeraria-haspopup'menu'
submenu-triggerrole'menuitem'

样式参考 ​

皮肤 ​

@xihan-ui/styles/menu.css 使用 [data-scope="menu"][data-part="trigger"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。

forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
triggerdata-disabled''(条件成立时才出现)
triggerdata-state'open' | 'closed'
positionerdata-hidden''(条件成立时才出现)
positionerdata-placement定位引擎算出的实际落位
positionerdata-positioned''(条件成立时才出现)
positionerdata-state'open' | 'closed'
contentdata-placement定位引擎算出的实际落位
contentdata-sizeprops.size
contentdata-state'open' | 'closed'
contentdata-toneprops.tone
contentdata-xh-material'frosted'
itemdata-disabled''(条件成立时才出现)
itemdata-highlighted''(条件成立时才出现)
itemdata-pressed''(条件成立时才出现)
itemdata-tonemetaOf.get(item.value)?.tone
itemdata-xh-collection-context'overlay'
itemdata-xh-collection-item''
itemdata-xh-collection-sizeprops.size
item-textdata-disabled''(条件成立时才出现)
item-textdata-highlighted''(条件成立时才出现)
item-textdata-xh-collection-slot'text'
item-indicatordata-disabled''(条件成立时才出现)
item-indicatordata-highlighted''(条件成立时才出现)
item-indicatordata-xh-collection-slot'prefix'
item-descriptiondata-disabled''(条件成立时才出现)
item-descriptiondata-highlighted''(条件成立时才出现)
item-descriptiondata-xh-collection-slot'description'
item-shortcutdata-disabled''(条件成立时才出现)
item-shortcutdata-highlighted''(条件成立时才出现)
item-shortcutdata-xh-collection-slot'shortcut'
item-suffixdata-disabled''(条件成立时才出现)
item-suffixdata-highlighted''(条件成立时才出现)
item-suffixdata-xh-collection-slot'suffix'
separatordata-xh-collection-separator''
arrowdata-placement定位引擎算出的实际落位
submenu-triggerdata-disabled''(条件成立时才出现)
submenu-triggerdata-in-path''(条件成立时才出现)
submenu-triggerdata-pressed''(条件成立时才出现)
submenu-triggerdata-state'open' | 'closed'
submenu-triggerdata-xh-collection-context'overlay'
submenu-triggerdata-xh-collection-item''
submenu-triggerdata-xh-collection-sizeprops.size

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-menu-arrow-sizearrow--xh-_overlay-arrow-sizedefault--xh-overlay-arrow-sizemenu 的 arrow 部件 --xh-_overlay-arrow-size 覆盖槽。
--xh-menu-backdropcontent-webkit-backdrop-filter
backdrop-filter
xh-material=frosted--xh-_material-backdropmenu 的 content 部件 -webkit-backdrop-filter、backdrop-filter 覆盖槽。
--xh-menu-borderarrow
content
borderdefault
not([data-xh-action-control])
xh-material=frosted
--xh-_material-border
--xh-material-frosted-border
menu 的 arrow、content 部件 border 覆盖槽。
--xh-menu-content-bgarrow
content
backgrounddefault
not([data-xh-action-control])
xh-material=frosted
--xh-_material-bg
--xh-material-frosted-bg
menu 的 arrow、content 部件 background 覆盖槽。
--xh-menu-content-fgcontentcolornot([data-xh-action-control])
xh-material=frosted
--xh-_material-fgmenu 的 content 部件 color 覆盖槽。
--xh-menu-content-gapcontentgapdefault--xh-list-option-gapmenu 的 content 部件 gap 覆盖槽。
--xh-menu-content-pxcontentpadding-inlinedefault--xh-surface-pad-xsmenu 的 content 部件 padding-inline 覆盖槽。
--xh-menu-content-pycontentpadding-blockdefault--xh-surface-pad-xsmenu 的 content 部件 padding-block 覆盖槽。
--xh-menu-content-radiuscontentborder-radiusdefault--xh-shape-overlaymenu 的 content 部件 border-radius 覆盖槽。
--xh-menu-content-shadowcontentbox-shadownot([data-xh-action-control])
xh-material=frosted
--xh-_material-shadowmenu 的 content 部件 box-shadow 覆盖槽。
--xh-menu-group-gapgroupgapdefault--xh-list-option-gapmenu 的 group 部件 gap 覆盖槽。
--xh-menu-group-label-fggroup-labelcolordefault--xh-material-frosted-fg-mutedmenu 的 group-label 部件 color 覆盖槽。
--xh-menu-group-label-font-sizegroup-labelfont-sizedefault--xh-text-caption-sizemenu 的 group-label 部件 font-size 覆盖槽。
--xh-menu-group-label-font-weightgroup-labelfont-weightdefault--xh-font-weight-mediummenu 的 group-label 部件 font-weight 覆盖槽。
--xh-menu-group-label-pxgroup-labelpadding-inlinedefault--xh-_menu-item-pxmenu 的 group-label 部件 padding-inline 覆盖槽。
--xh-menu-group-label-pygroup-labelpadding-blockdefault--xh-space-1menu 的 group-label 部件 padding-block 覆盖槽。
--xh-menu-highlightcontentbackgroundnot([data-xh-action-control])
xh-material=frosted
--xh-_material-highlightmenu 的 content 部件 background 覆盖槽。
--xh-menu-icon-sizecontent--xh-icon-sizedefault
size=lg
size=sm
--xh-glyph-size-lg
--xh-glyph-size-md
--xh-glyph-size-sm
menu 的 content 部件 --xh-icon-size 覆盖槽。
--xh-menu-item-bg-activeitembackground-colorin-path--xh-bg-subtlemenu 的 item 部件 background-color 覆盖槽。
--xh-menu-item-bg-hoveritembackground-colordisabled
error
highlighted
hover
is(:focus-visible, [data-highlighted])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
--xh-bg-subtlemenu 的 item 部件 background-color 覆盖槽。
--xh-menu-item-bg-presseditembackground-colordisabled
error
is(:active, [data-pressed])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
--xh-bg-subtle-hovermenu 的 item 部件 background-color 覆盖槽。
--xh-menu-item-description-fgitem-descriptioncolordefault--xh-material-frosted-fg-mutedmenu 的 item-description 部件 color 覆盖槽。
--xh-menu-item-description-font-sizeitem-descriptionfont-sizedefault--xh-text-caption-sizemenu 的 item-description 部件 font-size 覆盖槽。
--xh-menu-item-fgitemcolordefault
disabled
error
highlighted
hover
in-path
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
--xh-material-frosted-fgmenu 的 item 部件 color 覆盖槽。
--xh-menu-item-font-sizeitemfont-sizedefault--xh-_menu-font-sizemenu 的 item 部件 font-size 覆盖槽。
--xh-menu-item-gapitemgapdefault--xh-_menu-item-gapmenu 的 item 部件 gap 覆盖槽。
--xh-menu-item-indicator-fgitem-indicatorcolordefault--xh-_tonemenu 的 item-indicator 部件 color 覆盖槽。
--xh-menu-item-indicator-sizeitem-indicator--xh-icon-size
block-size
inline-size
default--xh-control-indicator-sizemenu 的 item-indicator 部件 --xh-icon-size、block-size、inline-size 覆盖槽。
--xh-menu-item-leadingitemline-heightdefault--xh-leading-normalmenu 的 item 部件 line-height 覆盖槽。
--xh-menu-item-pxitempadding-inlinedefault--xh-_menu-item-pxmenu 的 item 部件 padding-inline 覆盖槽。
--xh-menu-item-pyitempadding-blockdefault--xh-_menu-item-pymenu 的 item 部件 padding-block 覆盖槽。
--xh-menu-item-radiusitemborder-radiusdefault--xh-shape-controlmenu 的 item 部件 border-radius 覆盖槽。
--xh-menu-layerpositionerz-indexdefault--xh-_layermenu 的 positioner 部件 z-index 覆盖槽。
--xh-menu-max-hcontentmax-block-sizedefault--xh-overlay-menu-max-hmenu 的 content 部件 max-block-size 覆盖槽。
--xh-menu-max-wcontentmax-inline-sizedefault--xh-overlay-max-wmenu 的 content 部件 max-inline-size 覆盖槽。
--xh-menu-min-wcontentmin-inline-sizedefault--xh-overlay-menu-min-wmenu 的 content 部件 min-inline-size 覆盖槽。
--xh-menu-separator-colorseparatorbackgrounddefault--xh-material-frosted-separatormenu 的 separator 部件 background 覆盖槽。
--xh-menu-separator-myseparatormargin-blockdefault--xh-space-0_5menu 的 separator 部件 margin-block 覆盖槽。
--xh-menu-separator-radiusseparatorborder-radiusdefault--xh-shape-pillmenu 的 separator 部件 border-radius 覆盖槽。
--xh-menu-separator-thicknessseparatorblock-sizedefault--xh-stroke-thinmenu 的 separator 部件 block-size 覆盖槽。
--xh-menu-submenu-indicator-fgitembackground-colordefault--xh-material-frosted-fg-mutedmenu 的 item 部件 background-color 覆盖槽。
--xh-menu-submenu-indicator-sizeitemblock-size
inline-size
default--xh-control-indicator-sizemenu 的 item 部件 block-size、inline-size 覆盖槽。
--xh-menu-trigger-bg-activetriggerbackgrounddisabled
not([data-disabled])
state=open
--xh-bg-subtlemenu 的 trigger 部件 background 覆盖槽。

动效 ​

动效角色:按压 · 状态 · 出现(锚定列表)(见动效规范)。

共享关键帧 xh-overlay-slide-in · xh-overlay-slide-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立。时长与缓动读动效令牌,改令牌即改全局节奏。

皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;另有按 dir 分支的规则。

Released under The MIT License