Menubar 菜单栏

横向排列的一组菜单,像桌面应用的菜单栏。

<script setup lang="ts">
  import { ref } from 'vue'
  import {
    Kbd,
    Menubar,
    MenubarCheckboxItem,
    MenubarItem,
    MenubarMenu,
    MenubarRadioGroup,
    MenubarRadioItem,
    MenubarSeparator,
    MenubarSub,
    Stack,
    Text,
  } from '@hina-ui/vue'

  const last = ref('')
  const wrap = ref(true)
  const numbers = ref(false)
  const theme = ref('system')
</script>

<template>
  <Stack gap="sm" align="start">
    <Menubar label="编辑器菜单">
      <MenubarMenu label="文件">
        <MenubarItem @select="last = '新建'">
          新建
          <template #trailing><Kbd>Ctrl N</Kbd></template>
        </MenubarItem>
        <MenubarItem @select="last = '打开'">
          打开
          <template #trailing><Kbd>Ctrl O</Kbd></template>
        </MenubarItem>
        <MenubarSub label="最近打开">
          <MenubarItem @select="last = '第一章.md'">第一章.md</MenubarItem>
          <MenubarItem @select="last = '大纲.md'">大纲.md</MenubarItem>
        </MenubarSub>
        <MenubarSeparator />
        <MenubarItem @select="last = '保存'">
          保存
          <template #trailing><Kbd>Ctrl S</Kbd></template>
        </MenubarItem>
      </MenubarMenu>
      <MenubarMenu label="编辑">
        <MenubarItem @select="last = '撤销'">
          撤销
          <template #trailing><Kbd>Ctrl Z</Kbd></template>
        </MenubarItem>
        <MenubarItem @select="last = '重做'">
          重做
          <template #trailing><Kbd>Ctrl Y</Kbd></template>
        </MenubarItem>
        <MenubarSeparator />
        <MenubarItem @select="last = '查找'">
          查找
          <template #trailing><Kbd>Ctrl F</Kbd></template>
        </MenubarItem>
      </MenubarMenu>
      <MenubarMenu label="视图">
        <MenubarCheckboxItem v-model:checked="wrap">自动换行</MenubarCheckboxItem>
        <MenubarCheckboxItem v-model:checked="numbers">显示行号</MenubarCheckboxItem>
        <MenubarSeparator />
        <MenubarRadioGroup v-model="theme">
          <MenubarRadioItem value="light">浅色</MenubarRadioItem>
          <MenubarRadioItem value="dark">深色</MenubarRadioItem>
          <MenubarRadioItem value="system">跟随系统</MenubarRadioItem>
        </MenubarRadioGroup>
      </MenubarMenu>
    </Menubar>
    <Text v-if="last" tone="muted" size="sm">选择了:{{ last }}</Text>
  </Stack>
</template>
vue

用法

import { Menubar, MenubarMenu, MenubarItem } from '@hina-ui/vue'
ts

Menubar 是一条横向的栏,里面放若干个 MenubarMenu,每个菜单的 label 是栏上的触发器文字,默认插槽是菜单里的条目。点击触发器展开菜单,展开期间把指针移到另一个触发器上就切换过去;条目、多选项、单选项、分组、标题、分隔线与子菜单都与 DropdownMenu 同一套,只是名字换成 Menubar 开头。

<script setup lang="ts">
  import { Menubar, MenubarItem, MenubarMenu } from '@hina-ui/vue'
</script>

<template>
  <Menubar label="示例菜单">
    <MenubarMenu label="文件">
      <MenubarItem>新建</MenubarItem>
      <MenubarItem>打开</MenubarItem>
      <MenubarItem>保存</MenubarItem>
    </MenubarMenu>
    <MenubarMenu label="编辑">
      <MenubarItem>撤销</MenubarItem>
      <MenubarItem>重做</MenubarItem>
    </MenubarMenu>
    <MenubarMenu label="帮助">
      <MenubarItem>文档</MenubarItem>
      <MenubarItem>关于</MenubarItem>
    </MenubarMenu>
  </Menubar>
</template>
vue

示例

条目

条目的 icon 插槽用于前置图标,trailing 插槽放快捷键这类尾部内容,删除这类不可撤销的操作设置 tone="danger";MenubarLabel 是不可选中的标题,MenubarSeparator 画一条分隔线。

<script setup lang="ts">
  import { Copy, FilePlus, Trash2 } from '@lucide/vue'
  import {
    Kbd,
    Menubar,
    MenubarItem,
    MenubarLabel,
    MenubarMenu,
    MenubarSeparator,
  } from '@hina-ui/vue'
</script>

<template>
  <Menubar label="条目示例">
    <MenubarMenu label="文件">
      <MenubarLabel>当前文档</MenubarLabel>
      <MenubarItem>
        <template #icon><FilePlus /></template>
        新建
        <template #trailing><Kbd>Ctrl N</Kbd></template>
      </MenubarItem>
      <MenubarItem>
        <template #icon><Copy /></template>
        复制一份
      </MenubarItem>
      <MenubarSeparator />
      <MenubarItem tone="danger">
        <template #icon><Trash2 /></template>
        删除
      </MenubarItem>
    </MenubarMenu>
  </Menubar>
</template>
vue

多选项与单选项

MenubarCheckboxItem 是可以同时选中多项的开关,切换后菜单保持展开;一组互斥的选项用 MenubarRadioGroup 包裹。

自动换行开,行号关,字号中

<script setup lang="ts">
  import { ref } from 'vue'
  import {
    Menubar,
    MenubarCheckboxItem,
    MenubarLabel,
    MenubarMenu,
    MenubarRadioGroup,
    MenubarRadioItem,
    MenubarSeparator,
    Stack,
    Text,
  } from '@hina-ui/vue'

  const wrap = ref(true)
  const numbers = ref(false)
  const size = ref('md')
  const names: Record<string, string> = { sm: '小', md: '中', lg: '大' }
</script>

<template>
  <Stack gap="sm" align="start">
    <Menubar label="视图设置">
      <MenubarMenu label="视图">
        <MenubarCheckboxItem v-model:checked="wrap">自动换行</MenubarCheckboxItem>
        <MenubarCheckboxItem v-model:checked="numbers">显示行号</MenubarCheckboxItem>
        <MenubarSeparator />
        <MenubarLabel>字号</MenubarLabel>
        <MenubarRadioGroup v-model="size">
          <MenubarRadioItem value="sm">小</MenubarRadioItem>
          <MenubarRadioItem value="md">中</MenubarRadioItem>
          <MenubarRadioItem value="lg">大</MenubarRadioItem>
        </MenubarRadioGroup>
      </MenubarMenu>
    </Menubar>
    <Text tone="muted" size="sm">
      自动换行{{ wrap ? '开' : '关' }},行号{{ numbers ? '开' : '关' }},字号{{ names[size] }}
    </Text>
  </Stack>
</template>
vue

受控

v-model 绑定当前展开的菜单的 value,没有展开时是空字符串;可以从外部展开某个菜单。

当前展开:无

<script setup lang="ts">
  import { ref } from 'vue'
  import { Button, Inline, Menubar, MenubarItem, MenubarMenu, Stack, Text } from '@hina-ui/vue'

  const open = ref('')
</script>

<template>
  <Stack gap="md" align="start">
    <Inline gap="sm">
      <Button variant="outline" tone="neutral" @click="open = 'file'">展开「文件」</Button>
      <Button variant="outline" tone="neutral" @click="open = 'help'">展开「帮助」</Button>
    </Inline>
    <Menubar v-model="open" label="受控菜单">
      <MenubarMenu label="文件" value="file">
        <MenubarItem>新建</MenubarItem>
        <MenubarItem>打开</MenubarItem>
      </MenubarMenu>
      <MenubarMenu label="帮助" value="help">
        <MenubarItem>文档</MenubarItem>
        <MenubarItem>关于</MenubarItem>
      </MenubarMenu>
    </Menubar>
    <Text tone="muted" size="sm">当前展开:{{ open || '无' }}</Text>
  </Stack>
</template>
vue

不可用的菜单

MenubarMenu 的 disabled 让触发器不可用,条目上的 disabled 只让那一条不可选。

<script setup lang="ts">
  import { Menubar, MenubarItem, MenubarMenu } from '@hina-ui/vue'
</script>

<template>
  <Menubar label="不可用示例">
    <MenubarMenu label="文件">
      <MenubarItem>新建</MenubarItem>
      <MenubarItem disabled>另存为</MenubarItem>
    </MenubarMenu>
    <MenubarMenu label="调试" disabled>
      <MenubarItem>开始调试</MenubarItem>
    </MenubarMenu>
  </Menubar>
</template>
vue

行为

  • 点击触发器展开菜单,再次点击收起;展开期间指针移到另一个触发器即切换到那个菜单。
  • 左右方向键在触发器之间移动,展开期间左右键切换菜单,上下键在条目间移动,回车或者空格选中,Esc 收起并把焦点还给触发器。
  • 菜单打开期间页面照常可以滚动与交互,指针才能在触发器之间移动;选中条目或者点击外部都会收起。

无障碍

  • 根是 role="menubar",用 label 给它一个名称;触发器是带 aria-haspopup 的 menuitem,展开的面板是 role="menu"。
  • 触发器构成一组漫游焦点,Tab 只进出一次,方向键在其中移动。

API

Menubar

属性
类型
默认值
说明
label
string
—
菜单栏的无障碍名称
loop
boolean
true
方向键到达两端时是否绕回
modelValue
string
—
当前展开的菜单的 value,支持双向绑定
class
string
—
追加至根元素的类名

条目与其他子件

MenubarItem、MenubarCheckboxItem、MenubarRadioGroup、MenubarRadioItem、MenubarGroup、MenubarLabel、MenubarSeparator 与 MenubarSub 的属性、插槽与事件与 DropdownMenu 的同名子件完全一致,见 DropdownMenu。