メニューバー
stimeo--menubar
アプリのメニューバー。トップ項目をロービングし各 role=menu を開く。型先読み対応。
stimeo--menubar コントローラは WAI-ARIA の Menubar パターン(1 段)を実装する。トップ項目はロービングタブインデックスで 1 つの Tab ストップになり、左右キーで移動する。 ArrowDown/Enter/Space でメニューを開いて先頭項目へ(ArrowUp は末尾項目へ)、メニュー内は上下キーで移動(ループ)し、Home/End で両端へ、印字可能文字で型先読みする。メニューを開いた状態で左右キーを押すと隣のトップメニューへ移る。Escape は閉じて対応トップ項目へフォーカスを戻し、項目の実行・Tab・外側クリックでも閉じる。トップ項目とメニューは aria-controls / id で対応づくため、マークアップ順は自由。メニューの配置は利用側 CSS、動的配置は opt-in の stimeo-ui/positioning に委譲する。
キーボード操作
| キー | 動作 |
|---|---|
| → / ← | トップ項目間を移動(ループ)。メニューが開いていれば隣を開く。 |
| ↓ / Enter / Space | フォーカス中のトップ項目のメニューを開き先頭項目へ(ArrowUp は末尾)。 |
| ↓ / ↑ | 開いているメニュー内を移動(ループ)。 |
| Home / End | 先頭/末尾の項目へ。 |
| 印字可能文字 | 開いているメニュー内で次の先頭一致項目へ型先読み。 |
| Esc | メニューを閉じて対応トップ項目へフォーカスを戻す。 |
<%# Markup for the menubar demo.
Top-level items directly under role="menubar" each open a role="menu". The library
handles roving between top items (left/right keys), opening a menu with
ArrowDown/Enter, up/down movement and typeahead within a menu, moving sideways to
an adjacent menu, returning on Escape, and closing on outside click / Tab. Top
items ↔ menus are linked via aria-controls / id. The look is in demo.css. %>
<div
class="menubar"
data-controller="stimeo--menubar"
role="menubar"
aria-label="<%= t("components.menubar.demo.label") %>"
>
<button
type="button"
class="menubar__top"
role="menuitem"
aria-haspopup="menu"
aria-expanded="false"
aria-controls="menubar-file"
data-stimeo--menubar-target="top"
data-action="click->stimeo--menubar#toggle keydown->stimeo--menubar#onTopKeydown">
<%= t("components.menubar.demo.menus.file.label") %>
</button>
<ul
id="menubar-file"
class="menubar__menu"
role="menu"
aria-label="<%= t("components.menubar.demo.menus.file.label") %>"
hidden
data-stimeo--menubar-target="menu"
>
<% %w[new open save].each do |action| %>
<li role="none">
<button
type="button"
class="menubar__item"
role="menuitem"
tabindex="-1"
data-stimeo--menubar-target="item"
data-action="click->stimeo--menubar#activate keydown->stimeo--menubar#onItemKeydown">
<%= t("components.menubar.demo.menus.file.items.#{action}") %>
</button>
</li>
<% end %>
</ul>
<button
type="button"
class="menubar__top"
role="menuitem"
aria-haspopup="menu"
aria-expanded="false"
aria-controls="menubar-edit"
data-stimeo--menubar-target="top"
data-action="click->stimeo--menubar#toggle keydown->stimeo--menubar#onTopKeydown">
<%= t("components.menubar.demo.menus.edit.label") %>
</button>
<ul
id="menubar-edit"
class="menubar__menu"
role="menu"
aria-label="<%= t("components.menubar.demo.menus.edit.label") %>"
hidden
data-stimeo--menubar-target="menu"
>
<% %w[cut copy paste].each do |action| %>
<li role="none">
<button
type="button"
class="menubar__item"
role="menuitem"
tabindex="-1"
data-stimeo--menubar-target="item"
data-action="click->stimeo--menubar#activate keydown->stimeo--menubar#onItemKeydown">
<%= t("components.menubar.demo.menus.edit.items.#{action}") %>
</button>
</li>
<% end %>
</ul>
</div>
/*
* Presentation-only styles for the menubar demo.
* The library expresses open/close via the menu's hidden, the active top item via
* aria-expanded, and the active in-menu item via focus. Menu placement (directly
* below the top item) is static and the consumer's CSS responsibility; use
* stimeo-ui/positioning for dynamic flip.
*/
.menubar {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border: 1px solid var(--border-strong);
border-radius: 0.375rem;
background: var(--surface-subtle);
/* Positioning context for the menus' absolute placement. */
position: relative;
width: fit-content;
}
.menubar__top {
padding: 0.4rem 0.75rem;
font: inherit;
cursor: pointer;
border: 0;
border-radius: 0.25rem;
background: transparent;
color: var(--color-text);
}
.menubar__top:hover,
.menubar__top[aria-expanded="true"] {
background: var(--surface-subtle);
}
.menubar__menu {
position: absolute;
top: calc(100% + 0.25rem);
z-index: 10;
min-width: 10rem;
margin: 0;
padding: 0.25rem;
list-style: none;
background: var(--surface-card);
border: 1px solid var(--border-strong);
border-radius: 0.375rem;
box-shadow: 0 8px 24px rgb(15 23 42 / 0.12);
}
.menubar__menu[hidden] {
display: none;
}
.menubar__item {
display: block;
width: 100%;
padding: 0.45rem 0.6rem;
font: inherit;
text-align: left;
cursor: pointer;
border: 0;
border-radius: 0.25rem;
background: transparent;
color: var(--color-text);
}
.menubar__item:hover,
.menubar__item:focus {
background: var(--vital-100);
outline: none;
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--menubar"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
top
必須
|
トップレベルの menuitem ボタン。ロービングタブインデックスで一つの Tab ストップを形成する。 | data-stimeo--menubar-target="top" |
menu
必須
|
aria-controls/id でトップ項目に紐付く role=menu のポップアップ。開いている間表示される。 |
data-stimeo--menubar-target="menu" |
item
必須
|
メニュー内の menuitem。矢印キーとタイプアヘッドで項目間のフォーカスを移動する。 | data-stimeo--menubar-target="item" |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
activate
|
項目を実行した後にメニューを閉じ、所有元のトップ項目にフォーカスを戻す。 | stimeo--menubar#activate |
onItemKeydown
|
メニュー項目にフォーカスがある間のキーボード操作を処理する(矢印、Home/End、隣接メニューへの移動、Escape、Tab、タイプアヘッド)。 | stimeo--menubar#onItemKeydown |
onTopKeydown
|
トップ項目にフォーカスがある間のキーボード操作を処理する(矢印で移動・開く、Home/End、Escape で閉じる)。 | stimeo--menubar#onTopKeydown |
toggle
|
クリックされたトップ項目のメニューを開閉する。 | stimeo--menubar#toggle |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-expanded |
トップ項目 | そのメニューの開閉状態。 |
tabindex |
トップ項目 | アクティブは 0、他は -1(ロービング)。 |
hidden |
メニュー | 閉じているときは付与。 |