feat(components): [transfer] add custom empty slot for transfer panels (#18929)

* feat(components): [transfer] add custom empty slot for transfer panels

* docs: [transfer] add description and example

* test(components): add transfer component test

* docs: add version tag

* Update docs/en-US/component/transfer.md

Co-authored-by: btea <2356281422@qq.com>

---------

Co-authored-by: qiang <qw13131wang@gmail.com>
Co-authored-by: btea <2356281422@qq.com>
This commit is contained in:
Evan 2024-11-24 11:46:48 +08:00 committed by GitHub
parent e28e0f303e
commit 349a2e9c16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 112 additions and 8 deletions

View File

@ -33,6 +33,16 @@ transfer/customizable
:::
## Custom empty content ^(2.9.0)
You can customize the content when the list is empty or when no filtering results are found.
:::demo Use `left-empty` and `right-empty` slots to customize the empty content for each panel.
transfer/empty-content
:::
## Prop aliases
By default, Transfer looks for `key`, `label` and `disabled` in a data item. If your data items have different key names, you can use the `props` attribute to define aliases.
@ -74,11 +84,13 @@ transfer/prop-alias
### Transfer Slots
| Name | Description |
| ------------ | ------------------------------------------------------------------ |
| default | Custom content for data items. The scope parameter is `{ option }` |
| left-footer | content of left list footer |
| right-footer | content of right list footer |
| Name | Description |
| -------------------- | -------------------------------------------------------------------- |
| default | Custom content for data items. The scope parameter is `{ option }` |
| left-footer | content of left list footer |
| right-footer | content of right list footer |
| left-empty ^(2.9.0) | content when left panel is empty or when no data matches the filter |
| right-empty ^(2.9.0) | content when right panel is empty or when no data matches the filter |
### Transfer Exposes

View File

@ -0,0 +1,33 @@
<template>
<el-transfer v-model="value" :data="data">
<template #left-empty>
<el-empty :image-size="60" description="No data" />
</template>
<template #right-empty>
<el-empty :image-size="60" description="No data" />
</template>
</el-transfer>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
interface DataItem {
key: number
label: string
disabled: boolean
}
const generateData = (): DataItem[] => {
const data: DataItem[] = []
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: `Option ${i}`,
disabled: i % 4 === 0,
})
}
return data
}
const data = ref(generateData())
const value = ref([])
</script>

View File

@ -332,4 +332,52 @@ describe('Transfer', () => {
`)
})
})
describe('empty slots', () => {
it('render left-empty and right-empty slots', () => {
const wrapper = mount(() => (
<Transfer
data={[]}
v-slots={{
'left-empty': () => <span>No data</span>,
'right-empty': () => <span>No data</span>,
}}
/>
))
const panels = wrapper.findAll('.el-transfer-panel__empty')
expect(panels).toHaveLength(2)
expect(panels[0].text()).toBe('No data')
expect(panels[1].text()).toBe('No data')
})
it('render default empty content when slots not provided', () => {
const wrapper = mount(() => <Transfer data={[]} />)
const panels = wrapper.findAll('.el-transfer-panel__empty')
expect(panels).toHaveLength(2)
expect(panels[0].text()).toBe('No data')
expect(panels[1].text()).toBe('No data')
})
it('show no match content when filtering', async () => {
const wrapper = mount(() => (
<Transfer
data={getTestData()}
filterable={true}
v-slots={{
'left-empty': () => <span>No data</span>,
}}
/>
))
const leftPanel: any = wrapper.findComponent({ name: 'ElTransferPanel' })
leftPanel.vm.query = 'non-existing-data'
await nextTick()
const emptyContent = wrapper.find('.el-transfer-panel__empty')
expect(emptyContent.exists()).toBe(true)
expect(emptyContent.text()).toBe('No data')
})
})
})

View File

@ -40,9 +40,14 @@
<option-content :option="optionRender?.(item)" />
</el-checkbox>
</el-checkbox-group>
<p v-show="hasNoMatch || isEmpty(data)" :class="ns.be('panel', 'empty')">
{{ hasNoMatch ? t('el.transfer.noMatch') : t('el.transfer.noData') }}
</p>
<div
v-show="hasNoMatch || isEmpty(data)"
:class="ns.be('panel', 'empty')"
>
<slot name="empty">
{{ hasNoMatch ? t('el.transfer.noMatch') : t('el.transfer.noData') }}
</slot>
</div>
</div>
<p v-if="hasFooter" :class="ns.be('panel', 'footer')">
<slot />

View File

@ -13,6 +13,9 @@
:props="props.props"
@checked-change="onSourceCheckedChange"
>
<template #empty>
<slot name="left-empty" />
</template>
<slot name="left-footer" />
</transfer-panel>
<div :class="ns.e('buttons')">
@ -48,6 +51,9 @@
:props="props.props"
@checked-change="onTargetCheckedChange"
>
<template #empty>
<slot name="right-empty" />
</template>
<slot name="right-footer" />
</transfer-panel>
</div>