20260720172840
This commit is contained in:
1 parent
cda7f16d3a
commit
94207c77ff
3117 files changed
+201407
-1236
No files matched your search
+63
@@ -0,0 +1,63 @@
|
||||
# _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: 'section', desc: t('section') },
|
||||
{ name: 'header', desc: t('header') },
|
||||
{ name: 'title', desc: t('title') },
|
||||
{ name: 'body', desc: t('body') },
|
||||
{ name: 'list', desc: t('list') },
|
||||
{ name: 'item', desc: t('item') },
|
||||
{ name: 'itemIcon', desc: t('itemIcon') },
|
||||
{ name: 'itemContent', desc: t('itemContent') },
|
||||
{ name: 'footer', desc: t('footer') },
|
||||
{ name: 'actions', desc: t('actions') },
|
||||
{ name: 'source', desc: t('source'), version: '1.3.0' },
|
||||
{ name: 'target', desc: t('target'), version: '1.3.0' },
|
||||
])
|
||||
|
||||
const mockData = Array.from({ length: 20 }).map((_, i) => ({
|
||||
key: i,
|
||||
title: `content ${i + 1}`,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SemanticPreview
|
||||
component-name="Transfer"
|
||||
:semantics="semantics"
|
||||
>
|
||||
<template #default="{ classes }">
|
||||
<a-transfer
|
||||
show-search
|
||||
:titles="['Source', 'Target']"
|
||||
:data-source="mockData"
|
||||
:selected-keys="[]"
|
||||
:target-keys="[3, 9]"
|
||||
:render="(item: any) => item.title"
|
||||
:styles="{
|
||||
section: { height: '250px', width: '200px' },
|
||||
}"
|
||||
:classes="classes"
|
||||
>
|
||||
<template #footer>
|
||||
<div :style="{ padding: '8px' }">
|
||||
Custom Footer
|
||||
</div>
|
||||
</template>
|
||||
</a-transfer>
|
||||
</template>
|
||||
</SemanticPreview>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
# Custom Actions
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
You can customize operations with the `actions` prop. This example demonstrates how to customize actions, including handling disabled and loading states.
|
||||
|
||||
When `actions` is an array of strings, it will use the default Button component and set the strings as button text.
|
||||
|
||||
When `actions` is an array of Vue nodes, it will use these elements directly as action buttons, allowing you to use custom button components, such as buttons with loading state in this example.
|
||||
|
||||
Note:
|
||||
|
||||
1. When using custom buttons, the Transfer component will automatically handle the button's disabled state and click events.
|
||||
2. You can add a `disabled` property to your custom button to control its disabled state.
|
||||
3. You can add an `onClick` event handler to your custom button, which will be merged with the Transfer component's internal handler.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits } from 'antdv-next'
|
||||
import { DoubleLeftOutlined, DoubleRightOutlined } from '@antdv-next/icons'
|
||||
import { Button, message } from 'antdv-next'
|
||||
import { computed, h, ref } from 'vue'
|
||||
|
||||
interface RecordType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const mockData = Array.from({ length: 20 }).map<RecordType>((_, i) => ({
|
||||
key: i.toString(),
|
||||
title: `Content ${i + 1}`,
|
||||
description: `Description ${i + 1}`,
|
||||
}))
|
||||
|
||||
const initialTargetKeys = mockData.filter(item => Number(item.key) > 10).map(item => item.key)
|
||||
|
||||
const targetKeys = ref<string[]>(initialTargetKeys)
|
||||
const selectedKeys = ref<string[]>([])
|
||||
const loadingRight = ref(false)
|
||||
const loadingLeft = ref(false)
|
||||
|
||||
const handleChange: TransferEmits['change'] = (newTargetKeys, direction, moveKeys) => {
|
||||
targetKeys.value = newTargetKeys as string[]
|
||||
|
||||
if (direction === 'right') {
|
||||
loadingRight.value = true
|
||||
setTimeout(() => {
|
||||
loadingRight.value = false
|
||||
message.success(`Successfully added ${moveKeys.length} items to the right`)
|
||||
}, 1000)
|
||||
}
|
||||
else {
|
||||
loadingLeft.value = true
|
||||
setTimeout(() => {
|
||||
loadingLeft.value = false
|
||||
message.success(`Successfully added ${moveKeys.length} items to the left`)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectChange: TransferEmits['selectChange'] = (sourceSelectedKeys, targetSelectedKeys) => {
|
||||
selectedKeys.value = [...sourceSelectedKeys, ...targetSelectedKeys] as string[]
|
||||
}
|
||||
|
||||
const rightButtonDisabled = computed(() =>
|
||||
selectedKeys.value.length === 0 || selectedKeys.value.every(key => targetKeys.value.includes(key)),
|
||||
)
|
||||
|
||||
const leftButtonDisabled = computed(() =>
|
||||
selectedKeys.value.length === 0 || selectedKeys.value.every(key => !targetKeys.value.includes(key)),
|
||||
)
|
||||
|
||||
function handleRightButtonClick(event: MouseEvent) {
|
||||
console.log('Right button clicked', event)
|
||||
}
|
||||
|
||||
function handleLeftButtonClick(event: MouseEvent) {
|
||||
console.log('Left button clicked', event)
|
||||
}
|
||||
|
||||
const actions = computed(() => [
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'primary',
|
||||
icon: h(DoubleRightOutlined),
|
||||
loading: loadingRight.value,
|
||||
disabled: rightButtonDisabled.value,
|
||||
onClick: handleRightButtonClick,
|
||||
},
|
||||
{ default: () => 'Move To Right' },
|
||||
),
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'primary',
|
||||
icon: h(DoubleLeftOutlined),
|
||||
loading: loadingLeft.value,
|
||||
disabled: leftButtonDisabled.value,
|
||||
onClick: handleLeftButtonClick,
|
||||
},
|
||||
{ default: () => 'Move To Left' },
|
||||
),
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
:data-source="mockData"
|
||||
:actions="actions"
|
||||
:render="(item: RecordType) => item.title"
|
||||
@change="handleChange"
|
||||
@select-change="handleSelectChange"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,91 @@
|
||||
# Advanced
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Advanced Usage of Transfer.
|
||||
|
||||
You can customize the labels of the transfer buttons, the width and height of the columns, and what should be displayed in the footer.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits, TransferProps } from 'antdv-next'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
interface RecordType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
chosen: boolean
|
||||
}
|
||||
|
||||
const mockData = ref<RecordType[]>([])
|
||||
const targetKeys = ref<TransferProps['targetKeys']>([])
|
||||
|
||||
function getMock() {
|
||||
const tempTargetKeys: string[] = []
|
||||
const tempMockData: RecordType[] = []
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const data = {
|
||||
key: i.toString(),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
chosen: i % 2 === 0,
|
||||
}
|
||||
if (data.chosen) {
|
||||
tempTargetKeys.push(data.key)
|
||||
}
|
||||
tempMockData.push(data)
|
||||
}
|
||||
mockData.value = tempMockData
|
||||
targetKeys.value = tempTargetKeys
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getMock()
|
||||
})
|
||||
|
||||
const handleChange: TransferEmits['change'] = (newTargetKeys) => {
|
||||
targetKeys.value = newTargetKeys
|
||||
}
|
||||
|
||||
const panelStyles: TransferProps['styles'] = {
|
||||
section: {
|
||||
width: 250,
|
||||
height: 300,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
:data-source="mockData"
|
||||
show-search
|
||||
:styles="panelStyles"
|
||||
:actions="['to right', 'to left']"
|
||||
:render="(item: RecordType) => `${item.title}-${item.description}`"
|
||||
@change="handleChange"
|
||||
>
|
||||
<template #footer="{ info }">
|
||||
<a-button
|
||||
v-if="info?.direction === 'left'"
|
||||
size="small"
|
||||
style="display: flex; margin: 8px; margin-inline-end: auto"
|
||||
@click="getMock"
|
||||
>
|
||||
Left button reload
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else
|
||||
size="small"
|
||||
style="display: flex; margin: 8px; margin-inline-start: auto"
|
||||
@click="getMock"
|
||||
>
|
||||
Right button reload
|
||||
</a-button>
|
||||
</template>
|
||||
</a-transfer>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
# Basic
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
The most basic usage of `Transfer` involves providing the source data and target keys arrays, plus the rendering and some callback functions.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits, TransferProps } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface RecordType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const mockData = Array.from({ length: 20 }).map<RecordType>((_, i) => ({
|
||||
key: i.toString(),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
}))
|
||||
|
||||
const initialTargetKeys = mockData.filter(item => Number(item.key) > 10).map(item => item.key)
|
||||
|
||||
const targetKeys = ref<TransferProps['targetKeys']>(initialTargetKeys)
|
||||
const selectedKeys = ref<TransferProps['targetKeys']>([])
|
||||
|
||||
const handleChange: TransferEmits['change'] = (nextTargetKeys, direction, moveKeys) => {
|
||||
console.log('targetKeys:', nextTargetKeys)
|
||||
console.log('direction:', direction)
|
||||
console.log('moveKeys:', moveKeys)
|
||||
targetKeys.value = nextTargetKeys
|
||||
}
|
||||
|
||||
const handleSelectChange: TransferEmits['selectChange'] = (sourceSelectedKeys, targetSelectedKeys) => {
|
||||
console.log('sourceSelectedKeys:', sourceSelectedKeys)
|
||||
console.log('targetSelectedKeys:', targetSelectedKeys)
|
||||
selectedKeys.value = [...sourceSelectedKeys, ...targetSelectedKeys]
|
||||
}
|
||||
|
||||
const handleScroll: TransferEmits['scroll'] = (direction, e) => {
|
||||
console.log('direction:', direction)
|
||||
console.log('target:', (e.target as HTMLElement | null)?.className)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
:data-source="mockData"
|
||||
:titles="['Source', 'Target']"
|
||||
:render="(item: RecordType) => item.title"
|
||||
@change="handleChange"
|
||||
@select-change="handleSelectChange"
|
||||
@scroll="handleScroll"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
# Custom datasource
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Customize each Transfer Item, allowing you to render a complex datasource.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits, TransferProps } from 'antdv-next'
|
||||
import { h, onMounted, ref } from 'vue'
|
||||
|
||||
interface RecordType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
chosen: boolean
|
||||
}
|
||||
|
||||
const mockData = ref<RecordType[]>([])
|
||||
const targetKeys = ref<TransferProps['targetKeys']>([])
|
||||
|
||||
function getMock() {
|
||||
const tempTargetKeys: string[] = []
|
||||
const tempMockData: RecordType[] = []
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const data = {
|
||||
key: i.toString(),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
chosen: i % 2 === 0,
|
||||
}
|
||||
if (data.chosen) {
|
||||
tempTargetKeys.push(data.key)
|
||||
}
|
||||
tempMockData.push(data)
|
||||
}
|
||||
mockData.value = tempMockData
|
||||
targetKeys.value = tempTargetKeys
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getMock()
|
||||
})
|
||||
|
||||
const handleChange: TransferEmits['change'] = (newTargetKeys, direction, moveKeys) => {
|
||||
console.log(newTargetKeys, direction, moveKeys)
|
||||
targetKeys.value = newTargetKeys
|
||||
}
|
||||
|
||||
function renderItem(item: RecordType) {
|
||||
const customLabel = h('span', { class: 'transfer-custom-item' }, `${item.title} - ${item.description}`)
|
||||
return {
|
||||
label: customLabel,
|
||||
value: item.title,
|
||||
}
|
||||
}
|
||||
|
||||
const panelStyles: TransferProps['styles'] = {
|
||||
section: {
|
||||
width: '300px',
|
||||
height: '300px',
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
:data-source="mockData"
|
||||
:styles="panelStyles"
|
||||
:render="renderItem"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.transfer-custom-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# Custom Select All Labels
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Custom the labels for select all checkboxes.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits, TransferProps } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface RecordType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const mockData = Array.from({ length: 10 }).map<RecordType>((_, i) => ({
|
||||
key: i.toString(),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
}))
|
||||
|
||||
const oriTargetKeys = mockData.filter(item => Number(item.key) % 3 > 1).map(item => item.key)
|
||||
|
||||
const selectAllLabels: TransferProps['selectAllLabels'] = [
|
||||
'Select All',
|
||||
({ selectedCount, totalCount }) => `${selectedCount}/${totalCount}`,
|
||||
]
|
||||
|
||||
const targetKeys = ref(oriTargetKeys)
|
||||
|
||||
const handleChange: TransferEmits['change'] = (newTargetKeys) => {
|
||||
targetKeys.value = newTargetKeys
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
:data-source="mockData"
|
||||
:render="(item: RecordType) => item.title"
|
||||
:select-all-labels="selectAllLabels"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# Pagination
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Store a large amount of items with pagination.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits } from 'antdv-next'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
interface RecordType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
chosen: boolean
|
||||
}
|
||||
|
||||
const oneWay = ref(false)
|
||||
const mockData = ref<RecordType[]>([])
|
||||
const targetKeys = ref<string[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
const newTargetKeys: string[] = []
|
||||
const newMockData: RecordType[] = []
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
const data = {
|
||||
key: i.toString(),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
chosen: i % 2 === 0,
|
||||
}
|
||||
if (data.chosen) {
|
||||
newTargetKeys.push(data.key)
|
||||
}
|
||||
newMockData.push(data)
|
||||
}
|
||||
targetKeys.value = newTargetKeys
|
||||
mockData.value = newMockData
|
||||
})
|
||||
|
||||
const handleChange: TransferEmits['change'] = (newTargetKeys, direction, moveKeys) => {
|
||||
console.log(newTargetKeys, direction, moveKeys)
|
||||
targetKeys.value = newTargetKeys
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
:data-source="mockData"
|
||||
:render="(item: RecordType) => item.title"
|
||||
:one-way="oneWay"
|
||||
pagination
|
||||
@change="handleChange"
|
||||
/>
|
||||
<a-switch
|
||||
v-model:checked="oneWay"
|
||||
checked-children="one way"
|
||||
un-checked-children="one way"
|
||||
style="margin-top: 16px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
# One Way
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Use `oneWay` to make Transfer the one way style.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface RecordType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
const mockData = Array.from({ length: 20 }).map<RecordType>((_, i) => ({
|
||||
key: i.toString(),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
disabled: i % 3 < 1,
|
||||
}))
|
||||
|
||||
const oriTargetKeys = mockData.filter(item => Number(item.key) % 3 > 1).map(item => item.key)
|
||||
|
||||
const targetKeys = ref(oriTargetKeys)
|
||||
const selectedKeys = ref<string[]>([])
|
||||
const disabled = ref(false)
|
||||
|
||||
const handleChange: TransferEmits['change'] = (newTargetKeys, direction, moveKeys) => {
|
||||
targetKeys.value = newTargetKeys
|
||||
console.log('targetKeys: ', newTargetKeys)
|
||||
console.log('direction: ', direction)
|
||||
console.log('moveKeys: ', moveKeys)
|
||||
}
|
||||
|
||||
const handleSelectChange: TransferEmits['selectChange'] = (sourceSelectedKeys, targetSelectedKeys) => {
|
||||
selectedKeys.value = [...sourceSelectedKeys, ...targetSelectedKeys] as string[]
|
||||
console.log('sourceSelectedKeys: ', sourceSelectedKeys)
|
||||
console.log('targetSelectedKeys: ', targetSelectedKeys)
|
||||
}
|
||||
|
||||
const handleScroll: TransferEmits['scroll'] = (direction, e) => {
|
||||
console.log('direction:', direction)
|
||||
console.log('target:', (e.target as HTMLElement | null)?.className)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
:data-source="mockData"
|
||||
:titles="['Source', 'Target']"
|
||||
:render="(item: RecordType) => item.title"
|
||||
:disabled="disabled"
|
||||
one-way
|
||||
@change="handleChange"
|
||||
@select-change="handleSelectChange"
|
||||
@scroll="handleScroll"
|
||||
/>
|
||||
<a-switch
|
||||
v-model:checked="disabled"
|
||||
checked-children="disabled"
|
||||
un-checked-children="disabled"
|
||||
style="margin-top: 16px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
# Search
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Transfer with a search box.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits, TransferProps } from 'antdv-next'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
interface RecordType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
chosen: boolean
|
||||
}
|
||||
|
||||
const mockData = ref<RecordType[]>([])
|
||||
const targetKeys = ref<TransferProps['targetKeys']>([])
|
||||
|
||||
function getMock() {
|
||||
const tempTargetKeys: string[] = []
|
||||
const tempMockData: RecordType[] = []
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const data = {
|
||||
key: i.toString(),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
chosen: i % 2 === 0,
|
||||
}
|
||||
if (data.chosen) {
|
||||
tempTargetKeys.push(data.key)
|
||||
}
|
||||
tempMockData.push(data)
|
||||
}
|
||||
mockData.value = tempMockData
|
||||
targetKeys.value = tempTargetKeys
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getMock()
|
||||
})
|
||||
|
||||
function filterOption(inputValue: string, option: RecordType) {
|
||||
return option.description.includes(inputValue)
|
||||
}
|
||||
|
||||
const handleChange: TransferEmits['change'] = (newTargetKeys) => {
|
||||
targetKeys.value = newTargetKeys
|
||||
}
|
||||
|
||||
const handleSearch: TransferEmits['search'] = (direction, value) => {
|
||||
console.log('search:', direction, value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
:data-source="mockData"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
:render="(item: RecordType) => item.title"
|
||||
@change="handleChange"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# Status
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Add status to Transfer with `status`, which could be `error` or `warning`.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const emptyData: any[] = []
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-flex vertical gap="middle">
|
||||
<a-transfer :data-source="emptyData" status="error" />
|
||||
<a-transfer :data-source="emptyData" status="warning" show-search />
|
||||
</a-flex>
|
||||
</template>
|
||||
```
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# Custom semantic dom styling
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
You can customize the semantic dom style of Transfers by passing objects/functions through `classes` and `styles`.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferProps } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const mockData = Array.from({ length: 20 }).map<any>((_, i) => ({
|
||||
key: i.toString(),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
}))
|
||||
|
||||
const initialTargetKeys = mockData.filter(item => Number(item.key) > 10).map(item => item.key)
|
||||
|
||||
const targetKeysError = ref(initialTargetKeys)
|
||||
const targetKeysWarning = ref(initialTargetKeys)
|
||||
|
||||
const classes: TransferProps['classes'] = {
|
||||
section: 'transfer-demo-section',
|
||||
header: 'transfer-demo-header',
|
||||
actions: 'transfer-demo-actions',
|
||||
}
|
||||
|
||||
const stylesObject: TransferProps['styles'] = {
|
||||
header: { fontWeight: 'bold' },
|
||||
}
|
||||
|
||||
const stylesFn: TransferProps['styles'] = (info) => {
|
||||
if (info.props.status === 'warning') {
|
||||
return {
|
||||
section: { backgroundColor: 'rgba(246,255,237, 0.6)', borderColor: '#b7eb8f' },
|
||||
header: { color: '#8DBCC7', fontWeight: 'normal' },
|
||||
} as TransferProps['styles']
|
||||
}
|
||||
return {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-flex vertical gap="large" style="width: 100%">
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeysError"
|
||||
:data-source="mockData"
|
||||
:render="(item) => item.title"
|
||||
status="error"
|
||||
:classes="classes"
|
||||
:styles="stylesObject"
|
||||
/>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeysWarning"
|
||||
:data-source="mockData"
|
||||
:render="(item) => item.title"
|
||||
status="warning"
|
||||
:classes="classes"
|
||||
:styles="stylesFn"
|
||||
/>
|
||||
</a-flex>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.transfer-demo-section {
|
||||
background-color: rgba(250, 250, 250, 0.5);
|
||||
}
|
||||
|
||||
.transfer-demo-header {
|
||||
color: var(--ant-color-primary);
|
||||
}
|
||||
|
||||
.transfer-demo-actions button {
|
||||
background-color: rgba(255, 242, 232, 0.6);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
# Table Transfer
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Use the Table component as the customize render list.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TableProps, TransferEmits, TransferProps } from 'antdv-next'
|
||||
import { Table } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface DataType {
|
||||
key: string
|
||||
title: string
|
||||
description: string
|
||||
tag: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const mockTags = ['cat', 'dog', 'bird'] as const
|
||||
|
||||
const mockData: DataType[] = Array.from({ length: 20 }, (_, i) => ({
|
||||
key: String(i),
|
||||
title: `content${i + 1}`,
|
||||
description: `description of content${i + 1}`,
|
||||
tag: mockTags[i % 3]!,
|
||||
}))
|
||||
|
||||
const targetKeys = ref<string[]>([])
|
||||
|
||||
const interactionEnabled = ref(false)
|
||||
|
||||
const columns: TableProps['columns'] = [
|
||||
{ title: 'Name', dataIndex: 'title', key: 'title' },
|
||||
{ title: 'Tag', dataIndex: 'tag', key: 'tag' },
|
||||
{ title: 'Description', dataIndex: 'description', key: 'description' },
|
||||
]
|
||||
|
||||
const filterOption: NonNullable<TransferProps['filterOption']> = (input: string, item: DataType) => {
|
||||
const q = input.trim().toLowerCase()
|
||||
if (!q)
|
||||
return true
|
||||
return item.title!.toLowerCase().includes(q)
|
||||
}
|
||||
|
||||
const handleChange: TransferEmits['change'] = (keys) => {
|
||||
targetKeys.value = keys as string[]
|
||||
}
|
||||
|
||||
const panelStyles: TransferProps['styles'] = {
|
||||
section: {
|
||||
width: 420,
|
||||
minHeight: 520,
|
||||
},
|
||||
}
|
||||
|
||||
const tablePagination: TableProps['pagination'] = {
|
||||
pageSize: 10,
|
||||
showSizeChanger: false,
|
||||
hideOnSinglePage: false,
|
||||
}
|
||||
|
||||
interface ListSlotProps {
|
||||
direction: 'left' | 'right'
|
||||
disabled: boolean
|
||||
filteredItems: DataType[]
|
||||
selectedKeys: string[]
|
||||
onItemSelect: (key: string, selected: boolean) => void
|
||||
onItemSelectAll: (keys: string[], checkAll: boolean | 'replace') => void
|
||||
}
|
||||
|
||||
function tableRowSelection(slot: ListSlotProps): TableProps['rowSelection'] {
|
||||
return {
|
||||
getCheckboxProps: record => ({
|
||||
disabled: slot.disabled || Boolean((record as DataType).disabled),
|
||||
}),
|
||||
onChange: (selectedRowKeys) => {
|
||||
slot.onItemSelectAll(selectedRowKeys as string[], 'replace')
|
||||
},
|
||||
selectedRowKeys: slot.selectedKeys,
|
||||
selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT, Table.SELECTION_NONE],
|
||||
}
|
||||
}
|
||||
|
||||
function tableOnRow(slot: ListSlotProps): TableProps['onRow'] {
|
||||
return (record) => {
|
||||
const row = record as DataType
|
||||
return {
|
||||
onClick: (e: MouseEvent) => {
|
||||
if (slot.disabled || row.disabled)
|
||||
return
|
||||
const el = e.target as HTMLElement | null
|
||||
if (!el)
|
||||
return
|
||||
if (el.closest('.ant-table-selection-column'))
|
||||
return
|
||||
const key = row.key
|
||||
const selected = slot.selectedKeys.includes(key)
|
||||
slot.onItemSelect(key, !selected)
|
||||
},
|
||||
style: { cursor: slot.disabled || row.disabled ? undefined : 'pointer' },
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
:data-source="mockData"
|
||||
:disabled="interactionEnabled"
|
||||
:show-select-all="false"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
:styles="panelStyles"
|
||||
:render="(item: DataType) => item.title"
|
||||
@change="handleChange"
|
||||
>
|
||||
<template #default="slotProps">
|
||||
<a-table
|
||||
size="small"
|
||||
:row-selection="tableRowSelection(slotProps as ListSlotProps)"
|
||||
:on-row="tableOnRow(slotProps as ListSlotProps)"
|
||||
:columns="columns"
|
||||
:data-source="slotProps.filteredItems"
|
||||
:pagination="tablePagination"
|
||||
>
|
||||
<template #bodyCell="{ column, text }">
|
||||
<template v-if="column.key === 'tag'">
|
||||
<a-tag color="cyan">
|
||||
{{ String(text).toUpperCase() }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
</a-transfer>
|
||||
<a-space align="center" style="margin-top: 12px">
|
||||
<a-switch
|
||||
v-model:checked="interactionEnabled"
|
||||
checked-children="disabled"
|
||||
un-checked-children="disabled"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
# Tree Transfer
|
||||
|
||||
## Description (en-US)
|
||||
|
||||
Customize the render list with a Tree component.
|
||||
|
||||
## Source
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { TransferEmits } from 'antdv-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface TreeNode {
|
||||
key: string
|
||||
title: string
|
||||
children?: TreeNode[]
|
||||
}
|
||||
|
||||
const treeData: TreeNode[] = [
|
||||
{ key: '0-0', title: '0-0' },
|
||||
{
|
||||
key: '0-1',
|
||||
title: '0-1',
|
||||
children: [
|
||||
{ key: '0-1-0', title: '0-1-0' },
|
||||
{ key: '0-1-1', title: '0-1-1' },
|
||||
],
|
||||
},
|
||||
{ key: '0-2', title: '0-2' },
|
||||
{ key: '0-3', title: '0-3' },
|
||||
{ key: '0-4', title: '0-4' },
|
||||
]
|
||||
|
||||
const targetKeys = ref<string[]>([])
|
||||
|
||||
function flattenData(list: TreeNode[] = [], result: TreeNode[] = []) {
|
||||
list.forEach((item) => {
|
||||
result.push(item)
|
||||
if (item.children) {
|
||||
flattenData(item.children, result)
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
const transferDataSource = flattenData(treeData, []).map(item => ({
|
||||
key: item.key,
|
||||
title: item.title,
|
||||
}))
|
||||
|
||||
const isChecked = (selectedKeys: string[], eventKey: string) => selectedKeys.includes(eventKey)
|
||||
|
||||
function generateTree(treeNodes: TreeNode[] = [], checkedKeys: string[] = []): TreeNode[] {
|
||||
return treeNodes.map(({ children, ...props }) => ({
|
||||
...props,
|
||||
disabled: checkedKeys.includes(props.key),
|
||||
children: children ? generateTree(children, checkedKeys) : undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
const handleChange: TransferEmits['change'] = (keys) => {
|
||||
targetKeys.value = keys as string[]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-transfer
|
||||
v-model:target-keys="targetKeys"
|
||||
class="tree-transfer"
|
||||
:data-source="transferDataSource"
|
||||
:render="(item) => item.title"
|
||||
:show-select-all="false"
|
||||
@change="handleChange"
|
||||
>
|
||||
<template #default="{ direction, selectedKeys, onItemSelect }">
|
||||
<div v-if="direction === 'left'" style="padding: 8px">
|
||||
<a-tree
|
||||
block-node
|
||||
checkable
|
||||
check-strictly
|
||||
default-expand-all
|
||||
:checked-keys="[...selectedKeys, ...targetKeys]"
|
||||
:tree-data="generateTree(treeData, targetKeys)"
|
||||
@check="(_, info) => onItemSelect(info.node.key, !isChecked([...selectedKeys, ...targetKeys], info.node.key))"
|
||||
@select="(_, info) => onItemSelect(info.node.key, !isChecked([...selectedKeys, ...targetKeys], info.node.key))"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</a-transfer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.tree-transfer .ant-transfer-list:first-child) {
|
||||
flex: none;
|
||||
width: 50%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Transfer
|
||||
description: Double column transfer choice box.
|
||||
---
|
||||
|
||||
## When To Use
|
||||
- It is a select control essentially which can be use for selecting multiple items.
|
||||
- Transfer can display more information for items and take up more space.
|
||||
|
||||
Transfer the elements between two columns in an intuitive and efficient way.
|
||||
|
||||
One or more elements can be selected from either column, one click on the proper `direction` button, and the transfer is done. The left column is considered the `source` and the right column is considered the `target`. As you can see in the API description, these names are reflected in.
|
||||
|
||||
> Notice: Transfer is a controlled component, uncontrolled mode is not supported.
|
||||
|
||||
## Demos
|
||||
|
||||
| Demo | Path |
|
||||
| --- | --- |
|
||||
| Basic | demo/basic.md |
|
||||
| One Way | demo/oneWay.md |
|
||||
| Search | demo/search.md |
|
||||
| Advanced | demo/advanced.md |
|
||||
| Custom datasource | demo/custom-item.md |
|
||||
| Custom Actions | demo/actions.md |
|
||||
| Pagination | demo/large-data.md |
|
||||
| Table Transfer | demo/table-transfer.md |
|
||||
| Tree Transfer | demo/tree-transfer.md |
|
||||
| Status | demo/status.md |
|
||||
| Custom semantic dom styling | demo/style-class.md |
|
||||
| Custom Select All Labels | demo/custom-select-all-labels.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) |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| actions | A set of operations that are sorted from top to bottom. When an array of strings is provided, default buttons will be used; when an array of VueNode is provided, custom elements will be used | VueNode[] | [`>`, `<`] | 6.0.0 | × |
|
||||
| classes | Customize class for each semantic structure inside the component. Supports object or function | TransferClassNamesType | - | - | ✓ |
|
||||
| dataSource | Used for setting the source data. The elements that are part of this array will be present the left column. Except the elements whose keys are included in `targetKeys` prop | TransferItem[] | [] | - | × |
|
||||
| disabled | Whether disabled transfer | boolean | false | - | × |
|
||||
| filterOption | A function to determine whether an item should show in search result list, only works when searching | (inputValue: string, option: TransferItem, direction: `left` \| `right`) => boolean | - | - | × |
|
||||
| footer | A function used for rendering the footer | (props: TransferListProps, direction: `left` \| `right`) => VueNode | - | - | × |
|
||||
| locale | The i18n text including filter, empty text, item unit, etc | TransferLocale | - | - | × |
|
||||
| oneWay | Display as single direction style | boolean | false | - | × |
|
||||
| pagination | Use pagination. Not work in render props | boolean \| { pageSize: number; simple: boolean; showSizeChanger?: boolean; showLessItems?: boolean } | false | - | × |
|
||||
| render | The function to generate the item shown on a column. Based on an record (element of the dataSource array), this function should return a VueNode which is generated from that record. Also, it can return a plain object with `value` and `label`, `label` is a VueNode and `value` is for title | (record: TransferItem) => VueNode | - | - | × |
|
||||
| rowKey | Specify the key that will be used for uniquely identify each element | (record: TransferItem) => string | - | - | × |
|
||||
| selectAllLabels | A set of customized labels for select all checkboxes on the header | (VueNode \| (info: { selectedCount: number; totalCount: number }) => VueNode)[] | - | - | × |
|
||||
| selectedKeys | A set of keys of selected items, support `v-model:selected-keys` | string[] | [] | - | × |
|
||||
| selectionsIcon | Custom dropdown icon | VueNode | - | - | ✓ |
|
||||
| showSearch | If included, a search box is shown on each column | boolean \| { placeholder?: string; defaultValue?: string } | false | - | × |
|
||||
| showSelectAll | Show select all checkbox on the header | boolean | true | - | × |
|
||||
| status | Set validation status | `error` \| `warning` | - | - | × |
|
||||
| styles | Customize inline style for each semantic structure inside the component. Supports object or function | TransferStylesType | - | - | ✓ |
|
||||
| targetKeys | A set of keys of elements that are listed on the right column, support `v-model:target-keys` | string[] | [] | - | × |
|
||||
| titles | A set of titles that are sorted from left to right | VueNode[] | - | - | × |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Type | Version |
|
||||
| --- | --- | --- | --- |
|
||||
| change | A callback function that is executed when the transfer between columns is complete | (targetKeys: string[], direction: `left` \| `right`, moveKeys: string[]) => void | - |
|
||||
| scroll | A callback function which is executed when scroll options list | (direction: `left` \| `right`, event: Event) => void | - |
|
||||
| search | A callback function which is executed when search field are changed | (direction: `left` \| `right`, value: string) => void | - |
|
||||
| selectChange | A callback function which is executed when selected items are changed | (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => void | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| Slot | Description | Type | Version |
|
||||
| --- | --- | --- | --- |
|
||||
| actions | Custom operation buttons | (direction: `left` \| `right`) => VueNode | - |
|
||||
| footer | Custom footer | (props: TransferListProps, direction: `left` \| `right`) => VueNode | - |
|
||||
| render | Custom item render | (item: TransferItem) => VueNode | - |
|
||||
| notFoundContent | Custom empty content | (direction: `left` \| `right`) => VueNode | - |
|
||||
| selectAllLabels | Custom select all label | (info: { selectedCount: number; totalCount: number; direction: `left` \| `right` }) => VueNode | - |
|
||||
|
||||
## Types
|
||||
|
||||
### Render Props
|
||||
|
||||
Transfer accept `children` to customize render list, using follow props:
|
||||
|
||||
| Property | Description | Type | Version |
|
||||
| --- | --- | --- | --- |
|
||||
| direction | List render direction | `left` \| `right` | - |
|
||||
| disabled | Disable list or not | boolean | - |
|
||||
| filteredItems | Filtered items | TransferItem[] | - |
|
||||
| selectedKeys | Selected items | string[] | - |
|
||||
| onItemSelect | Select item | (key: string, selected: boolean) => void | - |
|
||||
| onItemSelectAll | Select a group of items | (keys: string[], selected: boolean) => void | - |
|
||||
|
||||
## Warning
|
||||
|
||||
According the standard of Vue, the key should always be supplied directly to the elements in the array. In Transfer, the keys should be set on the elements included in `dataSource` array. By default, `key` property is used as an unique identifier.
|
||||
|
||||
If there's no `key` in your data, you should use `rowKey` to specify the key that will be used for uniquely identify each element.
|
||||
|
||||
```vue
|
||||
<!-- eg. your primary key is `uid` -->
|
||||
<a-transfer :row-key="(record) => record.uid" />
|
||||
```
|
||||
|
||||
## Semantic DOM
|
||||
|
||||
| _semantic | demo/_semantic.md |
|
||||
|
||||
## FAQ
|
||||
|
||||
### How to support fetch and present data from a remote server in Transfer column.
|
||||
In order to keep the page number synchronized, you can disable columns you checked without removing the option.
|
||||
@@ -0,0 +1,31 @@
|
||||
# transfer Token
|
||||
|
||||
Use these variables through `theme.components.Transfer`. This document lists definitions only and does not include values.
|
||||
|
||||
```vue
|
||||
<a-config-provider
|
||||
:theme="{
|
||||
token: {
|
||||
// Global token configuration
|
||||
},
|
||||
components: {
|
||||
Transfer: {
|
||||
// Token name: value
|
||||
},
|
||||
},
|
||||
}"
|
||||
>
|
||||
...
|
||||
</a-config-provider>
|
||||
```
|
||||
|
||||
## Token List
|
||||
|
||||
| Token | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `listWidth` | `string \| number` | Width of list |
|
||||
| `listWidthLG` | `string \| number` | Width of large list |
|
||||
| `listHeight` | `string \| number` | Height of list |
|
||||
| `itemHeight` | `string \| number` | Height of list item |
|
||||
| `itemPaddingBlock` | `string \| number` | Vertical padding of list item |
|
||||
| `headerHeight` | `string \| number` | Height of header |
|
||||
Reference in new issue
Block a user