Components
Menubar
A desktop-style menu bar: a row of quiet ghost triggers, each opening the same paper vessel as the menu family. The triggers are our own buttons grafted onto the menu machine's trigger via `asChild` — the machine keeps the trigger element (positioning, focus restore, `data-state`) while the element wears the menubar scope. The popups keep the menu parts untouched, so the menu stylesheet dresses them. Keyboard note: the triggers move between each other with Tab, not arrow keys — cross-menu arrow traversal is out of scope for this version. Inside an open menu the machine handles arrows and Escape.
#Basic usage
Nothing chosen yet.
<script setup lang="ts">
import { Menubar } from "@bysages/vue";
import { ref } from "vue";
const status = ref("Nothing chosen yet.");
const menus = [
{
label: "File",
items: [
{ label: "New document", value: "file.new" },
{ label: "Open recent…", value: "file.open", disabled: true },
{ label: "Save", value: "file.save" },
{ label: "Delete draft", value: "file.delete", danger: true },
],
},
{
label: "Edit",
items: [
{ label: "Undo", value: "edit.undo" },
{ label: "Redo", value: "edit.redo" },
{ label: "Paste without format", value: "edit.paste", disabled: true },
],
},
{
label: "View",
items: [
{ label: "Toggle sidebar", value: "view.sidebar" },
{ label: "Focus mode", value: "view.focus" },
],
},
];
const choose = (value: string) => {
status.value = `Chose ${value}`;
};
</script>
<template>
<div class="w-full">
<Menubar :items="menus" @select="choose" />
<p role="status" class="mt-4 text-sm text-tertiary">
{{ status }}
</p>
</div>
</template>
Draft: The paper-and-ink system
Nothing chosen yet.
<script setup lang="ts">
import { Menubar } from "@bysages/vue";
import { ref } from "vue";
const status = ref("Nothing chosen yet.");
const menus = [
{
label: "File",
items: [
{ label: "New document", value: "file.new" },
{ label: "Open recent…", value: "file.open", disabled: true },
{ label: "Save", value: "file.save" },
{ label: "Delete draft", value: "file.delete", danger: true },
],
},
{
label: "Edit",
items: [
{ label: "Undo", value: "edit.undo" },
{ label: "Redo", value: "edit.redo" },
{ label: "Paste without format", value: "edit.paste", disabled: true },
],
},
{
label: "View",
items: [
{ label: "Toggle sidebar", value: "view.sidebar" },
{ label: "Focus mode", value: "view.focus" },
],
},
];
const choose = (value: string) => {
status.value = `Chose ${value}`;
};
</script>
<template>
<section class="bg-surface-1 border border-border rounded-lg p-4">
<h2 class="mb-4 font-serif text-lg">Draft: The paper-and-ink system</h2>
<Menubar :items="menus" @select="choose" />
<p role="status" class="mt-4 text-sm text-tertiary">
{{ status }}
</p>
</section>
</template>
#Props
#Menubar
A desktop-style menu bar: a row of quiet ghost triggers, each opening
the same paper vessel as the menu family. The triggers are our own
buttons grafted onto the menu machine's trigger via asChild — the
machine keeps the trigger element (positioning, focus restore,
data-state) while the element wears the menubar scope. The popups
keep the menu parts untouched, so the menu stylesheet dresses them.
Keyboard note: the triggers move between each other with Tab, not arrow keys — cross-menu arrow traversal is out of scope for this version. Inside an open menu the machine handles arrows and Escape.
| Prop | Type | Default | Description |
|---|---|---|---|
items | Array as PropType<MenubarGroup[]> | () => [] | — |
onSelect | Function as PropType<(value: string) => void> | undefined | — |