feat(primitives): add menu, dropdown-menu, context-menu, and menubar primitives

Implements WAI-ARIA APG-compliant headless menu primitive families:

- menu: base primitive with MenuRoot, MenuContent, MenuItem,
  MenuCheckboxItem, MenuRadioGroup/Item, MenuSub, and helpers
- dropdown-menu: DropdownMenuRoot with trigger anchoring
- context-menu: ContextMenuRoot with right-click virtual anchor
- menubar: MenubarRoot with keyboard navigation between menus

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-17 18:48:43 +07:00
parent 333a18cbaf
commit 1d3efa5028
81 changed files with 2554 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
<script lang="ts">
import type { Direction } from '../config-provider';
export interface MenuRootProps {
open?: boolean;
dir?: Direction;
modal?: boolean;
}
export interface MenuRootEmits {
'update:open': [value: boolean];
}
</script>
<script setup lang="ts">
import { shallowRef, toRef } from 'vue';
import { useConfig } from '../config-provider';
import { PopperRoot } from '../popper';
import { provideMenuContext, provideMenuRootContext } from './context';
import { useIsUsingKeyboard } from './useIsUsingKeyboard';
const {
open = false,
dir: dirProp,
modal = true,
} = defineProps<MenuRootProps>();
const emit = defineEmits<MenuRootEmits>();
defineSlots<{ default?: () => unknown }>();
const config = useConfig();
const dirRef = toRef(() => dirProp ?? config.dir.value);
const isUsingKeyboardRef = useIsUsingKeyboard();
const content = shallowRef<HTMLElement | null>(null);
const openRef = toRef(() => open);
provideMenuContext({
open: openRef,
onOpenChange: v => emit('update:open', v),
content,
onContentChange: (el) => { content.value = el; },
});
provideMenuRootContext({
onClose: () => emit('update:open', false),
dir: dirRef,
isUsingKeyboardRef,
modal: toRef(() => modal),
});
</script>
<template>
<PopperRoot>
<slot />
</PopperRoot>
</template>