菜单

点锚点打开菜单。点一条之后,选中换到那一条,菜单关闭;再打开时选中还在那一条。箭头只是装饰,点了也是关闭。这轮没有子菜单。

组只在两组及以上时才需要。勾用 md-icon。

标签何时使用
<md-gen-menu>菜单。负责锚点、打开关闭和配色
<md-gen-menu-group>一张卡。两组及以上时包住条目
<md-gen-menu-item>一行条目
import 'genv4/gen-menu/gen-menu.js';
import 'genv4/gen-menu/gen-menu-group.js';
import 'genv4/gen-menu/gen-menu-item.js';
import 'genv4/icon/icon.js';

锚点和菜单放在同一个 position: relative 的容器里。输入框、按钮、图标按钮只当锚点。点锚点切换 open 就能打开和关闭,点条目后菜单自己关。选中由页面设置:在菜单的 click 里给点中的那条加 selected,其余去掉。

下面代码里的 <svg data-icon="…"> 是占位,换成自己的图标即可。本页用一段脚本按名字填入路径:

const ICONS = {
  star: {viewBox: '0 0 24 24', d: STAR_OUTLINE}, // 空心星,一条 path
  'star-filled': {viewBox: '0 0 24 24', d: STAR_DISK + STAR_INNER}, // 外圆加内圆,evenodd
  arrow: {viewBox: '0 0 20 20', d: ARROW},
};

for (const svg of document.querySelectorAll('svg[data-icon]')) {
  const {viewBox, d} = ICONS[svg.dataset.icon];
  svg.setAttribute('viewBox', viewBox);
  svg.innerHTML = `<path d="${d}"></path>`;
}

一组,圆角 16。第 3 条选中,头部的勾只在选中时出现,未选中不占位。max-height: 196px 露出 4 行,下面的条目在菜单里滚。

<div style="position: relative">
  <md-text-input id="height-anchor" label="菜单" readonly value="三条"></md-text-input>
  <md-gen-menu id="height-menu" anchor="height-anchor" quick default-focus="none" anchor-corner="end-start" menu-corner="start-start" y-offset="4" style="max-height: 196px">
    <md-gen-menu-item><md-icon slot="start" selected aria-hidden="true">check</md-icon>一条</md-gen-menu-item>
    <md-gen-menu-item><md-icon slot="start" selected aria-hidden="true">check</md-icon>两条</md-gen-menu-item>
    <md-gen-menu-item selected><md-icon slot="start" selected aria-hidden="true">check</md-icon>三条</md-gen-menu-item>
    <md-gen-menu-item><md-icon slot="start" selected aria-hidden="true">check</md-icon>四条</md-gen-menu-item>
    <md-gen-menu-item><md-icon slot="start" selected aria-hidden="true">check</md-icon>五条</md-gen-menu-item>
    <md-gen-menu-item><md-icon slot="start" selected aria-hidden="true">check</md-icon>六条</md-gen-menu-item>
    <md-gen-menu-item><md-icon slot="start" selected aria-hidden="true">check</md-icon>七条</md-gen-menu-item>
  </md-gen-menu>
</div>

<script type="module">
  const anchor = document.querySelector('#height-anchor');
  const menu = document.querySelector('#height-menu');
  anchor.addEventListener('click', () => { menu.open = !menu.open; });
</script>

一组。未选中是空心星,选中换成实心星,文字位置不动。输入框不放放大镜。

<div style="position: relative">
  <md-text-input id="swap-anchor" label="菜单" readonly value="两条"></md-text-input>
  <md-gen-menu id="swap-menu" anchor="swap-anchor" quick default-focus="none" anchor-corner="end-start" menu-corner="start-start" y-offset="4">
    <md-gen-menu-item><svg slot="start" data-icon="star"></svg><svg slot="start" selected data-icon="star-filled"></svg>一条</md-gen-menu-item>
    <md-gen-menu-item selected><svg slot="start" data-icon="star"></svg><svg slot="start" selected data-icon="star-filled"></svg>两条</md-gen-menu-item>
    <md-gen-menu-item><svg slot="start" data-icon="star"></svg><svg slot="start" selected data-icon="star-filled"></svg>三条</md-gen-menu-item>
    <md-gen-menu-item><svg slot="start" data-icon="star"></svg><svg slot="start" selected data-icon="star-filled"></svg>四条</md-gen-menu-item>
  </md-gen-menu>
</div>

<script type="module">
  const anchor = document.querySelector('#swap-anchor');
  const menu = document.querySelector('#swap-menu');
  anchor.addEventListener('click', () => { menu.open = !menu.open; });
</script>

一组。没有预先选中的行。头部是空心星,尾部是三角箭头。按钮用现有的浅色实心按钮。

标签
<div style="position: relative">
  <md-filled-tonal-button id="icons-anchor"><svg slot="icon" data-icon="star"></svg>标签</md-filled-tonal-button>
  <md-gen-menu id="icons-menu" anchor="icons-anchor" quick default-focus="none" anchor-corner="end-start" menu-corner="start-start" y-offset="4">
    <md-gen-menu-item><svg slot="start" data-icon="star"></svg><svg slot="end" data-icon="arrow"></svg>一条</md-gen-menu-item>
    <md-gen-menu-item><svg slot="start" data-icon="star"></svg><svg slot="end" data-icon="arrow"></svg>两条</md-gen-menu-item>
    <md-gen-menu-item><svg slot="start" data-icon="star"></svg><svg slot="end" data-icon="arrow"></svg>三条</md-gen-menu-item>
    <md-gen-menu-item><svg slot="start" data-icon="star"></svg><svg slot="end" data-icon="arrow"></svg>四条</md-gen-menu-item>
  </md-gen-menu>
</div>

<script type="module">
  const anchor = document.querySelector('#icons-anchor');
  const menu = document.querySelector('#icons-menu');
  anchor.addEventListener('click', () => { menu.open = !menu.open; });
</script>

两组,每张卡圆角 8,中间 2px 缝。尾部是箭头,选中那条换成勾。配色是 theme="vibrant"。锚点用现有的实心图标按钮。

<div style="position: relative">
  <md-filled-icon-button id="groups-anchor" aria-label="打开菜单"><svg data-icon="star"></svg></md-filled-icon-button>
  <md-gen-menu id="groups-menu" anchor="groups-anchor" quick default-focus="none" anchor-corner="end-start" menu-corner="start-start" y-offset="4" theme="vibrant">
    <md-gen-menu-group>
      <md-gen-menu-item><svg slot="end" data-icon="arrow"></svg><md-icon slot="end" selected aria-hidden="true">check</md-icon>一条</md-gen-menu-item>
      <md-gen-menu-item selected><svg slot="end" data-icon="arrow"></svg><md-icon slot="end" selected aria-hidden="true">check</md-icon>两条</md-gen-menu-item>
      <md-gen-menu-item><svg slot="end" data-icon="arrow"></svg><md-icon slot="end" selected aria-hidden="true">check</md-icon>三条</md-gen-menu-item>
    </md-gen-menu-group>
    <md-gen-menu-group>
      <md-gen-menu-item><svg slot="end" data-icon="arrow"></svg><md-icon slot="end" selected aria-hidden="true">check</md-icon>四条</md-gen-menu-item>
      <md-gen-menu-item><svg slot="end" data-icon="arrow"></svg><md-icon slot="end" selected aria-hidden="true">check</md-icon>五条</md-gen-menu-item>
    </md-gen-menu-group>
  </md-gen-menu>
</div>

<script type="module">
  const anchor = document.querySelector('#groups-anchor');
  const menu = document.querySelector('#groups-menu');
  anchor.addEventListener('click', () => { menu.open = !menu.open; });
</script>

一组。一条只禁用,一条选中并且禁用。禁用的整条 38% 透明,点了不换选中,也不关菜单。选中底还在。

标签
<div style="position: relative">
  <md-filled-tonal-button id="disabled-anchor"><svg slot="icon" data-icon="star"></svg>标签</md-filled-tonal-button>
  <md-gen-menu id="disabled-menu" anchor="disabled-anchor" quick default-focus="none" anchor-corner="end-start" menu-corner="start-start" y-offset="4">
    <md-gen-menu-item>一条</md-gen-menu-item>
    <md-gen-menu-item disabled>禁用</md-gen-menu-item>
    <md-gen-menu-item selected disabled>选中并且禁用</md-gen-menu-item>
    <md-gen-menu-item>四条</md-gen-menu-item>
  </md-gen-menu>
</div>

<script type="module">
  const anchor = document.querySelector('#disabled-anchor');
  const menu = document.querySelector('#disabled-menu');
  anchor.addEventListener('click', () => { menu.open = !menu.open; });
</script>

菜单、组和条目的属性不同,下面分表列出。

属性HTML 属性类型默认值说明
anchoranchorstring''锚点元素的 id。和锚点要在同一个根节点里。
positioningpositioning"absolute" | "fixed" | "document" | "popover"'absolute'相对谁定位。absolute 相对锚点的定位父级,fixed 相对窗口,document 相对文档,popover 用顶层,浏览器不支持时退回 fixed。
quickquickbooleanfalse跳过打开和关闭动画。
hasOverflowhas-overflowbooleanfalse为真时不裁剪内容,也不出现滚动条。
openopenbooleanfalse菜单是否打开。
xOffsetx-offsetnumber0相对锚点的水平偏移,单位像素。从左到右时正数向右,从右到左时正数向左。
yOffsety-offsetnumber0相对锚点的垂直偏移,单位像素。正数向下。
noHorizontalFlipno-horizontal-flipbooleanfalse为真时,菜单在水平方向超出视口也不翻转。
noVerticalFlipno-vertical-flipbooleanfalse为真时,菜单在垂直方向超出视口也不翻转。
typeaheadDelaytypeahead-delaynumber200键盘按字跳转时,两次按键之间最多隔多久,单位毫秒。超时后重新开始匹配。
anchorCorneranchor-cornerCornerCorner.END_START锚点用来对齐的角,写成「块方向-行方向」,例如 end-start。
menuCornermenu-cornerCornerCorner.START_START菜单用来对齐的角,写成「块方向-行方向」,例如 start-start。
stayOpenOnOutsideClickstay-open-on-outside-clickbooleanfalse为真时,点菜单和锚点以外的地方不关闭。
stayOpenOnFocusoutstay-open-on-focusoutbooleanfalse为真时,焦点离开菜单不关闭。
skipRestoreFocusskip-restore-focusbooleanfalse为真时,关闭后不把焦点还回打开前的元素。
defaultFocusdefault-focusFocusStateFocusState.FIRST_ITEM打开后焦点落在哪里。none 不动,list-root 落在列表,first-item 第一条,last-item 最后一条。
noNavigationWrapno-navigation-wrapbooleanfalse为真时,键盘移到头或尾不再绕回。
themetheme"standard" | "vibrant"'standard'配色。standard 或 vibrant,不是页面的亮色和暗色。
anchorElementHTMLElementnull锚点元素。写了 anchor 时以那个 id 为准。
itemsMenuItem[]undefined菜单里的条目,按页面顺序。
方法参数返回值说明
show无void打开菜单。
close无void关闭菜单。
reposition无void按当前锚点重新计算位置。
事件类型冒泡穿透说明
openingEvent否否打开动画开始前
openedEvent否否菜单已经打开
closingEvent否否关闭动画开始前
closedEvent否否菜单已经关闭

一张圆角卡。没有属性,颜色跟着菜单的 theme。

属性HTML 属性类型默认值说明
disableddisabledbooleanfalse禁用。整条 38% 透明,点了不关菜单,键盘跳过。
typetypeMenuItemType'menuitem'读屏角色。menuitem、option、button、link。不改外观。
hrefhrefstring''链接地址。写了以后这一条渲染成链接。
targettargetstring''链接打开位置。
relrelstring''链接的 rel。
referrerPolicyreferrerpolicystring''链接的 referrer policy。
keepOpenkeep-openbooleanfalse为真时,点这一条不关菜单。
selectedselectedbooleanfalse选中。只由页面设置,组件不互斥。同一侧带 selected 属性的图标跟着显示。
typeaheadTextstringundefined键盘按字跳转时用来匹配的文字。不写就用条目里的文字。

适用于 <md-gen-menu-item>。组只有默认插槽,放条目。

名称说明
默认一行文字。过长时末尾省略号,不撑宽菜单
start头部图标,20px,颜色跟文字。元素带 selected 时只在条目选中时显示。同一侧还有不带 selected 的图标,就互相替换。这一侧只有带 selected 的图标时,未选中不占位
end尾部图标,规则和 start 相同

用组件 CSS 变量覆盖。下面只列出 Sass 里标记为支持的变量。

变量默认值
--md-gen-menu-card-color--md-sys-color-surface-container
--md-gen-menu-card-color-vibrant--md-sys-color-tertiary-container
--md-gen-menu-card-padding-block2px
--md-gen-menu-card-padding-inline0px
--md-gen-menu-card-shape16px
--md-gen-menu-gap2px
--md-gen-menu-scrollbar-color--md-sys-color-outline
--md-gen-menu-scrollbar-size4px
--md-gen-menu-shadowdrop-shadow(0 1px 3px #0000004d) drop-shadow(0 4px 8px #00000026)
--md-gen-menu-width208px

全部样式:gen-menu/internal/_gen-menu.scss

变量默认值
--md-gen-menu-group-container-color--md-sys-color-surface-container
--md-gen-menu-group-container-color-vibrant--md-sys-color-tertiary-container
--md-gen-menu-group-padding-block2px
--md-gen-menu-group-padding-inline0px
--md-gen-menu-group-shape16px
--md-gen-menu-group-shape-multi8px

全部样式:gen-menu/internal/_gen-menu-group.scss

变量默认值
--md-gen-menu-item-disabled-opacity0.38
--md-gen-menu-item-focus-opacity0.1
--md-gen-menu-item-focus-ring-color--md-sys-color-secondary
--md-gen-menu-item-focus-ring-selected-shape14px
--md-gen-menu-item-focus-ring-shape6px
--md-gen-menu-item-focus-ring-width2px
--md-gen-menu-item-hover-color--md-sys-color-on-surface
--md-gen-menu-item-hover-color-vibrant--md-sys-color-on-tertiary-container
--md-gen-menu-item-hover-opacity0.08
--md-gen-menu-item-icon-size20px
--md-gen-menu-item-label-color--md-sys-color-on-surface
--md-gen-menu-item-label-color-vibrant--md-sys-color-on-tertiary-container
--md-gen-menu-item-label-line-height20px
--md-gen-menu-item-label-size14px
--md-gen-menu-item-label-tracking0.1px
--md-gen-menu-item-label-weight500
--md-gen-menu-item-padding-block2px
--md-gen-menu-item-padding-inline4px
--md-gen-menu-item-row-gap8px
--md-gen-menu-item-row-height44px
--md-gen-menu-item-row-padding-inline12px
--md-gen-menu-item-selected-container-color--md-sys-color-primary-fixed
--md-gen-menu-item-selected-container-color-vibrant--md-sys-color-tertiary
--md-gen-menu-item-selected-hover-color--md-sys-color-on-surface
--md-gen-menu-item-selected-hover-color-vibrant--md-sys-color-on-tertiary
--md-gen-menu-item-selected-label-color--md-sys-color-on-primary-fixed
--md-gen-menu-item-selected-label-color-vibrant--md-sys-color-on-tertiary
--md-gen-menu-item-selected-shape12px
--md-gen-menu-item-shape4px

全部样式:gen-menu/internal/_gen-menu-item.scss

开发指南
快速开始主题问题与支持
Components
Ai Input提示横幅 Alert banner按钮 ButtonCheckboxChipsDialogsFloating action button (FAB)图标 Icon图标按钮 Icon ButtonLists菜单 MenuMenus弹窗 Modal步骤条 Process stepperProgress indicatorsRadioRipple搜索栏 Search BarSelectSlidersSwitchTabsText field文本输入 Text Input