This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
import React from 'react';
import type { DirectionType } from '../config-provider';
export interface TransferOperationProps {
className?: string;
actions: React.ReactNode[];
moveToLeft?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
moveToRight?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
leftActive?: boolean;
rightActive?: boolean;
style?: React.CSSProperties;
disabled?: boolean;
direction?: DirectionType;
oneWay?: boolean;
}
declare const Actions: React.FC<TransferOperationProps>;
export default Actions;
+72
View File
@@ -0,0 +1,72 @@
"use client";
import React from 'react';
import LeftOutlined from "@ant-design/icons/es/icons/LeftOutlined";
import RightOutlined from "@ant-design/icons/es/icons/RightOutlined";
import Button from '../button/Button';
function getArrowIcon(type, direction) {
const isRight = type === 'right';
if (direction !== 'rtl') {
return isRight ? /*#__PURE__*/React.createElement(RightOutlined, null) : /*#__PURE__*/React.createElement(LeftOutlined, null);
}
return isRight ? /*#__PURE__*/React.createElement(LeftOutlined, null) : /*#__PURE__*/React.createElement(RightOutlined, null);
}
const Action = ({
type,
actions,
moveToLeft,
moveToRight,
leftActive,
rightActive,
direction,
disabled
}) => {
const isRight = type === 'right';
const button = isRight ? actions[0] : actions[1];
const moveHandler = isRight ? moveToRight : moveToLeft;
const active = isRight ? rightActive : leftActive;
const icon = getArrowIcon(type, direction);
if (/*#__PURE__*/React.isValidElement(button)) {
const element = button;
const onClick = event => {
element?.props?.onClick?.(event);
moveHandler?.(event);
};
return /*#__PURE__*/React.cloneElement(element, {
disabled: disabled || !active,
onClick
});
}
return /*#__PURE__*/React.createElement(Button, {
type: "primary",
size: "small",
disabled: disabled || !active,
onClick: event => moveHandler?.(event),
icon: icon
}, button);
};
const Actions = props => {
const {
className,
style,
oneWay,
actions,
...restProps
} = props;
return /*#__PURE__*/React.createElement("div", {
className: className,
style: style
}, /*#__PURE__*/React.createElement(Action, {
type: "right",
actions: actions,
...restProps
}), !oneWay && /*#__PURE__*/React.createElement(Action, {
type: "left",
actions: actions,
...restProps
}), actions.slice(oneWay ? 1 : 2));
};
if (process.env.NODE_ENV !== 'production') {
Actions.displayName = 'Actions';
}
export default Actions;
+17
View File
@@ -0,0 +1,17 @@
import * as React from 'react';
import type { KeyWiseTransferItem } from '.';
import type { TransferKey } from './interface';
import type { RenderedItem, TransferListProps } from './Section';
export declare const OmitProps: readonly ["handleFilter", "handleClear", "checkedKeys"];
export type OmitProp = (typeof OmitProps)[number];
type PartialTransferListProps<RecordType> = Omit<TransferListProps<RecordType>, OmitProp>;
export interface TransferListBodyProps<RecordType> extends PartialTransferListProps<RecordType> {
filteredItems: RecordType[];
filteredRenderItems: RenderedItem<RecordType>[];
selectedKeys: TransferKey[];
}
export interface ListBodyRef<RecordType extends KeyWiseTransferItem> {
items?: RenderedItem<RecordType>[];
}
declare const _default: React.ForwardRefExoticComponent<TransferListBodyProps<KeyWiseTransferItem> & React.RefAttributes<ListBodyRef<KeyWiseTransferItem>>>;
export default _default;
+111
View File
@@ -0,0 +1,111 @@
"use client";
import * as React from 'react';
import { useControlledState } from '@rc-component/util';
import { clsx } from 'clsx';
import { isPlainObject } from '../_util/is';
import Pagination from '../pagination';
import ListItem from './ListItem';
export const OmitProps = ['handleFilter', 'handleClear', 'checkedKeys'];
const parsePagination = pagination => {
const defaultPagination = {
simple: true,
showSizeChanger: false,
showLessItems: false
};
return {
...defaultPagination,
...pagination
};
};
const TransferListBody = (props, ref) => {
const {
prefixCls,
classNames,
styles,
filteredRenderItems,
selectedKeys,
disabled: globalDisabled,
showRemove,
pagination,
onScroll,
onItemSelect,
onItemRemove
} = props;
const [current, setCurrent] = React.useState(1);
const mergedPagination = React.useMemo(() => {
if (!pagination) {
return null;
}
const convertPagination = isPlainObject(pagination) ? pagination : {};
return parsePagination(convertPagination);
}, [pagination]);
const [pageSize, setPageSize] = useControlledState(10, mergedPagination?.pageSize);
React.useEffect(() => {
if (mergedPagination) {
const maxPageCount = Math.ceil(filteredRenderItems.length / pageSize);
setCurrent(Math.min(current, maxPageCount));
}
}, [filteredRenderItems, mergedPagination, pageSize]);
const onInternalClick = (item, e) => {
onItemSelect(item.key, !selectedKeys.includes(item.key), e);
};
const onRemove = item => {
onItemRemove?.([item.key]);
};
const onPageChange = cur => {
setCurrent(cur);
};
const onSizeChange = (cur, size) => {
setCurrent(cur);
setPageSize(size);
};
const memoizedItems = React.useMemo(() => {
const displayItems = mergedPagination ? filteredRenderItems.slice((current - 1) * pageSize, current * pageSize) : filteredRenderItems;
return displayItems;
}, [current, filteredRenderItems, mergedPagination, pageSize]);
React.useImperativeHandle(ref, () => ({
items: memoizedItems
}));
const paginationNode = mergedPagination ? (/*#__PURE__*/React.createElement(Pagination, {
size: "small",
disabled: globalDisabled,
simple: mergedPagination.simple,
pageSize: pageSize,
showLessItems: mergedPagination.showLessItems,
showSizeChanger: mergedPagination.showSizeChanger,
className: `${prefixCls}-pagination`,
total: filteredRenderItems.length,
current: current,
onChange: onPageChange,
onShowSizeChange: onSizeChange
})) : null;
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("ul", {
className: clsx(`${prefixCls}-content`, classNames.list, {
[`${prefixCls}-content-show-remove`]: showRemove
}),
style: styles.list,
onScroll: onScroll
}, (memoizedItems || []).map(({
renderedEl,
renderedText,
item
}) => (/*#__PURE__*/React.createElement(ListItem, {
key: item.key,
prefixCls: prefixCls,
classNames: classNames,
styles: styles,
item: item,
renderedText: renderedText,
renderedEl: renderedEl,
showRemove: showRemove,
onClick: onInternalClick,
onRemove: onRemove,
checked: selectedKeys.includes(item.key),
disabled: globalDisabled
})))), paginationNode);
};
if (process.env.NODE_ENV !== 'production') {
TransferListBody.displayName = 'TransferListBody';
}
export default /*#__PURE__*/React.forwardRef(TransferListBody);
+17
View File
@@ -0,0 +1,17 @@
import * as React from 'react';
import type { KeyWiseTransferItem, TransferSemanticClassNames, TransferSemanticStyles } from '.';
type ListItemProps<RecordType> = {
prefixCls: string;
classNames: TransferSemanticClassNames;
styles: TransferSemanticStyles;
renderedText?: string | number;
renderedEl: React.ReactNode;
disabled?: boolean;
checked?: boolean;
onClick: (item: RecordType, e: React.MouseEvent<HTMLLIElement, MouseEvent>) => void;
onRemove?: (item: RecordType) => void;
item: RecordType;
showRemove?: boolean;
};
declare const _default: React.MemoExoticComponent<(<RecordType extends KeyWiseTransferItem>(props: ListItemProps<RecordType>) => React.JSX.Element)>;
export default _default;
+65
View File
@@ -0,0 +1,65 @@
"use client";
import * as React from 'react';
import DeleteOutlined from "@ant-design/icons/es/icons/DeleteOutlined";
import { clsx } from 'clsx';
import { isNumber } from '../_util/is';
import Checkbox from '../checkbox';
import { useLocale } from '../locale';
import defaultLocale from '../locale/en_US';
const ListItem = props => {
const {
prefixCls,
classNames,
styles,
renderedText,
renderedEl,
item,
checked,
disabled,
onClick,
onRemove,
showRemove
} = props;
const mergedDisabled = disabled || item?.disabled;
const classes = clsx(`${prefixCls}-content-item`, classNames.item, {
[`${prefixCls}-content-item-disabled`]: mergedDisabled,
[`${prefixCls}-content-item-checked`]: checked && !mergedDisabled
});
let title;
if (typeof renderedText === 'string' || isNumber(renderedText)) {
title = String(renderedText);
}
const [contextLocale] = useLocale('Transfer', defaultLocale.Transfer);
const liProps = {
className: classes,
style: styles.item,
title
};
const labelNode = /*#__PURE__*/React.createElement("span", {
className: clsx(`${prefixCls}-content-item-text`, classNames.itemContent),
style: styles.itemContent
}, renderedEl);
if (showRemove) {
return /*#__PURE__*/React.createElement("li", {
...liProps
}, labelNode, /*#__PURE__*/React.createElement("button", {
type: "button",
disabled: mergedDisabled,
className: `${prefixCls}-content-item-remove`,
"aria-label": contextLocale?.remove,
onClick: () => onRemove?.(item)
}, /*#__PURE__*/React.createElement(DeleteOutlined, null)));
}
// Default click to select
liProps.onClick = mergedDisabled ? undefined : event => onClick(item, event);
return /*#__PURE__*/React.createElement("li", {
...liProps
}, /*#__PURE__*/React.createElement(Checkbox, {
className: clsx(`${prefixCls}-checkbox`, classNames.itemIcon),
style: styles.itemIcon,
checked: checked,
disabled: mergedDisabled
}), labelNode);
};
export default /*#__PURE__*/React.memo(ListItem);
+50
View File
@@ -0,0 +1,50 @@
import React from 'react';
import type { KeyWiseTransferItem, RenderResult, SelectAllLabel, TransferDirection, TransferLocale, TransferSearchOption, TransferSemanticClassNames, TransferSemanticStyles } from './';
import type { PaginationType, TransferKey } from './interface';
import type { TransferListBodyProps } from './ListBody';
export interface RenderedItem<RecordType> {
renderedText: string;
renderedEl: React.ReactNode;
item: RecordType;
}
type RenderListFunction<T> = (props: TransferListBodyProps<T>) => React.ReactNode;
export interface TransferListProps<RecordType> extends TransferLocale {
prefixCls: string;
style?: React.CSSProperties;
classNames: TransferSemanticClassNames;
styles: TransferSemanticStyles;
titleText: React.ReactNode;
dataSource: RecordType[];
filterOption?: (filterText: string, item: RecordType, direction: TransferDirection) => boolean;
checkedKeys: TransferKey[];
handleFilter: (e: React.ChangeEvent<HTMLInputElement>) => void;
onItemSelect: (key: TransferKey, check: boolean, e?: React.MouseEvent<Element, MouseEvent>) => void;
onItemSelectAll: (dataSource: TransferKey[], checkAll: boolean | 'replace') => void;
onItemRemove?: (keys: TransferKey[]) => void;
handleClear: () => void;
/** Render item */
render?: (item: RecordType) => RenderResult;
showSearch?: boolean | TransferSearchOption;
searchPlaceholder: string;
itemUnit: string;
itemsUnit: string;
renderList?: RenderListFunction<RecordType>;
footer?: (props: TransferListProps<RecordType>, info?: {
direction: TransferDirection;
}) => React.ReactNode;
onScroll: (e: React.UIEvent<HTMLUListElement, UIEvent>) => void;
disabled?: boolean;
direction: TransferDirection;
showSelectAll?: boolean;
selectAllLabel?: SelectAllLabel;
showRemove?: boolean;
pagination?: PaginationType;
selectionsIcon?: React.ReactNode;
}
export interface TransferCustomListBodyProps<T> extends TransferListBodyProps<T> {
}
declare const TransferSection: {
<RecordType extends KeyWiseTransferItem>(props: TransferListProps<RecordType>): React.JSX.Element;
displayName: string;
};
export default TransferSection;
+306
View File
@@ -0,0 +1,306 @@
"use client";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import React, { useMemo, useRef, useState } from 'react';
import DownOutlined from "@ant-design/icons/es/icons/DownOutlined";
import { omit } from '@rc-component/util';
import { clsx } from 'clsx';
import { isNumber, isPlainObject } from '../_util/is';
import { groupKeysMap } from '../_util/transKeys';
import Checkbox from '../checkbox';
import Dropdown from '../dropdown';
import DefaultListBody, { OmitProps } from './ListBody';
import Search from './search';
const defaultRender = () => null;
function isRenderResultPlainObject(result) {
return !!(result && ! /*#__PURE__*/React.isValidElement(result) && Object.prototype.toString.call(result) === '[object Object]');
}
function getEnabledItemKeys(items) {
return items.filter(data => !data.disabled).map(data => data.key);
}
function getTextFromRenderResult(renderResult, item) {
for (const v of [renderResult, item.title, item.key]) {
if (typeof v === 'string' || isNumber(v)) {
return String(v);
}
}
return '';
}
const isValidIcon = icon => icon !== undefined;
const getShowSearchOption = showSearch => {
if (isPlainObject(showSearch)) {
return {
...showSearch,
defaultValue: showSearch.defaultValue || ''
};
}
return {
defaultValue: '',
placeholder: ''
};
};
const TransferSection = props => {
const {
prefixCls,
style,
classNames,
styles,
dataSource = [],
titleText = '',
checkedKeys,
disabled,
showSearch = false,
searchPlaceholder,
notFoundContent,
selectAll,
deselectAll,
selectCurrent,
selectInvert,
removeAll,
removeCurrent,
showSelectAll = true,
showRemove,
pagination,
direction,
itemsUnit,
itemUnit,
selectAllLabel,
selectionsIcon,
footer,
renderList,
onItemSelectAll,
onItemRemove,
handleFilter,
handleClear,
filterOption,
render = defaultRender
} = props;
const sectionPrefixCls = `${prefixCls}-section`;
const listPrefixCls = `${prefixCls}-list`;
const searchOptions = getShowSearchOption(showSearch);
const [filterValue, setFilterValue] = useState(searchOptions.defaultValue);
const listBodyRef = useRef({});
const internalHandleFilter = e => {
setFilterValue(e.target.value);
handleFilter(e);
};
const internalHandleClear = () => {
setFilterValue('');
handleClear();
};
const matchFilter = (text, item) => {
if (typeof filterOption === 'function') {
return filterOption(filterValue, item, direction);
}
return text.includes(filterValue);
};
const customRenderListBody = listProps => {
let bodyContent = renderList ? renderList({
...listProps,
onItemSelect: (key, check) => listProps.onItemSelect(key, check)
}) : null;
const customize = !!bodyContent;
if (!customize) {
// @ts-ignore
bodyContent = /*#__PURE__*/React.createElement(DefaultListBody, {
ref: listBodyRef,
...listProps,
prefixCls: listPrefixCls
});
}
return {
customize,
bodyContent
};
};
const renderItem = item => {
const renderResult = render(item);
const isRenderResultPlain = isRenderResultPlainObject(renderResult);
const renderedEl = isRenderResultPlain ? renderResult.label : renderResult;
const renderedText = isRenderResultPlain ? renderResult.value : getTextFromRenderResult(renderResult, item);
return {
item,
renderedEl,
renderedText
};
};
const notFoundContentEle = useMemo(() => Array.isArray(notFoundContent) ? notFoundContent[direction === 'left' ? 0 : 1] : notFoundContent, [notFoundContent, direction]);
const [filteredItems, filteredRenderItems] = useMemo(() => {
const filterItems = [];
const filterRenderItems = [];
dataSource.forEach(item => {
const renderedItem = renderItem(item);
if (filterValue && !matchFilter(renderedItem.renderedText, item)) {
return;
}
filterItems.push(item);
filterRenderItems.push(renderedItem);
});
return [filterItems, filterRenderItems];
}, [dataSource, filterValue]);
const checkedActiveItems = useMemo(() => {
return filteredItems.filter(item => checkedKeys.includes(item.key) && !item.disabled);
}, [checkedKeys, filteredItems]);
const checkStatus = useMemo(() => {
if (checkedActiveItems.length === 0) {
return 'none';
}
const checkedKeysMap = groupKeysMap(checkedKeys);
if (filteredItems.every(item => checkedKeysMap.has(item.key) || !!item.disabled)) {
return 'all';
}
return 'part';
}, [checkedActiveItems.length, checkedKeys, filteredItems]);
const renderListBody = () => {
const search = showSearch ? (/*#__PURE__*/React.createElement("div", {
className: `${listPrefixCls}-body-search-wrapper`
}, /*#__PURE__*/React.createElement(Search, {
prefixCls: `${listPrefixCls}-search`,
onChange: internalHandleFilter,
handleClear: internalHandleClear,
placeholder: searchOptions.placeholder || searchPlaceholder,
value: filterValue,
disabled: disabled
}))) : null;
const {
customize,
bodyContent
} = customRenderListBody({
...omit(props, OmitProps),
filteredItems,
filteredRenderItems,
selectedKeys: checkedKeys,
classNames,
styles
});
let bodyNode;
// We should wrap customize list body in a classNamed div to use flex layout.
if (customize) {
bodyNode = /*#__PURE__*/React.createElement("div", {
className: `${listPrefixCls}-body-customize-wrapper`
}, bodyContent);
} else {
bodyNode = filteredItems.length ? bodyContent : (/*#__PURE__*/React.createElement("div", {
className: `${listPrefixCls}-body-not-found`
}, notFoundContentEle));
}
return /*#__PURE__*/React.createElement("div", {
className: clsx(`${listPrefixCls}-body`, {
[`${listPrefixCls}-body-with-search`]: showSearch
}, classNames.body),
style: styles.body
}, search, bodyNode);
};
const checkBox = /*#__PURE__*/React.createElement(Checkbox, {
disabled: dataSource.filter(d => !d.disabled).length === 0 || disabled,
checked: checkStatus === 'all',
indeterminate: checkStatus === 'part',
className: `${listPrefixCls}-checkbox`,
onChange: () => {
// Only select enabled items
onItemSelectAll?.(filteredItems.filter(item => !item.disabled).map(({
key
}) => key), checkStatus !== 'all');
}
});
const getSelectAllLabel = (selectedCount, totalCount) => {
if (selectAllLabel) {
return typeof selectAllLabel === 'function' ? selectAllLabel({
selectedCount,
totalCount
}) : selectAllLabel;
}
const unit = totalCount > 1 ? itemsUnit : itemUnit;
return /*#__PURE__*/React.createElement(React.Fragment, null, (selectedCount > 0 ? `${selectedCount}/` : '') + totalCount, " ", unit);
};
// Custom Layout
const footerDom = footer && (footer.length < 2 ? footer(props) : footer(props, {
direction
}));
// Get filtered, checked item list
const listFooter = footerDom ? (/*#__PURE__*/React.createElement("div", {
className: clsx(`${listPrefixCls}-footer`, classNames.footer),
style: styles.footer
}, footerDom)) : null;
const checkAllCheckbox = !showRemove && !pagination && checkBox;
let items;
if (showRemove) {
items = [/* Remove Current Page */
pagination ? {
key: 'removeCurrent',
label: removeCurrent,
onClick() {
const pageKeys = getEnabledItemKeys((listBodyRef.current?.items || []).map(entity => entity.item));
onItemRemove?.(pageKeys);
}
} : null, /* Remove All */
{
key: 'removeAll',
label: removeAll,
onClick() {
onItemRemove?.(getEnabledItemKeys(filteredItems));
}
}].filter(Boolean);
} else {
items = [{
key: 'selectAll',
label: checkStatus === 'all' ? deselectAll : selectAll,
onClick() {
const keys = getEnabledItemKeys(filteredItems);
onItemSelectAll?.(keys, keys.length !== checkedKeys.length);
}
}, pagination ? {
key: 'selectCurrent',
label: selectCurrent,
onClick() {
const pageItems = listBodyRef.current?.items || [];
onItemSelectAll?.(getEnabledItemKeys(pageItems.map(entity => entity.item)), true);
}
} : null, {
key: 'selectInvert',
label: selectInvert,
onClick() {
const availablePageItemKeys = getEnabledItemKeys((listBodyRef.current?.items || []).map(entity => entity.item));
const checkedKeySet = new Set(checkedKeys);
const newCheckedKeysSet = new Set(checkedKeySet);
availablePageItemKeys.forEach(key => {
if (checkedKeySet.has(key)) {
newCheckedKeysSet.delete(key);
} else {
newCheckedKeysSet.add(key);
}
});
onItemSelectAll?.(_toConsumableArray(newCheckedKeysSet), 'replace');
}
}];
}
const dropdown = /*#__PURE__*/React.createElement(Dropdown, {
className: `${listPrefixCls}-header-dropdown`,
menu: {
items
},
disabled: disabled
}, isValidIcon(selectionsIcon) ? selectionsIcon : /*#__PURE__*/React.createElement(DownOutlined, null));
return /*#__PURE__*/React.createElement("div", {
className: clsx(sectionPrefixCls, classNames.section, {
[`${sectionPrefixCls}-with-pagination`]: !!pagination,
[`${sectionPrefixCls}-with-footer`]: !!footerDom
}),
style: {
...style,
...styles.section
}
}, /*#__PURE__*/React.createElement("div", {
className: clsx(`${listPrefixCls}-header`, classNames.header),
style: styles.header
}, showSelectAll ? (/*#__PURE__*/React.createElement(React.Fragment, null, checkAllCheckbox, dropdown)) : null, /*#__PURE__*/React.createElement("span", {
className: `${listPrefixCls}-header-selected`
}, getSelectAllLabel(checkedActiveItems.length, filteredItems.length)), /*#__PURE__*/React.createElement("span", {
className: clsx(`${listPrefixCls}-header-title`, classNames.title),
style: styles.title
}, titleText)), renderListBody(), listFooter);
};
if (process.env.NODE_ENV !== 'production') {
TransferSection.displayName = 'TransferSection';
}
export default TransferSection;
+5
View File
@@ -0,0 +1,5 @@
import type { TransferProps } from '..';
import type { AnyObject } from '../../_util/type';
import type { TransferKey } from '../interface';
declare const useData: <RecordType extends AnyObject>(dataSource?: RecordType[], rowKey?: TransferProps<RecordType>["rowKey"], targetKeys?: TransferKey[]) => RecordType[][];
export default useData;
+33
View File
@@ -0,0 +1,33 @@
import * as React from 'react';
import { groupKeysMap } from '../../_util/transKeys';
const useData = (dataSource, rowKey, targetKeys) => {
const mergedDataSource = React.useMemo(() => (dataSource || []).map(record => {
if (rowKey) {
return {
...record,
key: rowKey(record)
};
}
return record;
}), [dataSource, rowKey]);
const [leftDataSource, rightDataSource] = React.useMemo(() => {
const leftData = [];
const rightData = Array.from({
length: targetKeys?.length ?? 0
});
const targetKeysMap = groupKeysMap(targetKeys || []);
mergedDataSource.forEach(record => {
// rightData should be ordered by targetKeys
// leftData should be ordered by dataSource
if (targetKeysMap.has(record.key)) {
const idx = targetKeysMap.get(record.key);
rightData[idx] = record;
} else {
leftData.push(record);
}
});
return [leftData, rightData];
}, [mergedDataSource, targetKeys]);
return [mergedDataSource, leftDataSource.filter(Boolean), rightDataSource.filter(Boolean)];
};
export default useData;
+10
View File
@@ -0,0 +1,10 @@
import type { TransferKey } from '../interface';
declare function useSelection<T extends {
key: TransferKey;
}>(leftDataSource: T[], rightDataSource: T[], selectedKeys?: TransferKey[]): [
sourceSelectedKeys: TransferKey[],
targetSelectedKeys: TransferKey[],
setSourceSelectedKeys: (srcKeys: TransferKey[]) => void,
setTargetSelectedKeys: (srcKeys: TransferKey[]) => void
];
export default useSelection;
+36
View File
@@ -0,0 +1,36 @@
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import * as React from 'react';
import { useControlledState, useEvent } from '@rc-component/util';
const EMPTY_KEYS = [];
function filterKeys(keys, dataKeys) {
const filteredKeys = keys.filter(key => dataKeys.has(key));
return keys.length === filteredKeys.length ? keys : filteredKeys;
}
function flattenKeys(keys) {
return Array.from(keys).join(';');
}
function useSelection(leftDataSource, rightDataSource, selectedKeys) {
// Prepare `dataSource` keys
const [leftKeys, rightKeys] = React.useMemo(() => [new Set(leftDataSource.map(src => src?.key)), new Set(rightDataSource.map(src => src?.key))], [leftDataSource, rightDataSource]);
// Selected Keys
const [mergedSelectedKeys, setMergedSelectedKeys] = useControlledState(EMPTY_KEYS, selectedKeys);
const sourceSelectedKeys = React.useMemo(() => filterKeys(mergedSelectedKeys, leftKeys), [mergedSelectedKeys, leftKeys]);
const targetSelectedKeys = React.useMemo(() => filterKeys(mergedSelectedKeys, rightKeys), [mergedSelectedKeys, rightKeys]);
// // Reset when data changed
React.useEffect(() => {
setMergedSelectedKeys([].concat(_toConsumableArray(filterKeys(mergedSelectedKeys, leftKeys)), _toConsumableArray(filterKeys(mergedSelectedKeys, rightKeys))));
}, [flattenKeys(leftKeys), flattenKeys(rightKeys)]);
// Update keys
const setSourceSelectedKeys = useEvent(nextSrcKeys => {
setMergedSelectedKeys([].concat(_toConsumableArray(nextSrcKeys), _toConsumableArray(targetSelectedKeys)));
});
const setTargetSelectedKeys = useEvent(nextTargetKeys => {
setMergedSelectedKeys([].concat(_toConsumableArray(sourceSelectedKeys), _toConsumableArray(nextTargetKeys)));
});
return [
// Keys
sourceSelectedKeys, targetSelectedKeys,
// Updater
setSourceSelectedKeys, setTargetSelectedKeys];
}
export default useSelection;
+131
View File
@@ -0,0 +1,131 @@
import type { CSSProperties } from 'react';
import React from 'react';
import type { SemanticClassNamesType, SemanticStylesType } from '../_util/hooks';
import type { InputStatus } from '../_util/statusUtils';
import type { PaginationType, TransferKey } from './interface';
import type { TransferCustomListBodyProps, TransferListProps } from './Section';
export type { TransferOperationProps } from './Actions';
export type { TransferSearchProps } from './search';
export type { TransferListProps } from './Section';
export type TransferSemanticName = keyof TransferSemanticClassNames & keyof TransferSemanticStyles;
export type TransferSemanticClassNames = {
root?: string;
section?: string;
header?: string;
title?: string;
body?: string;
list?: string;
item?: string;
itemIcon?: string;
itemContent?: string;
footer?: string;
actions?: string;
};
export type TransferSemanticStyles = {
root?: React.CSSProperties;
section?: React.CSSProperties;
header?: React.CSSProperties;
title?: React.CSSProperties;
body?: React.CSSProperties;
list?: React.CSSProperties;
item?: React.CSSProperties;
itemIcon?: React.CSSProperties;
itemContent?: React.CSSProperties;
footer?: React.CSSProperties;
actions?: React.CSSProperties;
};
export type TransferClassNamesType = SemanticClassNamesType<TransferProps, TransferSemanticClassNames>;
export type TransferStylesType = SemanticStylesType<TransferProps, TransferSemanticStyles>;
export type TransferDirection = 'left' | 'right';
export interface RenderResultObject {
label: React.ReactElement;
value: string;
}
export type RenderResult = React.ReactElement | RenderResultObject | string | null;
export interface TransferItem {
key?: TransferKey;
title?: string;
description?: string;
disabled?: boolean;
[name: string]: any;
}
export type KeyWise<T> = T & {
key: TransferKey;
};
export type KeyWiseTransferItem = KeyWise<TransferItem>;
type TransferRender<RecordType> = (item: RecordType) => RenderResult;
export interface ListStyle {
direction: TransferDirection;
}
export type SelectAllLabel = React.ReactNode | ((info: {
selectedCount: number;
totalCount: number;
}) => React.ReactNode);
export interface TransferLocale {
titles?: React.ReactNode[];
notFoundContent?: React.ReactNode | React.ReactNode[];
searchPlaceholder: string;
itemUnit: string;
itemsUnit: string;
remove?: string;
selectAll?: string;
deselectAll?: string;
selectCurrent?: string;
selectInvert?: string;
removeAll?: string;
removeCurrent?: string;
}
export interface TransferSearchOption {
placeholder?: string;
defaultValue?: string;
}
export interface TransferProps<RecordType = any> {
prefixCls?: string;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
/** @deprecated Please use `styles.section` instead. */
listStyle?: ((style: ListStyle) => CSSProperties) | CSSProperties;
/** @deprecated Please use `styles.actions` instead. */
operationStyle?: CSSProperties;
classNames?: TransferClassNamesType;
styles?: TransferStylesType;
disabled?: boolean;
dataSource?: RecordType[];
targetKeys?: TransferKey[];
selectedKeys?: TransferKey[];
render?: TransferRender<RecordType>;
onChange?: (targetKeys: TransferKey[], direction: TransferDirection, moveKeys: TransferKey[]) => void;
onSelectChange?: (sourceSelectedKeys: TransferKey[], targetSelectedKeys: TransferKey[]) => void;
titles?: React.ReactNode[];
/** @deprecated Please use `actions` instead. */
operations?: string[];
actions?: React.ReactNode[];
showSearch?: boolean | TransferSearchOption;
filterOption?: (inputValue: string, item: RecordType, direction: TransferDirection) => boolean;
locale?: Partial<TransferLocale>;
footer?: (props: TransferListProps<RecordType>, info?: {
direction: TransferDirection;
}) => React.ReactNode;
rowKey?: (record: RecordType) => TransferKey;
onSearch?: (direction: TransferDirection, value: string) => void;
onScroll?: (direction: TransferDirection, e: React.SyntheticEvent<HTMLUListElement>) => void;
children?: (props: TransferCustomListBodyProps<RecordType>) => React.ReactNode;
showSelectAll?: boolean;
selectAllLabels?: SelectAllLabel[];
oneWay?: boolean;
pagination?: PaginationType;
status?: InputStatus;
selectionsIcon?: React.ReactNode;
}
declare const Transfer: {
<RecordType extends TransferItem = TransferItem>(props: TransferProps<RecordType>): React.JSX.Element;
displayName: string;
List: {
<RecordType extends KeyWiseTransferItem>(props: TransferListProps<RecordType>): React.JSX.Element;
displayName: string;
};
Search: React.FC<import("./search").TransferSearchProps>;
Operation: React.FC<import("./Actions").TransferOperationProps>;
};
export default Transfer;
+332
View File
@@ -0,0 +1,332 @@
"use client";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import React, { useCallback, useContext } from 'react';
import { clsx } from 'clsx';
import { useMergeSemantic, useMultipleSelect } from '../_util/hooks';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import { groupDisabledKeysMap, groupKeysMap } from '../_util/transKeys';
import { devUseWarning } from '../_util/warning';
import { useComponentConfig } from '../config-provider/context';
import DefaultRenderEmpty from '../config-provider/defaultRenderEmpty';
import DisabledContext from '../config-provider/DisabledContext';
import { FormItemInputContext } from '../form/context';
import { useLocale } from '../locale';
import defaultLocale from '../locale/en_US';
import Actions from './Actions';
import useData from './hooks/useData';
import useSelection from './hooks/useSelection';
import Search from './search';
import Section from './Section';
import useStyle from './style';
const Transfer = props => {
const {
prefixCls: customizePrefixCls,
className,
rootClassName,
classNames,
styles,
style,
listStyle,
operationStyle,
operations,
actions,
dataSource,
targetKeys = [],
selectedKeys,
selectAllLabels = [],
locale = {},
titles,
disabled,
showSearch = false,
showSelectAll,
oneWay,
pagination,
status: customStatus,
selectionsIcon,
filterOption,
render,
footer,
children,
rowKey,
onScroll,
onChange,
onSearch,
onSelectChange
} = props;
const {
getPrefixCls,
renderEmpty,
direction: dir,
className: contextClassName,
style: contextStyle,
classNames: contextClassNames,
styles: contextStyles,
selectionsIcon: contextSelectionsIcon
} = useComponentConfig('transfer');
const contextDisabled = useContext(DisabledContext);
const mergedDisabled = disabled ?? contextDisabled;
// =========== Merged Props for Semantic ==========
const mergedProps = {
...props,
disabled: mergedDisabled
};
const prefixCls = getPrefixCls('transfer', customizePrefixCls);
const [hashId, cssVarCls] = useStyle(prefixCls);
const mergedActions = actions || operations || [];
const isRtl = dir === 'rtl';
// Fill record with `key`
const [mergedDataSource, leftDataSource, rightDataSource] = useData(dataSource, rowKey, targetKeys);
// Get direction selected keys
const [
// Keys
sourceSelectedKeys, targetSelectedKeys,
// Setters
setSourceSelectedKeys, setTargetSelectedKeys] = useSelection(leftDataSource, rightDataSource, selectedKeys);
const [leftMultipleSelect, updateLeftPrevSelectedIndex] = useMultipleSelect(item => item.key);
const [rightMultipleSelect, updateRightPrevSelectedIndex] = useMultipleSelect(item => item.key);
const setStateKeys = useCallback((direction, keys) => {
if (direction === 'left') {
const nextKeys = typeof keys === 'function' ? keys(sourceSelectedKeys || []) : keys;
setSourceSelectedKeys(nextKeys);
} else {
const nextKeys = typeof keys === 'function' ? keys(targetSelectedKeys || []) : keys;
setTargetSelectedKeys(nextKeys);
}
}, [sourceSelectedKeys, targetSelectedKeys]);
const setPrevSelectedIndex = (direction, value) => {
const isLeftDirection = direction === 'left';
const updatePrevSelectedIndex = isLeftDirection ? updateLeftPrevSelectedIndex : updateRightPrevSelectedIndex;
updatePrevSelectedIndex(value);
};
const handleSelectChange = useCallback((direction, holder) => {
if (direction === 'left') {
onSelectChange?.(holder, targetSelectedKeys);
} else {
onSelectChange?.(sourceSelectedKeys, holder);
}
}, [sourceSelectedKeys, targetSelectedKeys]);
const getTitles = transferLocale => titles ?? transferLocale.titles ?? [];
const handleLeftScroll = e => {
onScroll?.('left', e);
};
const handleRightScroll = e => {
onScroll?.('right', e);
};
const moveTo = direction => {
const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys;
const dataSourceDisabledKeysMap = groupDisabledKeysMap(mergedDataSource);
// filter the disabled options
const newMoveKeys = moveKeys.filter(key => !dataSourceDisabledKeysMap.has(key));
const newMoveKeysMap = groupKeysMap(newMoveKeys);
// move items to target box
const newTargetKeys = direction === 'right' ? newMoveKeys.concat(targetKeys) : targetKeys.filter(targetKey => !newMoveKeysMap.has(targetKey));
// empty checked keys
const oppositeDirection = direction === 'right' ? 'left' : 'right';
setStateKeys(oppositeDirection, []);
handleSelectChange(oppositeDirection, []);
onChange?.(newTargetKeys, direction, newMoveKeys);
};
const moveToLeft = () => {
moveTo('left');
setPrevSelectedIndex('left', null);
};
const moveToRight = () => {
moveTo('right');
setPrevSelectedIndex('right', null);
};
const onItemSelectAll = (direction, keys, checkAll) => {
setStateKeys(direction, prevKeys => {
let mergedCheckedKeys = [];
if (checkAll === 'replace') {
mergedCheckedKeys = keys;
} else if (checkAll) {
// Merge current keys with origin key
mergedCheckedKeys = Array.from(new Set([].concat(_toConsumableArray(prevKeys), _toConsumableArray(keys))));
} else {
const selectedKeysMap = groupKeysMap(keys);
// Remove current keys from origin keys
mergedCheckedKeys = prevKeys.filter(key => !selectedKeysMap.has(key));
}
handleSelectChange(direction, mergedCheckedKeys);
return mergedCheckedKeys;
});
setPrevSelectedIndex(direction, null);
};
const onLeftItemSelectAll = (keys, checkAll) => onItemSelectAll('left', keys, checkAll);
const onRightItemSelectAll = (keys, checkAll) => onItemSelectAll('right', keys, checkAll);
const leftFilter = e => onSearch?.('left', e.target.value);
const rightFilter = e => onSearch?.('right', e.target.value);
const handleLeftClear = () => onSearch?.('left', '');
const handleRightClear = () => onSearch?.('right', '');
const handleSingleSelect = (direction, holder, selectedKey, checked, currentSelectedIndex) => {
const isSelected = holder.has(selectedKey);
if (isSelected) {
holder.delete(selectedKey);
setPrevSelectedIndex(direction, null);
}
if (checked) {
holder.add(selectedKey);
setPrevSelectedIndex(direction, currentSelectedIndex);
}
};
const handleMultipleSelect = (direction, data, holder, currentSelectedIndex) => {
const isLeftDirection = direction === 'left';
const multipleSelect = isLeftDirection ? leftMultipleSelect : rightMultipleSelect;
multipleSelect(currentSelectedIndex, data, holder);
};
const onItemSelect = (direction, selectedKey, checked, multiple) => {
const isLeftDirection = direction === 'left';
const holder = isLeftDirection ? sourceSelectedKeys : targetSelectedKeys;
const holderSet = new Set(holder);
const data = (isLeftDirection ? leftDataSource : rightDataSource).filter(item => !item.disabled);
const currentSelectedIndex = data.findIndex(item => item.key === selectedKey);
// multiple select by hold down the shift key
if (multiple && holder.length > 0) {
handleMultipleSelect(direction, data, holderSet, currentSelectedIndex);
} else {
handleSingleSelect(direction, holderSet, selectedKey, checked, currentSelectedIndex);
}
const holderArr = Array.from(holderSet);
handleSelectChange(direction, holderArr);
if (!props.selectedKeys) {
setStateKeys(direction, holderArr);
}
};
const onLeftItemSelect = (selectedKey, checked, e) => {
onItemSelect('left', selectedKey, checked, e?.shiftKey);
};
const onRightItemSelect = (selectedKey, checked, e) => onItemSelect('right', selectedKey, checked, e?.shiftKey);
const onRightItemRemove = keys => {
setStateKeys('right', []);
onChange?.(targetKeys.filter(key => !keys.includes(key)), 'left', _toConsumableArray(keys));
};
const handleListStyle = direction => {
if (typeof listStyle === 'function') {
return listStyle({
direction
});
}
return listStyle || {};
};
const formItemContext = useContext(FormItemInputContext);
const {
hasFeedback,
status
} = formItemContext;
const getLocale = transferLocale => ({
...transferLocale,
notFoundContent: renderEmpty?.('Transfer') || /*#__PURE__*/React.createElement(DefaultRenderEmpty, {
componentName: "Transfer"
}),
...locale
});
const mergedStatus = getMergedStatus(status, customStatus);
const mergedPagination = !children && pagination;
const leftActive = rightDataSource.filter(d => targetSelectedKeys.includes(d.key) && !d.disabled).length > 0;
const rightActive = leftDataSource.filter(d => sourceSelectedKeys.includes(d.key) && !d.disabled).length > 0;
// ====================== Styles ======================
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [contextStyles, styles], {
props: mergedProps
});
const cls = clsx(prefixCls, {
[`${prefixCls}-disabled`]: mergedDisabled,
[`${prefixCls}-customize-list`]: !!children,
[`${prefixCls}-rtl`]: isRtl
}, getStatusClassNames(prefixCls, mergedStatus, hasFeedback), contextClassName, className, rootClassName, hashId, cssVarCls, mergedClassNames.root);
// ====================== Locale ======================
const [contextLocale] = useLocale('Transfer', defaultLocale.Transfer);
const listLocale = getLocale(contextLocale);
const [leftTitle, rightTitle] = getTitles(listLocale);
const mergedSelectionsIcon = selectionsIcon ?? contextSelectionsIcon;
// ===================== Warning ======================
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Transfer');
process.env.NODE_ENV !== "production" ? warning(!pagination || !children, 'usage', '`pagination` not support customize render list.') : void 0;
[['listStyle', 'styles.section'], ['operationStyle', 'styles.actions'], ['operations', 'actions']].forEach(([deprecatedName, newName]) => {
warning.deprecated(!(deprecatedName in props), deprecatedName, newName);
});
}
// ====================== Render ======================
return /*#__PURE__*/React.createElement("div", {
className: cls,
style: {
...contextStyle,
...mergedStyles.root,
...style
}
}, /*#__PURE__*/React.createElement(Section, {
prefixCls: prefixCls,
style: handleListStyle('left'),
classNames: mergedClassNames,
styles: mergedStyles,
titleText: leftTitle,
dataSource: leftDataSource,
filterOption: filterOption,
checkedKeys: sourceSelectedKeys,
handleFilter: leftFilter,
handleClear: handleLeftClear,
onItemSelect: onLeftItemSelect,
onItemSelectAll: onLeftItemSelectAll,
render: render,
showSearch: showSearch,
renderList: children,
footer: footer,
onScroll: handleLeftScroll,
disabled: mergedDisabled,
direction: isRtl ? 'right' : 'left',
showSelectAll: showSelectAll,
selectAllLabel: selectAllLabels[0],
pagination: mergedPagination,
selectionsIcon: mergedSelectionsIcon,
...listLocale
}), /*#__PURE__*/React.createElement(Actions, {
className: clsx(`${prefixCls}-actions`, mergedClassNames.actions),
rightActive: rightActive,
moveToRight: moveToRight,
leftActive: leftActive,
actions: mergedActions,
moveToLeft: moveToLeft,
style: {
...operationStyle,
...mergedStyles.actions
},
disabled: mergedDisabled,
direction: dir,
oneWay: oneWay
}), /*#__PURE__*/React.createElement(Section, {
prefixCls: prefixCls,
style: handleListStyle('right'),
classNames: mergedClassNames,
styles: mergedStyles,
titleText: rightTitle,
dataSource: rightDataSource,
filterOption: filterOption,
checkedKeys: targetSelectedKeys,
handleFilter: rightFilter,
handleClear: handleRightClear,
onItemSelect: onRightItemSelect,
onItemSelectAll: onRightItemSelectAll,
onItemRemove: onRightItemRemove,
render: render,
showSearch: showSearch,
renderList: children,
footer: footer,
onScroll: handleRightScroll,
disabled: mergedDisabled,
direction: isRtl ? 'left' : 'right',
showSelectAll: showSelectAll,
selectAllLabel: selectAllLabels[1],
showRemove: oneWay,
pagination: mergedPagination,
selectionsIcon: mergedSelectionsIcon,
...listLocale
}));
};
if (process.env.NODE_ENV !== 'production') {
Transfer.displayName = 'Transfer';
}
Transfer.List = Section;
Transfer.Search = Search;
Transfer.Operation = Actions;
export default Transfer;
+7
View File
@@ -0,0 +1,7 @@
export type TransferKey = React.Key;
export type PaginationType = boolean | {
pageSize?: number;
simple?: boolean;
showSizeChanger?: boolean;
showLessItems?: boolean;
};
+1
View File
@@ -0,0 +1 @@
export {};
+11
View File
@@ -0,0 +1,11 @@
import * as React from 'react';
export interface TransferSearchProps {
prefixCls?: string;
placeholder?: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleClear?: () => void;
value?: string;
disabled?: boolean;
}
declare const Search: React.FC<TransferSearchProps>;
export default Search;
+34
View File
@@ -0,0 +1,34 @@
"use client";
import * as React from 'react';
import SearchOutlined from "@ant-design/icons/es/icons/SearchOutlined";
import Input from '../input/Input';
const Search = props => {
const {
placeholder = '',
value,
prefixCls,
disabled,
onChange,
handleClear
} = props;
const handleChange = React.useCallback(e => {
onChange?.(e);
if (e.target.value === '') {
handleClear?.();
}
}, [onChange]);
return /*#__PURE__*/React.createElement(Input, {
placeholder: placeholder,
className: prefixCls,
value: value,
onChange: handleChange,
disabled: disabled,
allowClear: true,
prefix: /*#__PURE__*/React.createElement(SearchOutlined, null)
});
};
if (process.env.NODE_ENV !== 'production') {
Search.displayName = 'Search';
}
export default Search;
+36
View File
@@ -0,0 +1,36 @@
import type { GetDefaultToken } from '../../theme/internal';
export interface ComponentToken {
/**
* @desc 列表宽度
* @descEN Width of list
*/
listWidth: number | string;
/**
* @desc 大号列表宽度
* @descEN Width of large list
*/
listWidthLG: number | string;
/**
* @desc 列表高度
* @descEN Height of list
*/
listHeight: number | string;
/**
* @desc 列表项高度
* @descEN Height of list item
*/
itemHeight: number | string;
/**
* @desc 列表项纵向内边距
* @descEN Vertical padding of list item
*/
itemPaddingBlock: number | string;
/**
* @desc 顶部高度
* @descEN Height of header
*/
headerHeight: number | string;
}
export declare const prepareComponentToken: GetDefaultToken<'Transfer'>;
declare const _default: (prefixCls: string, rootCls?: string) => readonly [string, string];
export default _default;
+315
View File
@@ -0,0 +1,315 @@
import { unit } from '@ant-design/cssinjs';
import { operationUnit, resetComponent, resetIcon, textEllipsis } from '../../style';
import { genStyleHooks, mergeToken } from '../../theme/internal';
const genTransferCustomizeStyle = token => {
const {
antCls,
componentCls,
listHeight,
controlHeightLG
} = token;
const tableCls = `${antCls}-table`;
const inputCls = `${antCls}-input`;
return {
[`${componentCls}-customize-list`]: {
[`${componentCls}-section`]: {
flex: '1 1 50%',
width: 'auto',
height: 'auto',
minHeight: listHeight,
minWidth: 0
},
// =================== Hook Components ===================
[`${tableCls}-wrapper`]: {
[`${tableCls}-small`]: {
border: 0,
borderRadius: 0,
[`${tableCls}-selection-column`]: {
width: controlHeightLG,
minWidth: controlHeightLG
}
},
[`${tableCls}-pagination${tableCls}-pagination`]: {
margin: 0,
padding: token.paddingXS
}
},
[`${inputCls}[disabled]`]: {
backgroundColor: 'transparent'
}
}
};
};
const genTransferStatusColor = (token, color) => {
const {
componentCls,
colorBorder
} = token;
return {
[`${componentCls}-section`]: {
borderColor: color,
[`${componentCls}-list-search:not([disabled])`]: {
borderColor: colorBorder
}
}
};
};
const genTransferStatusStyle = token => {
const {
componentCls
} = token;
return {
[`${componentCls}-status-error`]: {
...genTransferStatusColor(token, token.colorError)
},
[`${componentCls}-status-warning`]: {
...genTransferStatusColor(token, token.colorWarning)
}
};
};
const genTransferListStyle = token => {
const {
componentCls,
colorBorder,
colorSplit,
lineWidth,
itemHeight,
headerHeight,
transferHeaderVerticalPadding,
itemPaddingBlock,
controlItemBgActive,
colorTextDisabled,
colorTextSecondary,
listHeight,
listWidth,
listWidthLG,
fontSizeIcon,
marginXS,
paddingSM,
lineType,
antCls,
iconCls,
motionDurationSlow,
controlItemBgHover,
borderRadiusLG,
colorBgContainer,
colorText,
controlItemBgActiveHover
} = token;
const contentBorderRadius = unit(token.calc(borderRadiusLG).sub(lineWidth).equal());
return {
display: 'flex',
flexDirection: 'column',
width: listWidth,
height: listHeight,
border: `${unit(lineWidth)} ${lineType} ${colorBorder}`,
borderRadius: token.borderRadiusLG,
'&-with-pagination': {
width: listWidthLG,
height: 'auto'
},
[`${componentCls}-list`]: {
'&-search': {
[`${iconCls}-search`]: {
color: colorTextDisabled
}
},
'&-header': {
display: 'flex',
flex: 'none',
alignItems: 'center',
height: headerHeight,
// border-top is on the transfer dom. We should minus 1px for this
padding: `${unit(token.calc(transferHeaderVerticalPadding).sub(lineWidth).equal())} ${unit(paddingSM)} ${unit(transferHeaderVerticalPadding)}`,
color: colorText,
background: colorBgContainer,
borderBottom: `${unit(lineWidth)} ${lineType} ${colorSplit}`,
borderRadius: `${unit(borderRadiusLG)} ${unit(borderRadiusLG)} 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: '0 1 auto',
textAlign: 'end',
marginInlineStart: 'auto'
},
'&-dropdown': {
...resetIcon(),
fontSize: fontSizeIcon,
transform: 'translateY(10%)',
cursor: 'pointer',
'&[disabled]': {
cursor: 'not-allowed'
}
}
},
'&-body': {
display: 'flex',
flex: 'auto',
flexDirection: 'column',
fontSize: token.fontSize,
// https://blog.csdn.net/qq449245884/article/details/107373672/
minHeight: 0,
'&-search-wrapper': {
position: 'relative',
flex: 'none',
padding: paddingSM
}
},
'&-content': {
flex: 'auto',
margin: 0,
padding: 0,
overflow: 'auto',
listStyle: 'none',
borderRadius: `0 0 ${contentBorderRadius} ${contentBorderRadius}`,
'&-item': {
display: 'flex',
alignItems: 'center',
minHeight: itemHeight,
padding: `${unit(itemPaddingBlock)} ${unit(paddingSM)}`,
transition: `all ${motionDurationSlow}`,
'> *:not(:last-child)': {
marginInlineEnd: marginXS
},
'> *': {
flex: 'none'
},
'&-text': {
...textEllipsis,
flex: 'auto'
},
'&-remove': {
...operationUnit(token),
color: colorBorder,
'&:hover, &:focus': {
color: colorTextSecondary
},
'&:disabled': {
color: colorTextDisabled,
cursor: 'not-allowed'
}
},
[`&:not(${componentCls}-list-content-item-disabled)`]: {
'&:hover': {
backgroundColor: 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,
textAlign: 'end',
borderTop: `${unit(lineWidth)} ${lineType} ${colorSplit}`,
[`${antCls}-pagination-options`]: {
paddingInlineEnd: token.paddingXS
}
},
'&-body-not-found': {
flex: 'none',
width: '100%',
margin: 'auto 0',
color: colorTextDisabled,
textAlign: 'center'
},
'&-footer': {
borderTop: `${unit(lineWidth)} ${lineType} ${colorSplit}`
},
// fix: https://github.com/ant-design/ant-design/issues/44489
'&-checkbox': {
lineHeight: 1
}
}
};
};
const genTransferStyle = token => {
const {
antCls,
iconCls,
componentCls,
marginXS,
marginXXS,
fontSizeIcon,
colorBgContainerDisabled
} = token;
return {
[componentCls]: {
...resetComponent(token),
position: 'relative',
display: 'flex',
alignItems: 'stretch',
[`${componentCls}-disabled`]: {
[`${componentCls}-section`]: {
background: colorBgContainerDisabled
}
},
[`${componentCls}-section`]: genTransferListStyle(token),
[`${componentCls}-actions`]: {
display: 'flex',
flex: 'none',
flexDirection: 'column',
alignSelf: 'center',
margin: `0 ${unit(marginXS)}`,
verticalAlign: 'middle',
gap: marginXXS,
[`${antCls}-btn ${iconCls}`]: {
fontSize: fontSizeIcon
}
}
}
};
};
const genTransferRTLStyle = token => {
const {
componentCls
} = token;
return {
[`${componentCls}-rtl`]: {
direction: 'rtl'
}
};
};
export const prepareComponentToken = token => {
const {
fontSize,
lineHeight,
controlHeight,
controlHeightLG,
lineWidth
} = token;
const fontHeight = Math.round(fontSize * lineHeight);
return {
listWidth: 180,
listHeight: 200,
listWidthLG: 250,
headerHeight: controlHeightLG,
itemHeight: controlHeight,
itemPaddingBlock: (controlHeight - fontHeight) / 2,
transferHeaderVerticalPadding: Math.ceil((controlHeightLG - lineWidth - fontHeight) / 2)
};
};
// ============================== Export ==============================
export default genStyleHooks('Transfer', token => {
const transferToken = mergeToken(token);
return [genTransferStyle(transferToken), genTransferCustomizeStyle(transferToken), genTransferStatusStyle(transferToken), genTransferRTLStyle(transferToken)];
}, prepareComponentToken);