refactor:transfer (#6247)

* refactor:transfer

* merge v4 branch & fix theme interface conflict

* docs:update & refactor: transfer type
pull/6263/head^2
果冻橙 2023-02-13 20:21:29 +08:00 committed by GitHub
parent bbfb3cef7d
commit 68d295d7ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 556 additions and 443 deletions

View File

@ -41,7 +41,7 @@ import './table/style';
// import './progress/style';
import './timeline/style';
import './input-number/style';
import './transfer/style';
// import './transfer/style';
import './tree/style';
import './upload/style';
// import './layout/style';

View File

@ -42,7 +42,7 @@ import type { ComponentToken as SpinComponentToken } from '../../spin/style';
import type { ComponentToken as TagComponentToken } from '../../tag/style';
// import type { ComponentToken as TimelineComponentToken } from '../../timeline/style';
import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style';
// import type { ComponentToken as TransferComponentToken } from '../../transfer/style';
import type { ComponentToken as TransferComponentToken } from '../../transfer/style';
import type { ComponentToken as TypographyComponentToken } from '../../typography/style';
// import type { ComponentToken as UploadComponentToken } from '../../upload/style';
// import type { ComponentToken as TourComponentToken } from '../../tour/style';
@ -100,7 +100,7 @@ export interface ComponentTokenMap {
TreeSelect?: {};
Typography?: TypographyComponentToken;
// Timeline?: TimelineComponentToken;
// Transfer?: TransferComponentToken;
Transfer?: TransferComponentToken;
// Tabs?: TabsComponentToken;
// Calendar?: CalendarComponentToken;
// Steps?: StepsComponentToken;

View File

@ -5,13 +5,14 @@ import ListItem from './ListItem';
import Pagination from '../pagination';
import PropTypes from '../_util/vue-types';
import type { TransferItem } from '.';
import { booleanType } from '../_util/type';
export const transferListBodyProps = {
prefixCls: String,
filteredRenderItems: PropTypes.array.def([]),
selectedKeys: PropTypes.array,
disabled: { type: Boolean, default: undefined },
showRemove: { type: Boolean, default: undefined },
disabled: booleanType(),
showRemove: booleanType(),
pagination: PropTypes.any,
onItemSelect: Function,
onScroll: Function,

View File

@ -8,6 +8,7 @@ import TransButton from '../_util/transButton';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import type { ExtractPropTypes } from 'vue';
import { defineComponent } from 'vue';
import { booleanType } from '../_util/type';
function noop() {}
@ -15,10 +16,10 @@ export const transferListItemProps = {
renderedText: PropTypes.any,
renderedEl: PropTypes.any,
item: PropTypes.any,
checked: { type: Boolean, default: undefined },
checked: booleanType(),
prefixCls: String,
disabled: { type: Boolean, default: undefined },
showRemove: { type: Boolean, default: undefined },
disabled: booleanType(),
showRemove: booleanType(),
onClick: Function,
onRemove: Function,
};

View File

@ -0,0 +1,102 @@
<docs>
---
order: 4
title:
zh-CN: 自定义全选文字
en-US: Custom Select All Labels
---
## zh-CN
自定义穿梭框全选按钮的文字
## en-US
Custom the labels for select all checkboxs.
</docs>
<template>
<div>
<a-transfer
v-model:target-keys="targetKeys"
v-model:selected-keys="selectedKeys"
:data-source="mockData"
:titles="['Source', 'Target']"
:render="item => item.title"
:select-all-labels="selectAllLabels"
:disabled="disabled"
@change="handleChange"
@selectChange="handleSelectChange"
@scroll="handleScroll"
/>
<a-switch
v-model:checked="disabled"
un-checked-children="enabled"
checked-children="disabled"
style="margin-top: 16px"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import type { SelectAllLabel } from 'ant-design-vue/es/transfer';
interface MockData {
key: string;
title: string;
description: string;
disabled: boolean;
}
const mockData: MockData[] = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 3 < 1,
});
}
const oriTargetKeys = mockData.filter(item => +item.key % 3 > 1).map(item => item.key);
export default defineComponent({
data() {
const disabled = ref<boolean>(false);
const targetKeys = ref<string[]>(oriTargetKeys);
const selectedKeys = ref<string[]>(['1', '4']);
const handleChange = (nextTargetKeys: string[], direction: string, moveKeys: string[]) => {
console.log('targetKeys: ', nextTargetKeys);
console.log('direction: ', direction);
console.log('moveKeys: ', moveKeys);
};
const handleSelectChange = (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => {
console.log('sourceSelectedKeys: ', sourceSelectedKeys);
console.log('targetSelectedKeys: ', targetSelectedKeys);
};
const handleScroll = (direction: string, e: Event) => {
console.log('direction:', direction);
console.log('target:', e.target);
};
const selectAllLabels: SelectAllLabel[] = [
'Select All',
({ selectedCount, totalCount }) => `${selectedCount}/${totalCount}`,
];
return {
mockData,
targetKeys,
selectedKeys,
disabled,
selectAllLabels,
handleChange,
handleSelectChange,
handleScroll,
};
},
});
</script>

View File

@ -9,6 +9,7 @@
<table-transfer />
<tree-transfer />
<statusVue />
<custom-select-all-labels></custom-select-all-labels>
</demo-sort>
</template>
<script lang="ts">
@ -17,6 +18,7 @@ import Oneway from './oneway.vue';
import Search from './search.vue';
import Advanced from './advanced.vue';
import CustomItem from './custom-item.vue';
import CustomSelectAllLabels from './custom-select-all-labels.vue';
import TableTransfer from './table-transfer.vue';
import TreeTransfer from './tree-transfer.vue';
import Pagination from './pagination.vue';
@ -35,6 +37,7 @@ export default defineComponent({
Search,
Advanced,
CustomItem,
CustomSelectAllLabels,
Pagination,
TableTransfer,
TreeTransfer,

View File

@ -2,7 +2,7 @@
category: Components
type: Data Entry
title: Transfer
cover: https://gw.alipayobjects.com/zos/alicdn/QAXskNI4G/Transfer.svg
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*EAApQ5ephigAAAAAAAAAAAAADrJ8AQ/original
---
Alert component for feedback.
@ -31,6 +31,7 @@ One or more elements can be selected from either column, one click on the proper
| operationStyle | A custom CSS style used for rendering the operations column | CSSProperties | - | 3.0.0 |
| pagination | Use pagination. Not work in render props | boolean \| { pageSize: number, simple: boolean, showSizeChanger?: boolean, showLessItems?: boolean } | false | 3.0.0 |
| 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 element which is generated from that record. Also, it can return a plain object with `value` and `label`, `label` is a element and `value` is for title | Function(record) \| slot | | |
| selectAllLabels | A set of customized labels for select all checkboxes on the header | VueNode \| ((info: { selectedCount: number; totalCount: number }) => VueNode); | - | 3.0.0 |
| selectedKeys(v-model) | A set of keys of selected items. | string\[] | \[] | |
| showSearch | If included, a search box is shown on each column. | boolean | false | |
| showSelectAll | Show select all checkbox on the header | boolean | true | |

View File

@ -1,4 +1,4 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import type { CSSProperties, ExtractPropTypes } from 'vue';
import { computed, watchEffect, defineComponent, ref, watch, toRaw } from 'vue';
import PropTypes from '../_util/vue-types';
import { getPropsSlot } from '../_util/props-util';
@ -8,7 +8,15 @@ import Operation from './operation';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale/en_US';
import type { VueNode } from '../_util/type';
import { withInstall } from '../_util/type';
import {
withInstall,
stringType,
arrayType,
someType,
booleanType,
objectType,
functionType,
} from '../_util/type';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import type { TransferListBodyProps } from './ListBody';
import type { PaginationType } from './interface';
@ -17,6 +25,9 @@ import type { RenderEmptyHandler } from '../config-provider/renderEmpty';
import type { InputStatus } from '../_util/statusUtils';
import { getStatusClassNames, getMergedStatus } from '../_util/statusUtils';
// CSSINJS
import useStyle from './style';
export type { TransferListProps } from './list';
export type { TransferOperationProps } from './operation';
export type { TransferSearchProps } from './search';
@ -69,40 +80,41 @@ export interface TransferLocale {
export const transferProps = () => ({
id: String,
prefixCls: String,
dataSource: { type: Array as PropType<TransferItem[]>, default: [] },
disabled: { type: Boolean, default: undefined },
targetKeys: { type: Array as PropType<string[]>, default: undefined },
selectedKeys: { type: Array as PropType<string[]>, default: undefined },
render: { type: Function as PropType<TransferRender<TransferItem>> },
listStyle: {
type: [Function, Object] as PropType<((style: ListStyle) => CSSProperties) | CSSProperties>,
default: () => ({}),
},
operationStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
titles: { type: Array as PropType<string[]> },
operations: { type: Array as PropType<string[]> },
showSearch: { type: Boolean, default: false },
filterOption: { type: Function as PropType<(inputValue: string, item: TransferItem) => boolean> },
dataSource: arrayType<TransferItem[]>([]),
disabled: booleanType(),
targetKeys: arrayType<string[]>(),
selectedKeys: arrayType<string[]>(),
render: functionType<TransferRender<TransferItem>>(),
listStyle: someType<((style: ListStyle) => CSSProperties) | CSSProperties>(
[Function, Object],
() => ({}),
),
operationStyle: objectType<CSSProperties>(undefined as CSSProperties),
titles: arrayType<string[]>(),
operations: arrayType<string[]>(),
showSearch: booleanType(false),
filterOption: functionType<(inputValue: string, item: TransferItem) => boolean>(),
searchPlaceholder: String,
notFoundContent: PropTypes.any,
locale: { type: Object as PropType<Partial<TransferLocale>>, default: () => ({}) },
rowKey: { type: Function as PropType<(record: TransferItem) => string> },
showSelectAll: { type: Boolean, default: undefined },
selectAllLabels: { type: Array as PropType<SelectAllLabel[]> },
children: { type: Function as PropType<(props: TransferListBodyProps) => VueNode> },
oneWay: { type: Boolean, default: undefined },
pagination: { type: [Object, Boolean] as PropType<PaginationType>, default: undefined },
status: String as PropType<InputStatus>,
onChange: Function as PropType<
(targetKeys: string[], direction: TransferDirection, moveKeys: string[]) => void
>,
onSelectChange: Function as PropType<
locale: objectType(),
rowKey: functionType<(record: TransferItem) => string>(),
showSelectAll: booleanType(),
selectAllLabels: arrayType<SelectAllLabel[]>(),
children: functionType<(props: TransferListBodyProps) => VueNode>(),
oneWay: booleanType(),
pagination: someType<PaginationType>([Object, Boolean]),
status: stringType<InputStatus>(),
onChange:
functionType<
(targetKeys: string[], direction: TransferDirection, moveKeys: string[]) => void
>(),
onSelectChange: functionType<
(sourceSelectedKeys: string[], targetSelectedKeys: string[]) => void
>,
onSearch: Function as PropType<(direction: TransferDirection, value: string) => void>,
onScroll: Function as PropType<(direction: TransferDirection, e: UIEvent) => void>,
'onUpdate:targetKeys': Function as PropType<(keys: string[]) => void>,
'onUpdate:selectedKeys': Function as PropType<(keys: string[]) => void>,
onSearch: functionType<(direction: TransferDirection, value: string) => void>(),
onScroll: functionType<(direction: TransferDirection, e: UIEvent) => void>(),
'onUpdate:targetKeys': functionType<(keys: string[]) => void>(),
'onUpdate:selectedKeys': functionType<(keys: string[]) => void>(),
});
export type TransferProps = Partial<ExtractPropTypes<ReturnType<typeof transferProps>>>;
@ -125,6 +137,10 @@ const Transfer = defineComponent({
// emits: ['update:targetKeys', 'update:selectedKeys', 'change', 'search', 'scroll', 'selectChange'],
setup(props, { emit, attrs, slots, expose }) {
const { configProvider, prefixCls, direction } = useConfigInject('transfer', props);
// style
const [wrapSSR, hashId] = useStyle(prefixCls);
const sourceSelectedKeys = ref([]);
const targetSelectedKeys = ref([]);
@ -349,6 +365,7 @@ const Transfer = defineComponent({
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
},
getStatusClassNames(prefixCls.value, mergedStatus.value, formItemInputContext.hasFeedback),
hashId.value,
);
const titles = props.titles;
const leftTitle =
@ -356,7 +373,7 @@ const Transfer = defineComponent({
const rightTitle =
(titles && titles[1]) ?? slots.rightTitle?.() ?? (locale.titles || ['', ''])[1];
return (
<div class={cls} style={style as CSSProperties} id={id}>
<div {...attrs} class={cls} style={style as CSSProperties} id={id}>
<List
key="leftList"
prefixCls={`${prefixCls.value}-list`}
@ -422,13 +439,14 @@ const Transfer = defineComponent({
</div>
);
};
return () => (
<LocaleReceiver
componentName="Transfer"
defaultLocale={defaultLocale.Transfer}
children={renderTransfer}
/>
);
return () =>
wrapSSR(
<LocaleReceiver
componentName="Transfer"
defaultLocale={defaultLocale.Transfer}
children={renderTransfer}
/>,
);
},
});

View File

@ -3,7 +3,7 @@ category: Components
type: 数据录入
title: Transfer
subtitle: 穿梭框
cover: https://gw.alipayobjects.com/zos/alicdn/QAXskNI4G/Transfer.svg
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*EAApQ5ephigAAAAAAAAAAAAADrJ8AQ/original
---
双栏穿梭选择框。
@ -32,6 +32,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QAXskNI4G/Transfer.svg
| operationStyle | 操作栏的自定义样式 | CSSProperties | - | 3.0.0 |
| pagination | 使用分页样式,自定义渲染列表下无效 | boolean \| { pageSize: number, simple: boolean, showSizeChanger?: boolean, showLessItems?: boolean } | flase | 3.0.0 |
| render | 每行数据渲染函数,该函数的入参为 `dataSource` 中的项,返回值为 element。或者返回一个普通对象其中 `label` 字段为 element`value` 字段为 title | Function(record)\| slot | | |
| selectAllLabels | 自定义顶部多选框标题的集合 | VueNode \| ((info: { selectedCount: number; totalCount: number }) => VueNode); | - | 3.0.0 |
| selectedKeys(v-model) | 设置哪些项应该被选中 | string\[] | \[] | |
| showSearch | 是否显示搜索框 | boolean | false | |
| showSelectAll | 是否展示全选勾选框 | boolean | true | |

View File

@ -7,10 +7,11 @@ import Menu from '../menu';
import Dropdown from '../dropdown';
import Search from './search';
import ListBody from './ListBody';
import type { VNode, VNodeTypes, ExtractPropTypes, PropType, CSSProperties } from 'vue';
import type { VNode, VNodeTypes, ExtractPropTypes, CSSProperties } from 'vue';
import { watchEffect, computed, defineComponent, ref } from 'vue';
import type { RadioChangeEvent } from '../radio/interface';
import type { TransferDirection, TransferItem } from './index';
import { stringType, arrayType, booleanType } from '../_util/type';
const defaultRender = () => null;
@ -28,22 +29,22 @@ function getEnabledItemKeys<RecordType extends TransferItem>(items: RecordType[]
export const transferListProps = {
prefixCls: String,
dataSource: { type: Array as PropType<TransferItem[]>, default: [] },
dataSource: arrayType<TransferItem[]>([]),
filter: String,
filterOption: Function,
checkedKeys: PropTypes.arrayOf(PropTypes.string),
handleFilter: Function,
handleClear: Function,
renderItem: Function,
showSearch: { type: Boolean, default: false },
showSearch: booleanType(false),
searchPlaceholder: String,
notFoundContent: PropTypes.any,
itemUnit: String,
itemsUnit: String,
renderList: PropTypes.any,
disabled: { type: Boolean, default: undefined },
direction: String as PropType<TransferDirection>,
showSelectAll: { type: Boolean, default: undefined },
disabled: booleanType(),
direction: stringType<TransferDirection>(),
showSelectAll: booleanType(),
remove: String,
selectAll: String,
selectCurrent: String,
@ -51,7 +52,7 @@ export const transferListProps = {
removeAll: String,
removeCurrent: String,
selectAllLabel: PropTypes.any,
showRemove: { type: Boolean, default: undefined },
showRemove: booleanType(),
pagination: PropTypes.any,
onItemSelect: Function,
onItemSelectAll: Function,

View File

@ -1,52 +0,0 @@
@import '../../style/themes/index';
@transfer-prefix-cls: ~'@{ant-prefix}-transfer';
@table-prefix-cls: ~'@{ant-prefix}-table';
@input-prefix-cls: ~'@{ant-prefix}-input';
.@{transfer-prefix-cls}-customize-list {
.@{transfer-prefix-cls}-list {
flex: 1 1 50%;
width: auto;
height: auto;
min-height: @transfer-list-height;
}
// =================== Hook Components ===================
.@{table-prefix-cls}-wrapper {
.@{table-prefix-cls}-small {
border: 0;
border-radius: 0;
.@{table-prefix-cls}-selection-column {
width: 40px;
min-width: 40px;
}
> .@{table-prefix-cls}-content {
// Header background color
> .@{table-prefix-cls}-body > table > .@{table-prefix-cls}-thead > tr > th {
background: @table-header-bg;
}
.@{table-prefix-cls}-row:last-child td {
border-bottom: @border-width-base @border-style-base @border-color-split;
}
}
.@{table-prefix-cls}-body {
margin: 0;
}
}
.@{table-prefix-cls}-pagination.@{ant-prefix}-pagination {
margin: 16px 0 4px;
}
}
.@{input-prefix-cls} {
&[disabled] {
background-color: transparent;
}
}
}

View File

@ -1,221 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import '../../checkbox/style/mixin';
@import './customize';
@import './status';
@transfer-prefix-cls: ~'@{ant-prefix}-transfer';
@transfer-header-vertical-padding: ceil(
((@transfer-header-height - 1px - @font-size-base * @line-height-base) / 2)
);
.@{transfer-prefix-cls} {
.reset-component();
position: relative;
display: flex;
align-items: stretch;
&-disabled {
.@{transfer-prefix-cls}-list {
background: @transfer-disabled-bg;
}
}
&-list {
display: flex;
flex-direction: column;
width: 180px;
height: @transfer-list-height;
border: @border-width-base @border-style-base @border-color-base;
border-radius: @border-radius-base;
&-with-pagination {
width: 250px;
height: auto;
}
&-search {
.anticon-search {
color: @disabled-color;
}
}
&-header {
display: flex;
flex: none;
align-items: center;
height: @transfer-header-height;
// border-top is on the transfer dom. We should minus 1px for this
padding: (@transfer-header-vertical-padding - 1px) @control-padding-horizontal
@transfer-header-vertical-padding;
color: @text-color;
background: @component-background;
border-bottom: @border-width-base @border-style-base @border-color-split;
border-radius: @border-radius-base @border-radius-base 0 0;
> *:not(:last-child) {
margin-right: 4px;
}
> * {
flex: none;
}
&-title {
flex: auto;
overflow: hidden;
white-space: nowrap;
text-align: right;
text-overflow: ellipsis;
}
&-dropdown {
font-size: 10px;
transform: translateY(10%);
cursor: pointer;
&[disabled] {
cursor: not-allowed;
}
}
}
&-body {
display: flex;
flex: auto;
flex-direction: column;
overflow: hidden;
font-size: @font-size-base;
&-search-wrapper {
position: relative;
flex: none;
padding: @padding-sm;
}
}
&-content {
flex: auto;
margin: 0;
padding: 0;
overflow: auto;
list-style: none;
&-item {
display: flex;
align-items: center;
min-height: @transfer-item-height;
padding: @transfer-item-padding-vertical @control-padding-horizontal;
line-height: @transfer-item-height - 2 * @transfer-item-padding-vertical;
transition: all 0.3s;
> *:not(:last-child) {
margin-right: 8px;
}
> * {
flex: none;
}
&-text {
flex: auto;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&-remove {
.operation-unit();
position: relative;
color: @border-color-base;
&::after {
position: absolute;
top: -@transfer-item-padding-vertical;
right: -50%;
bottom: -@transfer-item-padding-vertical;
left: -50%;
content: '';
}
&:hover {
color: @link-hover-color;
}
}
}
&-item:not(&-item-disabled) {
&:hover {
background-color: @transfer-item-hover-bg;
cursor: pointer;
}
&.@{transfer-prefix-cls}-list-content-item-checked:hover {
background-color: @transfer-item-selected-hover-bg;
}
}
// Do not change hover style when `oneWay` mode
&-show-remove &-item:not(&-item-disabled):hover {
background: transparent;
cursor: default;
}
&-item-checked {
background-color: @item-active-bg;
}
&-item-disabled {
color: @btn-disable-color;
cursor: not-allowed;
}
}
&-pagination {
padding: @padding-xs 0;
text-align: right;
border-top: @border-width-base @border-style-base @border-color-split;
}
&-body-not-found {
flex: none;
width: 100%;
margin: auto 0;
color: @disabled-color;
text-align: center;
}
&-footer {
border-top: @border-width-base @border-style-base @border-color-split;
}
}
&-operation {
display: flex;
flex: none;
flex-direction: column;
align-self: center;
margin: 0 8px;
vertical-align: middle;
.@{ant-prefix}-btn {
display: block;
&:first-child {
margin-bottom: 4px;
}
.@{iconfont-css-prefix} {
font-size: 12px;
}
}
}
.@{ant-prefix}-empty-image {
max-height: (@transfer-header-height / 2) - 22;
}
}
@import './rtl';

View File

@ -1,13 +1,373 @@
import '../../style/index.less';
import './index.less';
import type { CSSObject } from '../../_util/cssinjs';
// style dependencies
import '../../empty/style';
import '../../checkbox/style';
import '../../button/style';
import '../../input/style';
import '../../menu/style';
import '../../dropdown/style';
import '../../pagination/style';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent, resetIcon, textEllipsis } from '../../_style';
// deps-lint-skip: form
export interface ComponentToken {
listWidth: number;
listWidthLG: number;
listHeight: number;
}
interface TransferToken extends FullToken<'Transfer'> {
transferItemHeight: number;
transferHeaderVerticalPadding: number;
transferItemPaddingVertical: number;
transferHeaderHeight: number;
}
const genTransferCustomizeStyle: GenerateStyle<TransferToken> = (
token: TransferToken,
): CSSObject => {
const { antCls, componentCls, listHeight, controlHeightLG, marginXXS, margin } = token;
const tableCls = `${antCls}-table`;
const inputCls = `${antCls}-input`;
return {
[`${componentCls}-customize-list`]: {
[`${componentCls}-list`]: {
flex: '1 1 50%',
width: 'auto',
height: 'auto',
minHeight: listHeight,
},
// =================== Hook Components ===================
[`${tableCls}-wrapper`]: {
[`${tableCls}-small`]: {
border: 0,
borderRadius: 0,
[`${tableCls}-selection-column`]: {
width: controlHeightLG,
minWidth: controlHeightLG,
},
},
[`${tableCls}-pagination${tableCls}-pagination`]: {
margin: `${margin}px 0 ${marginXXS}px`,
},
},
[`${inputCls}[disabled]`]: {
backgroundColor: 'transparent',
},
},
};
};
const genTransferStatusColor = (token: TransferToken, color: string): CSSObject => {
const { componentCls, colorBorder } = token;
return {
[`${componentCls}-list`]: {
borderColor: color,
'&-search:not([disabled])': {
borderColor: colorBorder,
},
},
};
};
const genTransferStatusStyle: GenerateStyle<TransferToken> = (token: TransferToken): CSSObject => {
const { componentCls } = token;
return {
[`${componentCls}-status-error`]: {
...genTransferStatusColor(token, token.colorError),
},
[`${componentCls}-status-warning`]: {
...genTransferStatusColor(token, token.colorWarning),
},
};
};
const genTransferListStyle: GenerateStyle<TransferToken> = (token: TransferToken): CSSObject => {
const {
componentCls,
colorBorder,
colorSplit,
lineWidth,
transferItemHeight,
transferHeaderHeight,
transferHeaderVerticalPadding,
transferItemPaddingVertical,
controlItemBgActive,
controlItemBgActiveHover,
colorTextDisabled,
listHeight,
listWidth,
listWidthLG,
fontSizeIcon,
marginXS,
paddingSM,
lineType,
iconCls,
motionDurationSlow,
} = token;
return {
display: 'flex',
flexDirection: 'column',
width: listWidth,
height: listHeight,
border: `${lineWidth}px ${lineType} ${colorBorder}`,
borderRadius: token.borderRadiusLG,
'&-with-pagination': {
width: listWidthLG,
height: 'auto',
},
'&-search': {
[`${iconCls}-search`]: {
color: colorTextDisabled,
},
},
'&-header': {
display: 'flex',
flex: 'none',
alignItems: 'center',
height: transferHeaderHeight,
// border-top is on the transfer dom. We should minus 1px for this
padding: `${
transferHeaderVerticalPadding - lineWidth
}px ${paddingSM}px ${transferHeaderVerticalPadding}px`,
color: token.colorText,
background: token.colorBgContainer,
borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`,
borderRadius: `${token.borderRadiusLG}px ${token.borderRadiusLG}px 0 0`,
'> *:not(:last-child)': {
marginInlineEnd: 4, // This is magic and fixed number, DO NOT use token since it may change.
},
'> *': {
flex: 'none',
},
'&-title': {
...textEllipsis,
flex: 'auto',
textAlign: 'end',
},
'&-dropdown': {
...resetIcon(),
fontSize: fontSizeIcon,
transform: 'translateY(10%)',
cursor: 'pointer',
'&[disabled]': {
cursor: 'not-allowed',
},
},
},
'&-body': {
display: 'flex',
flex: 'auto',
flexDirection: 'column',
overflow: 'hidden',
fontSize: token.fontSize,
'&-search-wrapper': {
position: 'relative',
flex: 'none',
padding: paddingSM,
},
},
'&-content': {
flex: 'auto',
margin: 0,
padding: 0,
overflow: 'auto',
listStyle: 'none',
'&-item': {
display: 'flex',
alignItems: 'center',
minHeight: transferItemHeight,
padding: `${transferItemPaddingVertical}px ${paddingSM}px`,
transition: `all ${motionDurationSlow}`,
'> *:not(:last-child)': {
marginInlineEnd: marginXS,
},
'> *': {
flex: 'none',
},
'&-text': {
...textEllipsis,
flex: 'auto',
},
'&-remove': {
position: 'relative',
color: colorBorder,
cursor: 'pointer',
transition: `all ${motionDurationSlow}`,
'&:hover': {
color: token.colorLinkHover,
},
'&::after': {
position: 'absolute',
insert: `-${transferItemPaddingVertical}px -50%`,
content: '""',
},
},
[`&:not(${componentCls}-list-content-item-disabled)`]: {
'&:hover': {
backgroundColor: token.controlItemBgHover,
cursor: 'pointer',
},
[`&${componentCls}-list-content-item-checked:hover`]: {
backgroundColor: controlItemBgActiveHover,
},
},
'&-checked': {
backgroundColor: controlItemBgActive,
},
'&-disabled': {
color: colorTextDisabled,
cursor: 'not-allowed',
},
},
// Do not change hover style when `oneWay` mode
[`&-show-remove ${componentCls}-list-content-item:not(${componentCls}-list-content-item-disabled):hover`]:
{
background: 'transparent',
cursor: 'default',
},
},
'&-pagination': {
padding: `${token.paddingXS}px 0`,
textAlign: 'end',
borderTop: `${lineWidth}px ${lineType} ${colorSplit}`,
},
'&-body-not-found': {
flex: 'none',
width: '100%',
margin: 'auto 0',
color: colorTextDisabled,
textAlign: 'center',
},
'&-footer': {
borderTop: `${lineWidth}px ${lineType} ${colorSplit}`,
},
};
};
const genTransferStyle: GenerateStyle<TransferToken> = (token: TransferToken): CSSObject => {
const {
antCls,
iconCls,
componentCls,
transferHeaderHeight,
marginXS,
marginXXS,
fontSizeIcon,
fontSize,
lineHeight,
} = token;
return {
[componentCls]: {
...resetComponent(token),
position: 'relative',
display: 'flex',
alignItems: 'stretch',
[`${componentCls}-disabled`]: {
[`${componentCls}-list`]: {
background: token.colorBgContainerDisabled,
},
},
[`${componentCls}-list`]: genTransferListStyle(token),
[`${componentCls}-operation`]: {
display: 'flex',
flex: 'none',
flexDirection: 'column',
alignSelf: 'center',
margin: `0 ${marginXS}px`,
verticalAlign: 'middle',
[`${antCls}-btn`]: {
display: 'block',
'&:first-child': {
marginBottom: marginXXS,
},
[iconCls]: {
fontSize: fontSizeIcon,
},
},
},
[`${antCls}-empty-image`]: {
maxHeight: transferHeaderHeight / 2 - Math.round(fontSize * lineHeight),
},
},
};
};
const genTransferRTLStyle: GenerateStyle<TransferToken> = (token: TransferToken): CSSObject => {
const { componentCls } = token;
return {
[`${componentCls}-rtl`]: {
direction: 'rtl',
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook(
'Transfer',
token => {
const { fontSize, lineHeight, lineWidth, controlHeightLG, controlHeight } = token;
const fontHeight = Math.round(fontSize * lineHeight);
const transferHeaderHeight = controlHeightLG;
const transferItemHeight = controlHeight;
const transferToken = mergeToken<TransferToken>(token, {
transferItemHeight,
transferHeaderHeight,
transferHeaderVerticalPadding: Math.ceil((transferHeaderHeight - lineWidth - fontHeight) / 2),
transferItemPaddingVertical: (transferItemHeight - fontHeight) / 2,
});
return [
genTransferStyle(transferToken),
genTransferCustomizeStyle(transferToken),
genTransferStatusStyle(transferToken),
genTransferRTLStyle(transferToken),
];
},
{
listWidth: 180,
listHeight: 200,
listWidthLG: 250,
},
);

View File

@ -1,71 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import '../../checkbox/style/mixin';
@transfer-prefix-cls: ~'@{ant-prefix}-transfer';
.@{transfer-prefix-cls} {
&-rtl {
direction: rtl;
}
&-list {
&-search {
.@{transfer-prefix-cls}-rtl & {
padding-right: @control-padding-horizontal-sm;
padding-left: 24px;
}
&-action {
.@{transfer-prefix-cls}-rtl & {
right: auto;
left: 12px;
}
}
}
&-header {
> *:not(:last-child) {
.@{transfer-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 4px;
}
}
.@{transfer-prefix-cls}-rtl & {
right: 0;
left: auto;
}
&-title {
.@{transfer-prefix-cls}-rtl & {
text-align: left;
}
}
}
&-content {
&-item {
> *:not(:last-child) {
.@{transfer-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 8px;
}
}
}
}
&-pagination {
.@{transfer-prefix-cls}-rtl & {
text-align: left;
}
}
&-footer {
.@{transfer-prefix-cls}-rtl & {
right: 0;
left: auto;
}
}
}
}

View File

@ -1,31 +0,0 @@
@import '../../input/style/mixin';
@transfer-prefix-cls: ~'@{ant-prefix}-transfer';
.transfer-status-color(@color) {
.@{transfer-prefix-cls}-list {
border-color: @color;
&-search:not([disabled]) {
border-color: @input-border-color;
&:hover {
.hover();
}
&:focus {
.active();
}
}
}
}
.@{transfer-prefix-cls} {
&-status-error {
.transfer-status-color(@error-color);
}
&-status-warning {
.transfer-status-color(@warning-color);
}
}