20260720172840

This commit is contained in:
oneao committed 2026-07-20 17:28:47 +08:00
1 parent cda7f16d3a
commit 94207c77ff
3117 files changed
+201407 -1236

No files matched your search

@@ -0,0 +1,116 @@
# _semantic
## Source
```vue
<script setup lang="ts">
import { MailOutlined } from '@antdv-next/icons'
import { computed, h, ref } from 'vue'
import { SemanticPreview } from '@/components/semantic'
import { useComponentLocale } from '@/composables/use-locale'
import { locales } from '../locales'
const { t } = useComponentLocale(locales)
const mode = ref<'horizontal' | 'vertical' | 'inline'>('horizontal')
const current = ref('mail')
const divRef = ref<HTMLDivElement | null>(null)
const semantics = computed(() => {
const baseLocale = [
{ name: 'root', desc: t('root') },
{ name: 'item', desc: t('item') },
{ name: 'itemIcon', desc: t('itemIcon') },
{ name: 'itemContent', desc: t('itemContent') },
]
const subMenuLocale = [
{ name: 'subMenu.itemTitle', desc: t('subMenu.itemTitle') },
{ name: 'subMenu.list', desc: t('subMenu.list') },
{ name: 'subMenu.item', desc: t('subMenu.item') },
{ name: 'subMenu.itemIcon', desc: t('subMenu.itemIcon') },
{ name: 'subMenu.itemContent', desc: t('subMenu.itemContent') },
]
const groupLocale = [
{ name: 'itemTitle', desc: t('itemTitle') },
{ name: 'list', desc: t('list') },
]
const additionalPopupLocale = mode.value !== 'inline' ? [{ name: 'popup', desc: t('popup') }] : []
const additionalGroupLocale = mode.value !== 'horizontal' ? groupLocale : []
return [...baseLocale, ...additionalGroupLocale, ...additionalPopupLocale, ...subMenuLocale]
})
const items = [
{ label: 'Navigation One', key: 'mail', icon: h(MailOutlined) },
{
key: 'SubMenu',
label: 'Navigation One',
icon: h(MailOutlined),
children: [
{
key: 'g1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1', icon: h(MailOutlined) },
{ key: '2', label: 'Option 2' },
],
},
],
},
]
const groupItem = [
{
key: 'grp',
label: 'Group',
type: 'group',
children: [
{ key: '13', label: 'Option 13' },
{ key: '14', label: 'Option 14' },
],
},
]
const itemList = computed(() => {
return mode.value === 'horizontal' ? items : [...items, ...groupItem]
})
function onClick(e: { key: string }) {
current.value = e.key
}
</script>
<template>
<SemanticPreview
component-name="Menu"
:semantics="semantics"
>
<template #default="{ classes }">
<a-flex ref="divRef" vertical gap="middle" align="center">
<a-segmented
:options="['horizontal', 'vertical', 'inline']"
:value="mode"
@change="(val: 'horizontal' | 'vertical' | 'inline') => mode = val"
/>
<div :style="{ height: '360px' }">
<a-menu
:selected-keys="[current]"
:mode="mode"
:items="itemList"
:styles="{
root: { width: mode === 'horizontal' ? '310px' : '230px' },
popup: { root: { zIndex: 1 } },
}"
:open-keys="['SubMenu']"
:get-popup-container="() => divRef!"
:classes="classes"
@click="onClick"
/>
</div>
</a-flex>
</template>
</SemanticPreview>
</template>
```
@@ -0,0 +1,160 @@
# Custom Submenu Render
## Description (en-US)
Use the `popupRender` prop to customize submenu popup rendering.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType, MenuProps } from 'antdv-next'
import { theme } from 'antdv-next'
import { computed, h } from 'vue'
const { token } = theme.useToken()
const popupStyle = computed(() => ({
padding: `${token.value.padding}px`,
minWidth: '480px',
background: token.value.colorBgElevated,
borderRadius: `${token.value.borderRadiusLG}px`,
boxShadow: token.value.boxShadowSecondary,
}))
const headerStyle = computed(() => ({
margin: 0,
paddingBottom: `${token.value.paddingXS}px`,
borderBottom: `1px solid ${token.value.colorSplit}`,
}))
const cardStyle = computed(() => ({
padding: `${token.value.paddingSM}px`,
}))
const cardItemStyle = computed(() => ({
borderRadius: `${token.value.borderRadius}px`,
transition: `all ${token.value.motionDurationSlow}`,
cursor: 'pointer',
}))
function renderMenuCard(title: string, description: string) {
return h('div', { class: 'menu-card', style: cardStyle.value }, [
h('div', { class: 'menu-card-title' }, title),
h('div', { class: 'menu-card-desc' }, description),
])
}
const menuItems = computed<MenuItemType[]>(() => [
{
key: 'home',
label: 'Home',
},
{
key: 'features',
label: 'Features',
children: [
{
key: 'getting-started',
label: renderMenuCard('Getting Started', 'Quick start guide and learn the basics.'),
class: 'menu-card-item',
style: cardItemStyle.value,
},
{
key: 'components',
label: renderMenuCard('Components', 'Explore our component library.'),
class: 'menu-card-item',
style: cardItemStyle.value,
},
{
key: 'templates',
label: renderMenuCard('Templates', 'Ready-to-use template designs.'),
class: 'menu-card-item',
style: cardItemStyle.value,
},
],
},
{
key: 'resources',
label: 'Resources',
children: [
{
key: 'blog',
label: renderMenuCard('Blog', 'Latest updates and articles.'),
class: 'menu-card-item',
style: cardItemStyle.value,
},
{
key: 'community',
label: renderMenuCard('Community', 'Join our developer community.'),
class: 'menu-card-item',
style: cardItemStyle.value,
},
],
},
])
const popupRender: MenuProps['popupRender'] = (node, info) => {
const title = info.item.title || info.item.key
return h('div', { class: 'menu-popup', style: popupStyle.value }, [
h('div', { class: 'menu-popup-title', style: headerStyle.value }, title),
node,
])
}
const themeConfig = {
components: {
Menu: {
popupBg: '#fff',
horizontalItemSelectedColor: '#1677ff',
horizontalItemHoverColor: '#1677ff',
},
},
}
</script>
<template>
<a-config-provider :theme="themeConfig">
<a-menu mode="horizontal" :items="menuItems" :popup-render="popupRender" />
</a-config-provider>
</template>
<style scoped>
.menu-popup :deep(.ant-menu) {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
column-gap: 16px;
row-gap: 8px;
background: transparent;
box-shadow: none;
padding: 0;
}
.menu-popup :deep(.ant-menu-item) {
height: auto;
line-height: 1.4;
}
.menu-popup :deep(.menu-card-item) {
padding: 0;
}
.menu-popup :deep(.menu-card-item:hover) {
background: rgba(0, 0, 0, 0.02);
}
.menu-popup :deep(.ant-menu-title-content) {
display: block;
}
.menu-card-title {
font-weight: 600;
line-height: 1.4;
}
.menu-card-desc {
color: rgba(0, 0, 0, 0.45);
font-size: 12px;
}
</style>
```
@@ -0,0 +1,80 @@
# Top Navigation
## Description (en-US)
Horizontal top navigation menu.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@antdv-next/icons'
import { h, ref } from 'vue'
const current = ref('mail')
function handleClick(e: any) {
console.log('click ', e)
current.value = e.key
}
const items: MenuItemType[] = [
{
label: 'Navigation One',
key: 'mail',
icon: MailOutlined,
},
{
label: 'Navigation Two',
key: 'app',
icon: AppstoreOutlined,
disabled: true,
},
{
label: 'Navigation Three - Submenu',
key: 'SubMenu',
icon: SettingOutlined,
children: [
{
type: 'group',
label: 'Item 1',
children: [
{ label: 'Option 1', key: 'setting:1' },
{ label: 'Option 2', key: 'setting:2' },
],
},
{
type: 'group',
label: 'Item 2',
children: [
{ label: 'Option 3', key: 'setting:3' },
{ label: 'Option 4', key: 'setting:4' },
],
},
],
},
{
key: 'alipay',
label: h(
'a',
{
href: 'https://ant.design',
target: '_blank',
rel: 'noopener noreferrer',
},
'Navigation Four - Link',
),
},
]
</script>
<template>
<a-menu
:selected-keys="[current]"
mode="horizontal"
:items="items"
@click="handleClick"
/>
</template>
```
@@ -0,0 +1,86 @@
# Collapsed inline menu
## Description (en-US)
Inline menu could be collapsed.
Here is [a complete demo](../../layout/docs.md/#layout-demo-side) with sider layout.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import {
AppstoreOutlined,
ContainerOutlined,
DesktopOutlined,
MailOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
PieChartOutlined,
} from '@antdv-next/icons'
import { ref } from 'vue'
const collapsed = ref(false)
function toggleCollapsed() {
collapsed.value = !collapsed.value
}
const items: MenuItemType[] = [
{ key: '1', icon: PieChartOutlined, label: 'Option 1' },
{ key: '2', icon: DesktopOutlined, label: 'Option 2' },
{ key: '3', icon: ContainerOutlined, label: 'Option 3' },
{
key: 'sub1',
label: 'Navigation One',
icon: MailOutlined,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: AppstoreOutlined,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
],
},
]
</script>
<template>
<div style="width: 256px">
<a-button
type="primary"
style="margin-bottom: 16px"
@click="toggleCollapsed"
>
<MenuUnfoldOutlined v-if="collapsed" />
<MenuFoldOutlined v-else />
</a-button>
<a-menu
:default-selected-keys="['1']"
:default-open-keys="['sub1']"
mode="inline"
theme="dark"
:inline-collapsed="collapsed"
:items="items"
/>
</div>
</template>
```
@@ -0,0 +1,100 @@
# Inline menu
## Description (en-US)
Vertical menu with inline submenus.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@antdv-next/icons'
import { ref } from 'vue'
function handleClick(e: any) {
console.log('click ', e)
}
const items: MenuItemType[] = [
{
key: 'sub1',
label: 'Navigation One',
icon: MailOutlined,
children: [
{
key: 'g1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
{
key: 'g2',
label: 'Item 2',
type: 'group',
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: AppstoreOutlined,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
type: 'divider',
},
{
key: 'sub4',
label: 'Navigation Three',
icon: SettingOutlined,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
{
key: 'grp',
label: 'Group',
type: 'group',
children: [
{ key: '13', label: 'Option 13' },
{ key: '14', label: 'Option 14' },
],
},
]
const openKeys = ref(['sub1'])
const selectedKeys = ref(['1'])
</script>
<template>
<a-menu
v-model:open-keys="openKeys"
v-model:selected-keys="selectedKeys"
style="width: 256px"
mode="inline"
:items="items"
@click="handleClick"
/>
</template>
```
@@ -0,0 +1,164 @@
# SFC Mode (Nested)
## Description (en-US)
Build an SFC menu with declarative `a-menu-item` and `a-sub-menu`, equivalent to the `items` structure.
## Source
```vue
<script setup lang="ts">
import {
AppstoreOutlined,
CalendarOutlined,
LinkOutlined,
MailOutlined,
SettingOutlined,
} from '@antdv-next/icons'
import { computed, ref, shallowRef, watch } from 'vue'
const mode = ref<'vertical' | 'inline'>('inline')
const theme = ref<'dark' | 'light'>('light')
const collapsed = ref(false)
const openKeys = ref<string[]>(['sub1'])
const cachedOpenKeys = ref<string[]>(['sub1'])
const inlineCollapsed = computed(() => mode.value === 'inline' && collapsed.value)
const expandAllChecked = computed(() => !collapsed.value && openKeys.value.length > 0)
watch(openKeys, (keys) => {
if (!collapsed.value) {
cachedOpenKeys.value = [...keys]
}
}, { deep: true })
function changeExpanded(value: boolean) {
if (collapsed.value) {
return
}
openKeys.value = value ? ['sub1', 'sub1-2', 'sub2'] : []
}
function changeCollapsed(value: boolean) {
collapsed.value = value
if (value) {
openKeys.value = []
}
else {
openKeys.value = [...cachedOpenKeys.value]
}
}
function changeMode(value: boolean) {
mode.value = value ? 'vertical' : 'inline'
}
function changeTheme(value: boolean) {
theme.value = value ? 'dark' : 'light'
}
const selectedKeys = shallowRef(['1'])
</script>
<template>
<a-switch :checked="mode === 'vertical'" @change="changeMode" />
Change Mode
<a-divider type="vertical" />
<a-switch :checked="collapsed" @change="changeCollapsed" />
Inline Collapsed
<a-divider type="vertical" />
<a-switch :checked="expandAllChecked" :disabled="collapsed" @change="changeExpanded" />
Expand All
<a-divider type="vertical" />
<a-switch :checked="theme === 'dark'" @change="changeTheme" />
Change Style
<br>
<br>
<div style="width: 256px">
<a-menu
v-model:selected-keys="selectedKeys"
v-model:open-keys="openKeys"
:inline-collapsed="inlineCollapsed"
:mode="mode"
:theme="theme"
>
<a-menu-item key="1">
<template #icon>
<MailOutlined />
</template>
Navigation One
</a-menu-item>
<a-menu-item key="2">
<template #icon>
<CalendarOutlined />
</template>
Navigation Two
</a-menu-item>
<a-sub-menu key="sub1">
<template #icon>
<AppstoreOutlined />
</template>
<template #title>
Navigation Two
</template>
<a-menu-item key="3">
Option 3
</a-menu-item>
<a-menu-item key="4">
Option 4
</a-menu-item>
<a-sub-menu key="sub1-2">
<template #title>
Submenu
</template>
<a-menu-item key="5">
Option 5
</a-menu-item>
<a-menu-item key="6">
Option 6
</a-menu-item>
</a-sub-menu>
</a-sub-menu>
<a-sub-menu key="sub2">
<template #icon>
<SettingOutlined />
</template>
<template #title>
Navigation Three
</template>
<a-menu-item key="7">
Option 7
</a-menu-item>
<a-menu-item key="8">
Option 8
</a-menu-item>
<a-menu-item key="9">
Option 9
</a-menu-item>
<a-menu-item key="10">
Option 10
</a-menu-item>
</a-sub-menu>
<a-menu-item key="link">
<template #icon>
<LinkOutlined />
</template>
<a
href="https://antdv-next.com"
target="_blank"
rel="noopener noreferrer"
>
Antdv Next
</a>
</a-menu-item>
</a-menu>
</div>
</template>
```
@@ -0,0 +1,118 @@
# Open current submenu only
## Description (en-US)
Click the menu and you will see that all the other menus gets collapsed to keep the entire menu compact.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@antdv-next/icons'
import { ref } from 'vue'
const items: MenuItemType[] = [
{
key: '1',
icon: MailOutlined,
label: 'Navigation One',
children: [
{ key: '11', label: 'Option 1' },
{ key: '12', label: 'Option 2' },
{ key: '13', label: 'Option 3' },
{ key: '14', label: 'Option 4' },
],
},
{
key: '2',
icon: AppstoreOutlined,
label: 'Navigation Two',
children: [
{ key: '21', label: 'Option 1' },
{ key: '22', label: 'Option 2' },
{
key: '23',
label: 'Submenu',
children: [
{ key: '231', label: 'Option 1' },
{ key: '232', label: 'Option 2' },
{ key: '233', label: 'Option 3' },
],
},
{
key: '24',
label: 'Submenu 2',
children: [
{ key: '241', label: 'Option 1' },
{ key: '242', label: 'Option 2' },
{ key: '243', label: 'Option 3' },
],
},
],
},
{
key: '3',
icon: SettingOutlined,
label: 'Navigation Three',
children: [
{ key: '31', label: 'Option 1' },
{ key: '32', label: 'Option 2' },
{ key: '33', label: 'Option 3' },
{ key: '34', label: 'Option 4' },
],
},
]
interface LevelKeysProps {
key?: string
children?: LevelKeysProps[]
}
function getLevelKeys(itemList: LevelKeysProps[]) {
const keyLevelMap: Record<string, number> = {}
const func = (list: LevelKeysProps[], level = 1) => {
list.forEach((item) => {
if (item.key) {
keyLevelMap[item.key] = level
}
if (item.children) {
func(item.children, level + 1)
}
})
}
func(itemList)
return keyLevelMap
}
const levelKeys = getLevelKeys(items as LevelKeysProps[])
const stateOpenKeys = ref<string[]>(['2', '23'])
function onOpenChange(openKeys: string[]) {
const currentOpenKey = openKeys.find(key => !stateOpenKeys.value.includes(key))
if (currentOpenKey !== undefined) {
const repeatIndex = openKeys
.filter(key => key !== currentOpenKey)
.findIndex(key => levelKeys[key] === levelKeys[currentOpenKey])
stateOpenKeys.value = openKeys
.filter((_, index) => index !== repeatIndex)
.filter(key => levelKeys[key]! <= levelKeys[currentOpenKey]!)
}
else {
stateOpenKeys.value = openKeys
}
}
</script>
<template>
<a-menu
mode="inline"
style="width: 256px"
:default-selected-keys="['231']"
:open-keys="stateOpenKeys"
:items="items"
@open-change="onOpenChange"
/>
</template>
```
@@ -0,0 +1,133 @@
# Slot Render (items mode)
## Description (en-US)
Customize menu item rendering with `labelRender`, `extraRender`, and `iconRender` slots.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import { ClockCircleFilled } from '@antdv-next/icons'
const items: MenuItemType[] = [
{
key: 'sub1',
label: 'Navigation One',
children: [
{
key: 'g1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
{
key: 'g2',
label: 'Item 2',
type: 'group',
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
],
},
{
key: 'sub2',
label: 'Navigation Two',
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
type: 'divider',
},
{
key: 'sub4',
label: 'Navigation Three',
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
{
key: 'grp',
label: 'Group',
type: 'group',
children: [
{ key: '13', label: 'Option 13' },
{ key: '14', label: 'Option 14' },
],
},
]
</script>
<template>
<a-menu
style="width: 320px"
:default-open-keys="['sub1']"
:default-selected-keys="['1']"
mode="inline"
:items="items"
>
<template #labelRender="item">
<span class="menu-slot-label">
{{ item.label }}
</span>
</template>
<template #extraRender="item">
<span
v-if="item?.key && !item?.children && item?.type !== 'group'"
class="menu-slot-extra"
>
#{{ item.key }}
</span>
</template>
<template #iconRender="item">
<span v-if="item?.key" class="menu-slot-icon">
<ClockCircleFilled />
</span>
</template>
</a-menu>
</template>
<style scoped>
.menu-slot-label {
font-weight: 500;
}
.menu-slot-extra {
color: #1677ff;
font-size: 12px;
}
.menu-slot-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 50%;
font-size: 10px;
color: #fff;
background: #1677ff;
}
</style>
```
@@ -0,0 +1,73 @@
# Custom semantic dom styling
## Description (en-US)
## Source
```vue
<script setup lang="ts">
import type { MenuItemType, MenuProps } from 'antdv-next'
import { useCssModule } from 'vue'
const moduleStyles = useCssModule()
const items: MenuItemType[] = [
{
key: 'SubMenu',
label: 'Navigation One',
children: [
{
key: 'g1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
],
},
{ key: 'mail', label: 'Navigation Two' },
]
const classes: MenuProps['classes'] = {
root: moduleStyles.root,
item: moduleStyles.item,
}
const styles: MenuProps['styles'] = {
root: { border: '1px solid #f0f0f0', padding: '8px', borderRadius: '4px' },
item: { color: '#1677ff' },
subMenu: { list: { color: '#fa541c' } },
}
const stylesFn: MenuProps['styles'] = (info) => {
const hasSub = info.props.items?.[0]
return {
root: {
backgroundColor: hasSub ? 'rgba(240,249,255, 0.6)' : '#fff',
},
}
}
</script>
<template>
<a-flex vertical gap="middle">
<a-menu :classes="classes" :items="items" :styles="styles" />
<a-menu mode="inline" :classes="classes" :items="items" :styles="stylesFn" />
</a-flex>
</template>
<style module>
.root {
border: 1px solid #f0f0f0;
max-width: 600px;
padding: 8px;
border-radius: 4px;
}
.item {
color: #1677ff;
}
</style>
```
@@ -0,0 +1,65 @@
# Sub-menu theme
## Description (en-US)
You can config SubMenu theme with `theme` prop to enable different theme color effect. This sample is dark for root and light for SubMenu.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import { MailOutlined } from '@antdv-next/icons'
import { computed, ref } from 'vue'
const menuTheme = ref<'dark' | 'light'>('light')
const current = ref('1')
function changeTheme(value: boolean) {
menuTheme.value = value ? 'dark' : 'light'
}
function onClick(e: any) {
current.value = e.key
}
const items = computed<MenuItemType[]>(() => [
{
key: 'sub1',
icon: MailOutlined,
label: 'Navigation One',
theme: menuTheme.value,
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
{ key: '3', label: 'Option 3' },
],
},
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
])
</script>
<template>
<div>
<a-switch
:checked="menuTheme === 'dark'"
checked-children="Dark"
un-checked-children="Light"
@change="changeTheme"
/>
<br>
<br>
<a-menu
style="width: 256px"
:open-keys="['sub1']"
:selected-keys="[current]"
mode="vertical"
theme="dark"
:items="items"
:get-popup-container="(node: any) => node.parentNode"
@click="onClick"
/>
</div>
</template>
```
@@ -0,0 +1,104 @@
# Switch the menu type
## Description (en-US)
Show the dynamic switching mode (between `inline` and `vertical`).
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import {
AppstoreOutlined,
CalendarOutlined,
LinkOutlined,
MailOutlined,
SettingOutlined,
} from '@antdv-next/icons'
import { h, ref } from 'vue'
const mode = ref<'vertical' | 'inline'>('inline')
const theme = ref<'dark' | 'light'>('light')
const items: MenuItemType[] = [
{
key: '1',
icon: MailOutlined,
label: 'Navigation One',
},
{
key: '2',
icon: CalendarOutlined,
label: 'Navigation Two',
},
{
key: 'sub1',
label: 'Navigation Two',
icon: AppstoreOutlined,
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
{
key: 'sub1-2',
label: 'Submenu',
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
],
},
],
},
{
key: 'sub2',
label: 'Navigation Three',
icon: SettingOutlined,
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
],
},
{
key: 'link',
icon: LinkOutlined,
label: h(
'a',
{
href: 'https://ant.design',
target: '_blank',
rel: 'noopener noreferrer',
},
'Antdv Next',
),
},
]
function changeMode(value: boolean) {
mode.value = value ? 'vertical' : 'inline'
}
function changeTheme(value: boolean) {
theme.value = value ? 'dark' : 'light'
}
</script>
<template>
<a-switch :checked="mode === 'vertical'" @change="changeMode" />
Change Mode
<a-divider type="vertical" />
<a-switch :checked="theme === 'dark'" @change="changeTheme" />
Change Style
<br>
<br>
<a-menu
style="width: 256px"
:default-selected-keys="['1']"
:default-open-keys="['sub1']"
:mode="mode"
:theme="theme"
:items="items"
/>
</template>
```
@@ -0,0 +1,89 @@
# Menu Themes
## Description (en-US)
There are two built-in themes: `light` and `dark`. The default value is `light`.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@antdv-next/icons'
import { ref } from 'vue'
const theme = ref<'dark' | 'light'>('dark')
const current = ref('1')
const items: MenuItemType[] = [
{
key: 'sub1',
label: 'Navigation One',
icon: MailOutlined,
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: AppstoreOutlined,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
key: 'sub4',
label: 'Navigation Three',
icon: SettingOutlined,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
]
function changeTheme(value: boolean) {
theme.value = value ? 'dark' : 'light'
}
function onClick(e: any) {
console.log('click ', e)
current.value = e.key
}
</script>
<template>
<a-switch
:checked="theme === 'dark'"
checked-children="Dark"
un-checked-children="Light"
@change="changeTheme"
/>
<br>
<br>
<a-menu
:theme="theme"
:selected-keys="[current]"
style="width: 256px"
:default-open-keys="['sub1']"
mode="inline"
:items="items"
@click="onClick"
/>
</template>
```
@@ -0,0 +1,83 @@
# Vertical menu
## Description (en-US)
Submenus open as pop-ups.
## Source
```vue
<script setup lang="ts">
import type { MenuItemType } from 'antdv-next'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@antdv-next/icons'
function onClick(e: any) {
console.log('click', e)
}
const items: MenuItemType[] = [
{
key: 'sub1',
icon: MailOutlined,
label: 'Navigation One',
children: [
{
key: '1-1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
{
key: '1-2',
label: 'Item 2',
type: 'group',
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
],
},
{
key: 'sub2',
icon: AppstoreOutlined,
label: 'Navigation Two',
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
key: 'sub4',
label: 'Navigation Three',
icon: SettingOutlined,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
]
</script>
<template>
<a-menu
style="width: 256px"
mode="vertical"
:items="items"
@click="onClick"
/>
</template>
```
@@ -0,0 +1,174 @@
---
title: Menu
description: A versatile menu for navigation.
---
## When To Use
Navigation is an important part of any website, as a good navigation setup allows users to move around the site quickly and efficiently. Antdv Next offers two navigation options: top and side. Top navigation provides all the categories and functions of the website. Side navigation provides the multi-level structure of the website.
More layouts with navigation: [Layout](../layout/docs.md).
## Notes for developers
- Menu is rendered as a `ul` element, so it only supports [`li` and `script-supporting` elements](https://html.spec.whatwg.org/multipage/grouping-content.html#the-ul-element) as children nodes. Your customized node should be wrapped by `Menu.Item`.
- Menu needs to collect its node structure, so its children should be `Menu.*` or encapsulated components.
## Demos
| Demo | Path |
| --- | --- |
| Top Navigation | demo/horizontal.md |
| Inline menu | demo/inline.md |
| Collapsed inline menu | demo/inline-collapsed.md |
| Open current submenu only | demo/sider-current.md |
| Vertical menu | demo/vertical.md |
| Menu Themes | demo/theme.md |
| Sub-menu theme | demo/submenu-theme.md |
| Switch the menu type | demo/switch-mode.md |
| SFC Mode (Nested) | demo/sfc.md |
| Slot Render (items mode) | demo/slot-render.md |
| Custom semantic dom styling | demo/style-class.md |
| Custom Submenu Render | demo/custom-popup-render.md |
## API
Common props ref:[Common props](../../docs/vue/common-props.md)
### Props
| Property | Description | Type | Default | Version | [Global Config](../config-provider/docs.md#component-config) |
| --- | --- | --- | --- | --- | --- |
| classes | Customize class for each semantic structure inside the component. Supports object or function | MenuClassNamesType | - | - | ✓ |
| styles | Customize inline style for each semantic structure inside the component. Supports object or function | MenuStylesType | - | - | ✓ |
| rootClass | Root container class | string | - | - | × |
| defaultOpenKeys | Array with the keys of default opened sub menus | string[] | - | - | × |
| defaultSelectedKeys | Array with the keys of default selected menu items | string[] | - | - | × |
| expandIcon | Custom expand icon of submenu | VueNode \| ((props: SubMenuProps & { isSubMenu: boolean }) => VueNode) | - | - | ✓ |
| forceSubMenuRender | Render submenu into DOM before it becomes visible | boolean | false | - | × |
| inlineCollapsed | Specifies the collapsed status when menu is inline mode | boolean | - | - | × |
| inlineIndent | Indent (in pixels) of inline menu items on each level | number | 24 | - | × |
| items | Menu item content | ItemType[] | - | - | × |
| mode | Type of menu | `vertical` \| `horizontal` \| `inline` | `vertical` | - | × |
| multiple | Allows selection of multiple items | boolean | false | - | × |
| openKeys | Array with the keys of currently opened sub-menus | string[] | - | - | × |
| overflowedIndicator | Customized the ellipsis icon when menu is collapsed horizontally | VueNode | `<EllipsisOutlined />` | - | × |
| selectable | Allows selecting menu items | boolean | true | - | × |
| selectedKeys | Array with the keys of currently selected menu items | string[] | - | - | × |
| subMenuCloseDelay | Delay time to hide submenu when mouse leaves (in seconds) | number | 0.1 | - | × |
| subMenuOpenDelay | Delay time to show submenu when mouse enters, (in seconds) | number | 0 | - | × |
| theme | Color theme of the menu | `light` \| `dark` | `light` | - | × |
| triggerSubMenuAction | Which action can trigger submenu open/close | `hover` \| `click` | `hover` | - | × |
| getPopupContainer | To set the container of the submenu popup. The default is to create a div element in body, but you can reset it to the scrolling area and make a relative reposition | (triggerNode: HTMLElement) => HTMLElement | () => document.body | - | × |
| itemIcon | Custom menu item icon render | (props: MenuItemProps & RenderIconInfo) => any | - | - | × |
| labelRender | Custom label render | (item: RenderItem) => any | - | - | × |
| iconRender | Custom icon render (items mode) | (item: RenderItem) => any | - | - | × |
| extraRender | Custom extra render | (item: RenderItem) => any | - | - | × |
| popupRender | Custom popup renderer for submenu | (node: VueNode, info: { item: SubMenuProps; keys: string[] }) => VueNode | - | - | × |
### Events
| Event | Description | Type | Version |
| --- | --- | --- | --- |
| click | Called when a menu item is clicked | (info: MenuInfo) => void | - |
| select | Called when a menu item is selected | (info: SelectInfo) => void | - |
| deselect | Called when a menu item is deselected (multiple mode only) | (info: SelectInfo) => void | - |
| openChange | Called when sub-menus are opened or closed | (openKeys: string[]) => void | - |
### Slots
| Slot | Description | Type | Version |
| --- | --- | --- | --- |
| expandIcon | Custom expand icon | () => any | - |
| labelRender | Customize label content | (item: RenderItem) => any | - |
| iconRender | Customize item icon (items mode) | (item: RenderItem) => any | - |
| extraRender | Customize extra content | (item: RenderItem) => any | - |
| itemIcon | Custom item icon | (props: MenuItemProps & RenderIconInfo) => any | - |
### Methods
| Method | Description | Type | Version |
| --- | --- | --- | --- |
| menu | Menu ref | VcMenuRef \| null | - |
| focus | Focus menu | (options?: FocusOptions) => void | - |
## Types
### ItemType
> type ItemType = MenuItemType | SubMenuType | MenuItemGroupType | MenuDividerType | null;
#### MenuItemType
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| danger | Display the danger style | boolean | false | - |
| disabled | Whether menu item is disabled | boolean | false | - |
| extra | The extra of the menu item | VueNode | - | - |
| icon | The icon of the menu item | VueNode | - | - |
| key | Unique ID of the menu item | string | - | - |
| label | Menu label | VueNode | - | - |
| title | Set display title for collapsed item | string | - | - |
#### SubMenuType
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| children | Sub-menus or sub-menu items | ItemType[] | - | - |
| disabled | Whether sub-menu is disabled | boolean | false | - |
| icon | Icon of sub menu | VueNode | - | - |
| key | Unique ID of the sub-menu | string | - | - |
| label | Menu label | VueNode | - | - |
| popupClassName | Sub-menu class name, not working when `mode="inline"` | string | - | - |
| popupOffset | Sub-menu offset, not working when `mode="inline"` | [number, number] | - | - |
| theme | Color theme of the SubMenu (inherits from Menu by default) | `light` \| `dark` | - | - |
| onTitleClick | Callback executed when the sub-menu title is clicked | (info: { key: string; domEvent: MouseEvent }) => void | - | - |
| popupRender | Custom popup renderer for current sub-menu | (node: VueNode, info: { item: SubMenuProps; keys: string[] }) => VueNode | - | - |
#### MenuItemGroupType
Define `type` as `group` to make as group:
```ts
const groupItem = {
type: 'group', // Must have
label: 'My Group',
children: [],
}
```
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| children | Sub-menu items | MenuItemType[] | - | - |
| label | The title of the group | VueNode | - | - |
#### MenuDividerType
Divider line in between menu items, only used in vertical popup Menu or Dropdown Menu. Need define the `type` as `divider`:
```ts
const dividerItem = {
type: 'divider', // Must have
}
```
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| dashed | Whether line is dashed | boolean | false | - |
## FAQ
### Why will Menu's children be rendered twice?
Menu collects structure info with [twice-render](https://github.com/react-component/menu/blob/f4684514096d6b7123339cbe72e7b0f68db0bce2/src/Menu.tsx#L543) to support HOC usage. Merging into one render may cause the logic to become much more complex. Contributions to help improve the collection logic are welcomed.
### Why Menu do not responsive collapse in Flex layout?
Menu will render fully item in flex layout and then collapse it. You need tell flex not consider Menu width to enable responsive ([online demo](https://codesandbox.io/s/ding-bu-dao-hang-antd-4-21-7-forked-5e3imy?file=/demo.js)):
```html
<div style="display: flex;">
<div style="flex: none;">Some Content</div>
<a-menu style="min-width: 0; flex: auto;" />
</div>
```
## Semantic DOM
| _semantic | demo/_semantic.md |
@@ -0,0 +1,80 @@
# menu Token
Use these variables through `theme.components.Menu`. This document lists definitions only and does not include values.
```vue
<a-config-provider
:theme="{
token: {
// Global token configuration
},
components: {
Menu: {
// Token name: value
},
},
}"
>
...
</a-config-provider>
```
## Token List
| Token | Type | Description |
| --- | --- | --- |
| `dropdownWidth` | `string \| number` | Width of popup menu |
| `zIndexPopup` | `number` | z-index of popup menu |
| `groupTitleColor` | `string` | Color of group title text |
| `groupTitleLineHeight` | `string \| number` | line-height of group title |
| `groupTitleFontSize` | `number` | font-size of group title |
| `itemBorderRadius` | `number` | Radius of menu item |
| `subMenuItemBorderRadius` | `number` | Radius of sub-menu item |
| `itemColor` | `string` | Color of menu item text |
| `itemHoverColor` | `string` | Hover color of menu item text |
| `horizontalItemHoverColor` | `string` | Hover color of horizontal menu item text |
| `itemSelectedColor` | `string` | Color of selected menu item text |
| `subMenuItemSelectedColor` | `string` | Color of submenu title when submenu has selected item |
| `horizontalItemSelectedColor` | `string` | Color of selected horizontal menu item text |
| `itemDisabledColor` | `string` | Color of disabled menu item text |
| `dangerItemColor` | `string` | Color of danger menu item text |
| `dangerItemHoverColor` | `string` | Hover color of danger menu item text |
| `dangerItemSelectedColor` | `string` | Color of selected danger menu item text |
| `dangerItemActiveBg` | `string` | Background color of danger menu item when active |
| `dangerItemSelectedBg` | `string` | Background color of selected danger menu item |
| `itemBg` | `string` | - |
| `itemHoverBg` | `string` | Background color of menu item when hover |
| `subMenuItemBg` | `string` | Background color of sub-menu item |
| `itemActiveBg` | `string` | Background color of menu item when active |
| `itemSelectedBg` | `string` | Background color of menu item when selected |
| `horizontalItemSelectedBg` | `string` | Background color of horizontal menu item when selected |
| `activeBarWidth` | `string \| number` | Width of menu item active bar |
| `activeBarHeight` | `number` | Height of menu item active bar |
| `activeBarBorderWidth` | `string \| number` | Border width of menu item active bar |
| `itemMarginInline` | `number` | Horizontal margin of menu item |
| `horizontalItemHoverBg` | `string` | Background color of horizontal menu item when hover |
| `horizontalItemBorderRadius` | `number` | Border radius of horizontal menu item |
| `itemHeight` | `string \| number` | Height of menu item |
| `collapsedWidth` | `string \| number` | Width when collapsed |
| `popupBg` | `string` | Background color of popup |
| `itemMarginBlock` | `CSSProperties` | margin-block of menu item |
| `itemPaddingInline` | `CSSProperties` | padding-inline of menu item |
| `horizontalLineHeight` | `CSSProperties` | LineHeight of horizontal menu item |
| `iconMarginInlineEnd` | `CSSProperties` | Spacing between icon and text |
| `iconSize` | `number` | Size of icon |
| `collapsedIconSize` | `number` | Size of icon when collapsed |
| `darkPopupBg` | `string` | The background color of the overlay menu in dark mode. |
| `darkItemColor` | `string` | Color of menu item text in dark mode |
| `darkDangerItemColor` | `string` | Color of danger menu item text in dark mode |
| `darkItemBg` | `string` | Background of menu item in dark mode |
| `darkSubMenuItemBg` | `string` | Background of submenu item in dark mode |
| `darkItemSelectedColor` | `string` | Color of selected menu item in dark mode |
| `darkItemSelectedBg` | `string` | Background of active menu item in dark mode |
| `darkItemHoverBg` | `string` | Background of hovered menu item in dark mode |
| `darkGroupTitleColor` | `string` | Color of group title text in dark mode |
| `darkItemHoverColor` | `string` | Color of hovered menu item in dark mode |
| `darkItemDisabledColor` | `string` | Color of disabled menu item in dark mode |
| `darkDangerItemSelectedBg` | `string` | Background of active danger menu item in dark mode |
| `darkDangerItemHoverColor` | `string` | Background of hovered danger menu item in dark mode |
| `darkDangerItemSelectedColor` | `string` | Color of selected danger menu item in dark mode |
| `darkDangerItemActiveBg` | `string` | Background of active danger menu item in dark mode |