A comprehensive context menu component library for Vue 3 applications with themes, keyboard navigation, and programmatic control
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
The MenuBar component provides a traditional horizontal menu bar interface similar to desktop applications. It supports both expanded and collapsed (mini) modes, with dropdown context menus for each menu item.
Horizontal menu bar component that displays menu items and shows dropdown context menus when clicked.
declare component MenuBar {
props: {
/** Menu bar configuration options */
options: MenuBarOptions;
};
slots: {
/** Content before menu items */
prefix: {};
/** Content after menu items */
suffix: {};
};
}
interface MenuBarOptions extends Omit<MenuOptions, 'x'|'y'|'getContainer'> {
/** Whether the menu bar is in collapsed/mini mode */
mini?: boolean;
/** Popup direction for dropdown menus in collapsed state */
barPopDirection?: MenuPopDirection;
/** Array of top-level menu items */
items?: MenuItem[];
/** Menu theme */
theme?: string;
/** Other MenuOptions properties... */
}Usage Examples:
<template>
<!-- Basic menu bar -->
<menu-bar :options="basicMenuOptions" />
<!-- Menu bar with prefix/suffix slots -->
<menu-bar :options="advancedMenuOptions">
<template #prefix>
<div class="app-logo">
<img src="/logo.png" alt="App Logo" />
</div>
</template>
<template #suffix>
<div class="menu-actions">
<button @click="showSettings">Settings</button>
<button @click="showHelp">Help</button>
</div>
</template>
</menu-bar>
<!-- Collapsed/mini menu bar -->
<menu-bar :options="miniMenuOptions" />
</template>
<script setup>
import { ref } from 'vue';
// Basic menu bar configuration
const basicMenuOptions = ref({
theme: 'default',
items: [
{
label: 'File',
children: [
{ label: 'New', shortcut: 'Ctrl+N', onClick: createNew },
{ label: 'Open', shortcut: 'Ctrl+O', onClick: openFile },
{ label: 'Save', shortcut: 'Ctrl+S', onClick: saveFile },
{ divided: true, label: 'Exit', onClick: exitApp }
]
},
{
label: 'Edit',
children: [
{ label: 'Undo', shortcut: 'Ctrl+Z', onClick: undo },
{ label: 'Redo', shortcut: 'Ctrl+Y', onClick: redo },
{ divided: true, label: 'Cut', shortcut: 'Ctrl+X', onClick: cut },
{ label: 'Copy', shortcut: 'Ctrl+C', onClick: copy },
{ label: 'Paste', shortcut: 'Ctrl+V', onClick: paste }
]
},
{
label: 'View',
children: [
{ label: 'Zoom In', shortcut: 'Ctrl++', onClick: zoomIn },
{ label: 'Zoom Out', shortcut: 'Ctrl+-', onClick: zoomOut },
{ label: 'Reset Zoom', shortcut: 'Ctrl+0', onClick: resetZoom }
]
}
]
});
// Advanced menu bar with dynamic content
const advancedMenuOptions = ref({
theme: 'win10',
minWidth: 150,
keyboardControl: true,
items: [
{
label: 'File',
children: [
{ label: 'New Project', onClick: createProject },
{
label: 'Recent Projects',
children: getRecentProjects() // Dynamic submenu
},
{ divided: true, label: 'Import', onClick: importData },
{ label: 'Export', onClick: exportData }
]
},
{
label: 'Tools',
children: [
{ label: 'Preferences', onClick: showPreferences },
{ label: 'Extensions', onClick: showExtensions },
{
label: 'Developer',
children: [
{ label: 'Console', onClick: showConsole },
{ label: 'Debugger', onClick: showDebugger }
]
}
]
}
]
});
// Mini/collapsed menu bar
const miniMenuOptions = ref({
mini: true,
theme: 'flat dark',
barPopDirection: 'bl',
items: [
{
label: 'Menu', // This label is not shown in mini mode
children: [
{ label: 'File Operations', children: getFileOperations() },
{ label: 'Edit Operations', children: getEditOperations() },
{ label: 'View Options', children: getViewOptions() },
{ divided: true, label: 'Settings', onClick: showSettings },
{ label: 'About', onClick: showAbout }
]
}
]
});
function getRecentProjects() {
return [
{ label: 'Project Alpha', onClick: () => openProject('alpha') },
{ label: 'Project Beta', onClick: () => openProject('beta') },
{ label: 'Project Gamma', onClick: () => openProject('gamma') }
];
}
</script>The MenuBar component supports two display modes:
Shows all top-level menu items horizontally across the bar.
interface ExpandedMenuBarConfig {
mini: false; // or undefined
items: MenuItem[]; // Multiple top-level items displayed
}Shows a single menu button that opens a dropdown containing all menu items.
interface MiniMenuBarConfig {
mini: true;
items: MenuItem[]; // All items shown in single dropdown
barPopDirection?: MenuPopDirection; // Direction for dropdown
}Mode Comparison:
<template>
<!-- Expanded mode - shows "File", "Edit", "View" horizontally -->
<menu-bar :options="expandedOptions" />
<!-- Mini mode - shows single hamburger menu button -->
<menu-bar :options="miniOptions" />
</template>
<script setup>
const expandedOptions = {
mini: false,
items: [
{ label: 'File', children: [...] },
{ label: 'Edit', children: [...] },
{ label: 'View', children: [...] }
]
};
const miniOptions = {
mini: true,
barPopDirection: 'bl',
items: [
{
label: 'Menu', // Not displayed in mini mode
children: [
{ label: 'File', children: [...] },
{ label: 'Edit', children: [...] },
{ label: 'View', children: [...] }
]
}
]
};
</script><template>
<menu-bar :options="responsiveMenuOptions" />
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
const screenWidth = ref(window.innerWidth);
const responsiveMenuOptions = computed(() => ({
mini: screenWidth.value < 768, // Mini mode on mobile
theme: screenWidth.value < 768 ? 'flat' : 'default',
barPopDirection: 'bl',
items: getMenuItems()
}));
function updateScreenWidth() {
screenWidth.value = window.innerWidth;
}
onMounted(() => {
window.addEventListener('resize', updateScreenWidth);
});
onUnmounted(() => {
window.removeEventListener('resize', updateScreenWidth);
});
</script><template>
<menu-bar :options="stateAwareMenuOptions" />
</template>
<script setup>
import { ref, computed } from 'vue';
const isProjectOpen = ref(false);
const hasSelection = ref(false);
const canUndo = ref(false);
const canRedo = ref(false);
const stateAwareMenuOptions = computed(() => ({
theme: 'win10',
items: [
{
label: 'File',
children: [
{ label: 'New Project', onClick: createProject },
{ label: 'Open Project', onClick: openProject },
{
label: 'Close Project',
disabled: !isProjectOpen.value,
onClick: closeProject
},
{ divided: true, label: 'Save', shortcut: 'Ctrl+S', onClick: save },
{
label: 'Save As...',
disabled: !isProjectOpen.value,
onClick: saveAs
}
]
},
{
label: 'Edit',
children: [
{
label: 'Undo',
shortcut: 'Ctrl+Z',
disabled: !canUndo.value,
onClick: undo
},
{
label: 'Redo',
shortcut: 'Ctrl+Y',
disabled: !canRedo.value,
onClick: redo
},
{ divided: true },
{
label: 'Cut',
shortcut: 'Ctrl+X',
disabled: !hasSelection.value,
onClick: cut
},
{
label: 'Copy',
shortcut: 'Ctrl+C',
disabled: !hasSelection.value,
onClick: copy
}
]
}
]
}));
</script><template>
<menu-bar
:options="customMenuOptions"
class="custom-menu-bar"
>
<template #prefix>
<div class="brand-section">
<img src="/logo.svg" alt="Brand" class="brand-logo" />
<span class="brand-text">My App</span>
</div>
</template>
<template #suffix>
<div class="user-section">
<span class="user-name">{{ currentUser.name }}</span>
<button @click="showUserMenu" class="user-menu-btn">
<img :src="currentUser.avatar" alt="User" class="user-avatar" />
</button>
</div>
</template>
</menu-bar>
</template>
<style scoped>
.custom-menu-bar {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 0 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.brand-section {
display: flex;
align-items: center;
gap: 8px;
}
.brand-logo {
width: 24px;
height: 24px;
}
.user-section {
display: flex;
align-items: center;
gap: 12px;
}
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
</style>Complete configuration interface extending MenuOptions with menu bar specific properties.
interface MenuBarOptions extends Omit<MenuOptions, 'x'|'y'|'getContainer'> {
/** Whether the menu bar is in collapsed/mini mode */
mini?: boolean;
/** Popup direction for dropdown menus in collapsed state */
barPopDirection?: MenuPopDirection;
// Inherited from MenuOptions:
/** Array of top-level menu items */
items?: MenuItem[];
/** Menu theme */
theme?: string;
/** Z-index for dropdown menus */
zIndex?: number;
/** Custom CSS class */
customClass?: string;
/** Enable keyboard navigation */
keyboardControl?: boolean;
/** Maximum width for dropdown menus */
maxWidth?: number;
/** Maximum height for dropdown menus */
maxHeight?: number;
/** Minimum width for dropdown menus */
minWidth?: number;
/** Custom icon font class */
iconFontClass?: string;
/** Reserve icon width for items without icons */
preserveIconWidth?: boolean;
/** Menu close callback */
onClose?: (lastClickItem: MenuItem | undefined) => void;
/** Keyboard focus movement callbacks */
onKeyFocusMoveLeft?: () => void;
onKeyFocusMoveRight?: () => void;
}The MenuBar component supports keyboard navigation when keyboardControl is enabled:
<template>
<menu-bar :options="keyboardMenuOptions" />
</template>
<script setup>
const keyboardMenuOptions = {
keyboardControl: true,
items: [...],
onKeyFocusMoveLeft: () => {
// Handle left navigation
console.log('Focus moved left');
},
onKeyFocusMoveRight: () => {
// Handle right navigation
console.log('Focus moved right');
}
};
</script>