20260720172840
This commit is contained in:
1 parent
cda7f16d3a
commit
94207c77ff
3117 files changed
+201407
-1236
No files matched your search
@@ -0,0 +1,57 @@
|
||||
# _semantic
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { SemanticPreview } from '@/components/semantic'
|
||||
import { useComponentLocale } from '@/composables/use-locale'
|
||||
import { locales } from '../locales'
|
||||
|
||||
const { t } = useComponentLocale(locales)
|
||||
|
||||
const semantics = computed(() => [
|
||||
{ name: 'root', desc: t('root') },
|
||||
{ name: 'header', desc: t('header') },
|
||||
{ name: 'item', desc: t('item') },
|
||||
{ name: 'indicator', desc: t('indicator') },
|
||||
{ name: 'body', desc: t('body') },
|
||||
{ name: 'content', desc: t('content') },
|
||||
{ name: 'popup.root', desc: t('popup.root') },
|
||||
])
|
||||
|
||||
const items = computed(() =>
|
||||
Array.from({ length: 30 }, (_, i) => {
|
||||
const id = String(i)
|
||||
return {
|
||||
label: `Tab-${id}`,
|
||||
key: id,
|
||||
disabled: i === 28,
|
||||
content: `Content of tab ${id}`,
|
||||
}
|
||||
}),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SemanticPreview
|
||||
component-name="Tabs"
|
||||
:semantics="semantics"
|
||||
>
|
||||
<template #default="{ classes }">
|
||||
<a-tabs
|
||||
default-active-key="1"
|
||||
:style="{ height: '220px', width: '100%' }"
|
||||
:styles="{
|
||||
popup: {
|
||||
root: { background: '#fff' },
|
||||
},
|
||||
}"
|
||||
:items="items"
|
||||
:classes="classes"
|
||||
/>
|
||||
</template>
|
||||
</SemanticPreview>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# Basic
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Default activate first tab.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: 'Tab 1',
|
||||
content: 'Content of Tab Pane 1',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Tab 2',
|
||||
content: 'Content of Tab Pane 2',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: 'Tab 3',
|
||||
content: 'Content of Tab Pane 3',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-tabs :items="items" />
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
# Card type tab
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Another type of Tabs, which doesn't support vertical mode.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
|
||||
function onChange(key: string) {
|
||||
console.log(key)
|
||||
}
|
||||
|
||||
const items: TabsProps['items'] = Array.from({ length: 3 }).map((_, i) => {
|
||||
const id = String(i + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab ${id}`,
|
||||
content: `Content of Tab Pane ${id}`,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-tabs default-active-key="1" type="card" :items="items" @change="onChange" />
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
# Centered
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Centered tabs.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
|
||||
const items: TabsProps['items'] = Array.from({ length: 3 }).map((_, i) => {
|
||||
const id = String(i + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab ${id}`,
|
||||
content: `Content of Tab Pane ${id}`,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-tabs centered :items="items" />
|
||||
</template>
|
||||
```
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
# Customized trigger of new tab
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Hide default plus icon, and bind event for customized trigger.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface TabItem {
|
||||
key: string
|
||||
label: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const defaultPanes: TabItem[] = Array.from({ length: 2 }).map((_, index) => {
|
||||
const id = String(index + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab ${id}`,
|
||||
content: `Content of Tab Pane ${index + 1}`,
|
||||
}
|
||||
})
|
||||
|
||||
const activeKey = ref(defaultPanes[0]?.key ?? '1')
|
||||
const items = ref<TabItem[]>([...defaultPanes])
|
||||
let newTabIndex = 0
|
||||
|
||||
function onChange(key: string) {
|
||||
activeKey.value = key
|
||||
}
|
||||
|
||||
function add() {
|
||||
const newActiveKey = `newTab${newTabIndex++}`
|
||||
items.value = [...items.value, { key: newActiveKey, label: 'New Tab', content: 'New Tab Pane' }]
|
||||
activeKey.value = newActiveKey
|
||||
}
|
||||
|
||||
function remove(targetKey: string) {
|
||||
const targetIndex = items.value.findIndex(pane => pane.key === targetKey)
|
||||
const newPanes = items.value.filter(pane => pane.key !== targetKey)
|
||||
if (newPanes.length && targetKey === activeKey.value) {
|
||||
const nextPane = newPanes[targetIndex === newPanes.length ? targetIndex - 1 : targetIndex]
|
||||
if (nextPane) {
|
||||
activeKey.value = nextPane.key
|
||||
}
|
||||
}
|
||||
items.value = newPanes
|
||||
}
|
||||
|
||||
function onEdit(targetKey: string | MouseEvent | KeyboardEvent, action: 'add' | 'remove') {
|
||||
if (action === 'add') {
|
||||
add()
|
||||
}
|
||||
else {
|
||||
remove(targetKey as string)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<a-button @click="add">
|
||||
ADD
|
||||
</a-button>
|
||||
</div>
|
||||
<a-tabs
|
||||
v-model:active-key="activeKey"
|
||||
type="editable-card"
|
||||
hide-add
|
||||
:items="items"
|
||||
@change="onChange"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
# Indicator
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Set `indicator` prop to custom indicator size and align.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{ key: '1', label: 'Tab 1', content: 'Content of Tab Pane 1' },
|
||||
{ key: '2', label: 'Tab 2', content: 'Content of Tab Pane 2' },
|
||||
{ key: '3', label: 'Tab 3', content: 'Content of Tab Pane 3' },
|
||||
]
|
||||
|
||||
type Align = 'start' | 'center' | 'end'
|
||||
|
||||
const alignValue = ref<Align>('center')
|
||||
|
||||
function onChange(key: string) {
|
||||
console.log(key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-segmented
|
||||
v-model:value="alignValue"
|
||||
style="margin-bottom: 8px;"
|
||||
:options="['start', 'center', 'end']"
|
||||
/>
|
||||
<a-tabs
|
||||
default-active-key="1"
|
||||
:items="items"
|
||||
:indicator="{ size: (origin: number) => origin - 20, align: alignValue }"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# Draggable Tabs
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Use `renderTabBar` with drag support to reorder tabs.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
type TabItem = NonNullable<TabsProps['items']>[number]
|
||||
|
||||
const items = ref<TabItem[]>([
|
||||
{ key: '1', label: 'Tab 1', content: 'Content of Tab Pane 1' },
|
||||
{ key: '2', label: 'Tab 2', content: 'Content of Tab Pane 2' },
|
||||
{ key: '3', label: 'Tab 3', content: 'Content of Tab Pane 3' },
|
||||
])
|
||||
|
||||
const draggingKey = ref<string | null>(null)
|
||||
|
||||
function getNodeKey(node: any) {
|
||||
return String(node?.props?.['data-node-key'] ?? '')
|
||||
}
|
||||
|
||||
function handleDragStart(key: string, event: DragEvent) {
|
||||
if (!key) {
|
||||
return
|
||||
}
|
||||
draggingKey.value = key
|
||||
event.dataTransfer?.setData('text/plain', key)
|
||||
event.dataTransfer?.setDragImage?.(event.currentTarget as Element, 0, 0)
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
draggingKey.value = null
|
||||
}
|
||||
|
||||
function handleDrop(overKey: string, event: DragEvent) {
|
||||
const activeKey = event.dataTransfer?.getData('text/plain')
|
||||
if (!activeKey || !overKey || activeKey === overKey) {
|
||||
return
|
||||
}
|
||||
const activeIndex = items.value.findIndex(item => String(item.key) === activeKey)
|
||||
const overIndex = items.value.findIndex(item => String(item.key) === overKey)
|
||||
if (activeIndex < 0 || overIndex < 0) {
|
||||
return
|
||||
}
|
||||
const next = items.value.slice()
|
||||
const [moved] = next.splice(activeIndex, 1)
|
||||
if (!moved) {
|
||||
return
|
||||
}
|
||||
next.splice(overIndex, 0, moved)
|
||||
items.value = next
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-tabs :items="items" :styles="{ item: { cursor: 'move' } }">
|
||||
<template #renderTabBar="{ TabNavListComponent, props }">
|
||||
<component :is="TabNavListComponent" v-bind="props">
|
||||
<template #default="node">
|
||||
<div
|
||||
class="draggable-tab" :class="{ 'draggable-tab--dragging': draggingKey === getNodeKey(node) }"
|
||||
draggable="true" @dragstart="(event) => handleDragStart(getNodeKey(node), event)" @dragover.prevent
|
||||
@drop="(event) => handleDrop(getNodeKey(node), event)" @dragend="handleDragEnd"
|
||||
>
|
||||
<component :is="node" />
|
||||
</div>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.draggable-tab {
|
||||
margin-left: var(--ant-tabs-horizontal-item-gutter);
|
||||
}
|
||||
|
||||
.draggable-tab:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.draggable-tab--dragging {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
# Customized bar of tab
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Use `renderTabBar` with a sticky container to pin the tab bar.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { theme } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface TabItem {
|
||||
key: string
|
||||
label: string
|
||||
content: string
|
||||
style?: Record<string, any>
|
||||
}
|
||||
|
||||
const items: TabItem[] = Array.from({ length: 3 }).map((_, i) => {
|
||||
const id = String(i + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab ${id}`,
|
||||
content: `Content of Tab Pane ${id}`,
|
||||
style: i === 0 ? { height: '200px' } : undefined,
|
||||
}
|
||||
})
|
||||
|
||||
const { token } = theme.useToken()
|
||||
const activeKey = ref('1')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-tabs v-model:active-key="activeKey" :items="items">
|
||||
<template #renderTabBar="{ TabNavListComponent, props }">
|
||||
<div
|
||||
style="position: sticky; top: 64px; z-index: 1;"
|
||||
:style="{ background: token.colorBgContainer }"
|
||||
>
|
||||
<component :is="TabNavListComponent" v-bind="props" />
|
||||
</div>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
# Disabled
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Disabled a tab.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: 'Tab 1',
|
||||
content: 'Tab 1',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Tab 2',
|
||||
content: 'Tab 2',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: 'Tab 3',
|
||||
content: 'Tab 3',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-tabs default-active-key="1" :items="items" />
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
# Add & close tab
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Only card type Tabs support adding & closable. Use `closable={false}` to disable close.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface TabItem {
|
||||
key: string
|
||||
label: string
|
||||
content: string
|
||||
closable?: boolean
|
||||
}
|
||||
|
||||
const initialItems: TabItem[] = [
|
||||
{ key: '1', label: 'Tab 1', content: 'Content of Tab 1' },
|
||||
{ key: '2', label: 'Tab 2', content: 'Content of Tab 2' },
|
||||
{ key: '3', label: 'Tab 3', content: 'Content of Tab 3', closable: false },
|
||||
]
|
||||
|
||||
const activeKey = ref(initialItems[0]?.key ?? '1')
|
||||
const items = ref<TabItem[]>([...initialItems])
|
||||
let newTabIndex = 0
|
||||
|
||||
function onChange(newActiveKey: string) {
|
||||
activeKey.value = newActiveKey
|
||||
}
|
||||
|
||||
function add() {
|
||||
const newActiveKey = `newTab${newTabIndex++}`
|
||||
items.value = [...items.value, { key: newActiveKey, label: 'New Tab', content: 'Content of new Tab' }]
|
||||
activeKey.value = newActiveKey
|
||||
}
|
||||
|
||||
function remove(targetKey: string) {
|
||||
let newActiveKey = activeKey.value
|
||||
let lastIndex = -1
|
||||
items.value.forEach((item, i) => {
|
||||
if (item.key === targetKey) {
|
||||
lastIndex = i - 1
|
||||
}
|
||||
})
|
||||
const newPanes = items.value.filter(item => item.key !== targetKey)
|
||||
if (newPanes.length && newActiveKey === targetKey) {
|
||||
if (lastIndex >= 0 && newPanes[lastIndex]) {
|
||||
newActiveKey = newPanes[lastIndex]!.key!
|
||||
}
|
||||
else if (newPanes[0]) {
|
||||
newActiveKey = newPanes[0]!.key!
|
||||
}
|
||||
}
|
||||
items.value = newPanes
|
||||
activeKey.value = newActiveKey
|
||||
}
|
||||
|
||||
function onEdit(targetKey: string | MouseEvent | KeyboardEvent, action: 'add' | 'remove') {
|
||||
if (action === 'add') {
|
||||
add()
|
||||
}
|
||||
else {
|
||||
remove(targetKey as string)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-tabs
|
||||
v-model:active-key="activeKey"
|
||||
type="editable-card"
|
||||
:items="items"
|
||||
@change="onChange"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,79 @@
|
||||
# Extra content
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
You can add extra actions to the right or left or even both side of Tabs.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
import { computed, h, ref, resolveComponent } from 'vue'
|
||||
|
||||
type PositionType = 'left' | 'right'
|
||||
|
||||
const options = ['left', 'right']
|
||||
|
||||
const items: TabsProps['items'] = Array.from({ length: 3 }).map((_, i) => {
|
||||
const id = String(i + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab ${id}`,
|
||||
content: `Content of tab ${id}`,
|
||||
}
|
||||
})
|
||||
|
||||
const position = ref<PositionType[]>(['left', 'right'])
|
||||
|
||||
const slot = computed(() => {
|
||||
if (position.value.length === 0) {
|
||||
return null
|
||||
}
|
||||
const result: Record<string, boolean> = {}
|
||||
position.value.forEach((dir) => {
|
||||
result[dir] = true
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const extraContent = computed(() =>
|
||||
h(resolveComponent('a-button') as any, null, { default: () => 'Extra Action' }),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-tabs :items="items" :tab-bar-extra-content="extraContent" />
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<div>You can also specify its direction or both side</div>
|
||||
<a-divider />
|
||||
<a-checkbox-group v-model:value="position" :options="options" />
|
||||
<br>
|
||||
<br>
|
||||
<a-tabs :items="items">
|
||||
<template v-if="slot?.left" #leftExtra>
|
||||
<a-button class="tabs-extra-demo-button">
|
||||
Left Extra Action
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-if="slot?.right" #rightExtra>
|
||||
<a-button>Right Extra Action</a-button>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tabs-extra-demo-button {
|
||||
margin-inline-end: 16px;
|
||||
}
|
||||
|
||||
.ant-row-rtl .tabs-extra-demo-button {
|
||||
margin-inline-end: 0;
|
||||
margin-inline-start: 16px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
# Icon
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
The Tab with Icon.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
import { AndroidOutlined, AppleOutlined } from '@antdv-next/icons'
|
||||
import { h } from 'vue'
|
||||
|
||||
const icons = [AppleOutlined, AndroidOutlined]
|
||||
|
||||
const items: TabsProps['items'] = icons.map((Icon, i) => {
|
||||
const id = String(i + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab ${id}`,
|
||||
content: `Tab ${id}`,
|
||||
icon: () => h(Icon),
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-tabs default-active-key="2" :items="items" />
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
# Placement
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Tab's placement: start, end, top or bottom. Will auto switch to `top` in mobile.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
type TabPlacement = 'top' | 'bottom' | 'start' | 'end'
|
||||
|
||||
interface TabItem {
|
||||
key: string
|
||||
label: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const tabPlacement = ref<TabPlacement>('start')
|
||||
|
||||
const items: TabItem[] = Array.from({ length: 3 }).map((_, i) => {
|
||||
const id = String(i + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab ${id}`,
|
||||
content: `Content of Tab ${id}`,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-space style="margin-bottom: 24px;">
|
||||
Tab placement:
|
||||
<a-radio-group v-model:value="tabPlacement">
|
||||
<a-radio-button value="top">
|
||||
top
|
||||
</a-radio-button>
|
||||
<a-radio-button value="bottom">
|
||||
bottom
|
||||
</a-radio-button>
|
||||
<a-radio-button value="start">
|
||||
start
|
||||
</a-radio-button>
|
||||
<a-radio-button value="end">
|
||||
end
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-space>
|
||||
<a-tabs :tab-placement="tabPlacement" :items="items" />
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,114 @@
|
||||
# Size
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Large size tabs are usually used in page header, and small size could be used in Modal.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
type TabSize = 'small' | 'middle' | 'large'
|
||||
|
||||
interface TabItem {
|
||||
key: string
|
||||
label: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const size = ref<TabSize>('small')
|
||||
const activeKey = ref('1')
|
||||
const items = ref<TabItem[]>([
|
||||
{ key: '1', label: 'Tab 1', content: 'Content of editable tab 1' },
|
||||
{ key: '2', label: 'Tab 2', content: 'Content of editable tab 2' },
|
||||
{ key: '3', label: 'Tab 3', content: 'Content of editable tab 3' },
|
||||
])
|
||||
|
||||
const staticItems: TabItem[] = Array.from({ length: 3 }).map((_, i) => {
|
||||
const id = String(i + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab ${id}`,
|
||||
content: `Content of tab ${id}`,
|
||||
}
|
||||
})
|
||||
|
||||
const cardItems: TabItem[] = Array.from({ length: 3 }).map((_, i) => {
|
||||
const id = String(i + 1)
|
||||
return {
|
||||
key: id,
|
||||
label: `Card Tab ${id}`,
|
||||
content: `Content of card tab ${id}`,
|
||||
}
|
||||
})
|
||||
|
||||
function add() {
|
||||
const newKey = String(items.value.length + 1)
|
||||
items.value.push({
|
||||
key: newKey,
|
||||
label: `Tab ${newKey}`,
|
||||
content: `Content of editable tab ${newKey}`,
|
||||
})
|
||||
activeKey.value = newKey
|
||||
}
|
||||
|
||||
function remove(targetKey: string) {
|
||||
const targetIndex = items.value.findIndex(item => item.key === targetKey)
|
||||
const newItems = items.value.filter(item => item.key !== targetKey)
|
||||
|
||||
if (newItems.length && targetKey === activeKey.value) {
|
||||
const newIndex = targetIndex === newItems.length ? targetIndex - 1 : targetIndex
|
||||
activeKey.value = newItems[newIndex]?.key ?? newItems[0]?.key ?? '1'
|
||||
}
|
||||
|
||||
items.value = newItems
|
||||
}
|
||||
|
||||
function onEdit(targetKey: string | MouseEvent | KeyboardEvent, action: 'add' | 'remove') {
|
||||
if (action === 'add') {
|
||||
add()
|
||||
}
|
||||
else if (typeof targetKey === 'string') {
|
||||
remove(targetKey)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-radio-group v-model:value="size" style="margin-bottom: 16px;">
|
||||
<a-radio-button value="small">
|
||||
Small
|
||||
</a-radio-button>
|
||||
<a-radio-button value="middle">
|
||||
Middle
|
||||
</a-radio-button>
|
||||
<a-radio-button value="large">
|
||||
Large
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-tabs
|
||||
default-active-key="1"
|
||||
:size="size"
|
||||
style="margin-bottom: 32px;"
|
||||
:items="staticItems"
|
||||
/>
|
||||
<a-tabs
|
||||
default-active-key="1"
|
||||
type="card"
|
||||
:size="size"
|
||||
style="margin-bottom: 32px;"
|
||||
:items="cardItems"
|
||||
/>
|
||||
<a-tabs
|
||||
v-model:active-key="activeKey"
|
||||
type="editable-card"
|
||||
:size="size"
|
||||
:items="items"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,55 @@
|
||||
# Slide
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
In order to fit in more tabs, they can slide left and right (or up and down).
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
type TabPlacement = 'top' | 'left'
|
||||
|
||||
interface TabItem {
|
||||
key: string
|
||||
label: string
|
||||
content: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const mode = ref<TabPlacement>('top')
|
||||
|
||||
const items: TabItem[] = Array.from({ length: 30 }, (_, i) => {
|
||||
const id = String(i)
|
||||
return {
|
||||
key: id,
|
||||
label: `Tab-${id}`,
|
||||
content: `Content of tab ${id}`,
|
||||
disabled: i === 28,
|
||||
}
|
||||
})
|
||||
|
||||
const activeKey = ref('0')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-radio-group v-model:value="mode" style="margin-bottom: 8px;">
|
||||
<a-radio-button value="top">
|
||||
Horizontal
|
||||
</a-radio-button>
|
||||
<a-radio-button value="left">
|
||||
Vertical
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-tabs
|
||||
v-model:active-key="activeKey"
|
||||
:tab-placement="mode"
|
||||
style="height: 220px;"
|
||||
:items="items"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
# Custom semantic dom styling
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TabsProps } from 'antdv-next'
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{ key: '1', label: 'Tab 1', content: 'Content of Tab Pane 1' },
|
||||
{ key: '2', label: 'Tab 2', content: 'Content of Tab Pane 2' },
|
||||
{ key: '3', label: 'Tab 3', content: 'Content of Tab Pane 3' },
|
||||
]
|
||||
|
||||
const classes: TabsProps['classes'] = {
|
||||
root: 'custom-tabs-root',
|
||||
}
|
||||
|
||||
const stylesObject: TabsProps['styles'] = {
|
||||
root: { borderWidth: '2px', borderStyle: 'dashed', padding: '16px', marginBottom: '10px' },
|
||||
header: { backgroundColor: 'rgba(245,245,245,0.5)' },
|
||||
item: { fontWeight: 'bold', color: '#1890ff', padding: `6px 10px` },
|
||||
indicator: { backgroundColor: 'rgba(255,77,79, 0.3)', height: '4px' },
|
||||
body: { backgroundColor: 'rgba(230,247,255,0.8)' },
|
||||
content: { padding: '16px' },
|
||||
}
|
||||
|
||||
const stylesFn: TabsProps['styles'] = (info) => {
|
||||
if (info.props.type === 'card') {
|
||||
return {
|
||||
root: { backgroundColor: 'rgba(250,250,250, 0.8)', borderColor: '#d9d9d9' },
|
||||
header: { textAlign: 'start' },
|
||||
} satisfies TabsProps['styles']
|
||||
}
|
||||
return {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-flex vertical gap="middle">
|
||||
<a-tabs :items="items" :classes="classes" :styles="stylesObject" />
|
||||
<a-tabs type="card" :items="items" :classes="classes" :styles="stylesFn" />
|
||||
</a-flex>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.custom-tabs-root {
|
||||
border-width: 2px;
|
||||
border-style: dashed;
|
||||
padding: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: Tabs
|
||||
description: Tabs make it easy to explore and switch between different views.
|
||||
---
|
||||
|
||||
## When To Use
|
||||
Antdv Next has 3 types of Tabs for different situations.
|
||||
|
||||
- Card Tabs: for managing too many closeable views.
|
||||
- Normal Tabs: for functional aspects of a page.
|
||||
- [Radio.Button](../radio/docs.md/#radio-demo-radiobutton): for secondary tabs.
|
||||
|
||||
## Demos
|
||||
|
||||
| Demo | Path |
|
||||
| --- | --- |
|
||||
| Basic | demo/basic.md |
|
||||
| Disabled | demo/disabled.md |
|
||||
| Centered | demo/centered.md |
|
||||
| Icon | demo/icon.md |
|
||||
| Indicator | demo/custom-indicator.md |
|
||||
| Slide | demo/slide.md |
|
||||
| Extra content | demo/extra.md |
|
||||
| Size | demo/size.md |
|
||||
| Placement | demo/placement.md |
|
||||
| Card type tab | demo/card.md |
|
||||
| Add & close tab | demo/editable-card.md |
|
||||
| Customized trigger of new tab | demo/custom-add-trigger.md |
|
||||
| Customized bar of tab | demo/custom-tab-bar.md |
|
||||
| Draggable Tabs | demo/custom-tab-bar-node.md |
|
||||
| Custom semantic dom styling | demo/style-class.md |
|
||||
|
||||
## API
|
||||
|
||||
### Property
|
||||
Common props ref:[Common props](../../docs/vue/common-props.md)
|
||||
|
||||
| Property | Description | Type | Default | Version | [Global Config](../config-provider/docs.md#component-config) |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| activeKey | Current TabPane's key, support `v-model:active-key` | string | - | - | × |
|
||||
| addIcon | Customize add icon, only works with `type="editable-card"` | VueNode | `<PlusOutlined />` | - | ✓ |
|
||||
| animated | Whether to change tabs with animation | boolean \| \{ inkBar: boolean, tabPane: boolean \} | \{ inkBar: true, tabPane: false \} | - | × |
|
||||
| centered | Centers tabs | boolean | false | - | × |
|
||||
| classes | Customize class for each semantic structure inside the component. Supports object or function | TabsClassNamesType | - | - | ✓ |
|
||||
| defaultActiveKey | Initial active TabPane's key, if `activeKey` is not set | string | `The key of first tab` | - | × |
|
||||
| hideAdd | Hide plus icon or not. Only works while `type="editable-card"` | boolean | false | - | × |
|
||||
| indicator | Customize `size` and `align` of indicator | \{ size?: number \| (origin: number) => number; align?: `start` \| `center` \| `end` \} | - | - | ✓ |
|
||||
| items | Configure tab content | TabItemType[] | [] | - | × |
|
||||
| more | Customize the collapse menu | MoreProps | \{ icon: `<EllipsisOutlined />`, trigger: 'hover' \} | - | ✓ |
|
||||
| moreIcon | Custom icon of the collapse menu | VueNode | `<EllipsisOutlined />` | - | ✓ |
|
||||
| removeIcon | The custom icon of remove, only works with `type="editable-card"` | VueNode | `<CloseOutlined />` | - | ✓ |
|
||||
| renderTabBar | Replace the TabBar | (ctx: \{ props: any; TabNavListComponent: any \}) => VueNode | - | - | × |
|
||||
| size | Preset tab bar size | `large` \| `middle` \| `small` | `middle` | - | × |
|
||||
| styles | Customize inline style for each semantic structure inside the component. Supports object or function | TabsStylesType | - | - | ✓ |
|
||||
| tabBarExtraContent | Extra content in tab bar | VueNode \| \{ left?: VueNode, right?: VueNode \} | - | - | × |
|
||||
| tabBarGutter | The gap between tabs | number | - | - | × |
|
||||
| tabBarStyle | Tab bar style object | CSSProperties | - | - | × |
|
||||
| tabPlacement | Placement of tabs | `top` \| `end` \| `bottom` \| `start` | `top` | - | × |
|
||||
| destroyOnHidden | Whether destroy inactive TabPane when change tab | boolean | false | - | × |
|
||||
| type | Basic style of tabs | `line` \| `card` \| `editable-card` | `line` | - | × |
|
||||
|
||||
### TabItemType
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| closeIcon | Customize close icon in TabPane's head. Setting to `null` or `false` hides the close button | VueNode | - | - |
|
||||
| destroyOnHidden | Whether destroy inactive TabPane when change tab | boolean | false | - |
|
||||
| disabled | Set TabPane disabled | boolean | false | - |
|
||||
| forceRender | Forced render of content in tabs, not lazy render after clicking on tabs | boolean | false | - |
|
||||
| key | TabPane's key | string | - | - |
|
||||
| label | Tab header text element | VueNode | - | - |
|
||||
| icon | Tab header icon element | VueNode | - | - |
|
||||
| content | Tab content element | VueNode | - | - |
|
||||
| closable | Whether a close (x) button is visible, only works while `type="editable-card"` | boolean | true | - |
|
||||
|
||||
### MoreProps
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| icon | The custom icon | VueNode | - | - |
|
||||
| DropdownProps | Dropdown props | DropdownProps | - | - |
|
||||
|
||||
### Events
|
||||
| Event | Description | Type | Version |
|
||||
| --- | --- | --- | --- |
|
||||
| change | Callback executed when active tab is changed | (activeKey: string) => void | - |
|
||||
| edit | Callback executed when tab is added or removed. Only works while `type="editable-card"` | (e: MouseEvent \| KeyboardEvent \| string, action: 'add' \| 'remove') => void | - |
|
||||
| tabClick | Callback executed when tab is clicked | (key: string, event: MouseEvent) => void | - |
|
||||
| tabScroll | Trigger when tab scroll | (\{ direction: `left` \| `right` \| `top` \| `bottom` \}) => void | - |
|
||||
|
||||
### Slots
|
||||
| Slot | Description | Type | Version |
|
||||
| --- | --- | --- | --- |
|
||||
| addIcon | Customize add icon, only works with `type="editable-card"` | () => any | - |
|
||||
| moreIcon | Custom collapse menu icon | () => any | - |
|
||||
| removeIcon | The custom icon of remove, only works with `type="editable-card"` | () => any | - |
|
||||
| labelRender | Custom label render | (args: \{ item: TabItemType; index: number \}) => any | - |
|
||||
| contentRender | Custom content render | (args: \{ item: TabItemType; index: number \}) => any | - |
|
||||
| renderTabBar | Replace the TabBar | (args: \{ props: any; TabNavListComponent: any \}) => any | - |
|
||||
| rightExtra | Extra content on the right | () => any | - |
|
||||
| leftExtra | Extra content on the left | () => any | - |
|
||||
|
||||
### Methods
|
||||
| Method | Description | Type | Version |
|
||||
| --- | --- | --- | --- |
|
||||
| nativeElement | Root element | any | - |
|
||||
|
||||
## Semantic DOM
|
||||
|
||||
| _semantic | demo/_semantic.md |
|
||||
@@ -0,0 +1,51 @@
|
||||
# tabs Token
|
||||
|
||||
Use these variables through `theme.components.Tabs`. This document lists definitions only and does not include values.
|
||||
|
||||
```vue
|
||||
<a-config-provider
|
||||
:theme="{
|
||||
token: {
|
||||
// Global token configuration
|
||||
},
|
||||
components: {
|
||||
Tabs: {
|
||||
// Token name: value
|
||||
},
|
||||
},
|
||||
}"
|
||||
>
|
||||
...
|
||||
</a-config-provider>
|
||||
```
|
||||
|
||||
## Token List
|
||||
|
||||
| Token | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `zIndexPopup` | `number` | z-index of dropdown menu |
|
||||
| `cardBg` | `string` | Background color of card tab |
|
||||
| `cardHeight` | `number` | Height of card tab |
|
||||
| `cardHeightSM` | `number` | Height of small card tab |
|
||||
| `cardHeightLG` | `number` | Height of large card tab |
|
||||
| `cardPadding` | `string` | Padding of card tab |
|
||||
| `cardPaddingSM` | `string` | Padding of small card tab |
|
||||
| `cardPaddingLG` | `string` | Padding of large card tab |
|
||||
| `titleFontSize` | `number` | Font size of title |
|
||||
| `titleFontSizeLG` | `number` | Font size of large title |
|
||||
| `titleFontSizeSM` | `number` | Font size of small title |
|
||||
| `inkBarColor` | `string` | Color of indicator |
|
||||
| `horizontalMargin` | `string` | Horizontal margin of horizontal tab |
|
||||
| `horizontalItemGutter` | `number` | Horizontal gutter of horizontal tab |
|
||||
| `horizontalItemMargin` | `string` | Horizontal margin of horizontal tab item |
|
||||
| `horizontalItemMarginRTL` | `string` | Horizontal margin of horizontal tab item (RTL) |
|
||||
| `horizontalItemPadding` | `string` | Horizontal padding of horizontal tab item |
|
||||
| `horizontalItemPaddingLG` | `string` | Horizontal padding of large horizontal tab item |
|
||||
| `horizontalItemPaddingSM` | `string` | Horizontal padding of small horizontal tab item |
|
||||
| `verticalItemPadding` | `string` | Vertical padding of vertical tab item |
|
||||
| `verticalItemMargin` | `string` | Vertical margin of vertical tab item |
|
||||
| `itemColor` | `string` | Text color of tab |
|
||||
| `itemActiveColor` | `string` | Text color of active tab |
|
||||
| `itemHoverColor` | `string` | Text color of hover tab |
|
||||
| `itemSelectedColor` | `string` | Text color of selected tab |
|
||||
| `cardGutter` | `number` | Gutter of card tab |
|
||||
Reference in new issue
Block a user