--- localeCode: en-US order: 33 category: Input title: Transfer icon: doc-transfer width: 60% dir: column brief: A more intuitive and efficient multiple-selection selector, which can reveal more information about options, and supports search functions. The disadvantage is that it takes up more space --- ## Demos ### How to import ```jsx import import { Transfer } from '@douyinfe/semi-ui'; ``` ### Basic Usage dataSource should have value、label、key. ```jsx live=true dir="column" import React from 'react'; import { Transfer } from '@douyinfe/semi-ui'; () => { const data = Array.from({ length: 100 }, (v, i) => { return { label: `Item ${i}`, value: i, disabled: false, key: i, }; }); return ( console.log(values, items)} /> ); }; ``` ### Grouped Set type to `groupList` For grouped dataSource, the first-level child elements must have title and children attributes, structure reference Does not support multi-level nesting ```jsx live=true dir="column" import React from 'react'; import { Transfer } from '@douyinfe/semi-ui'; () => { const dataWithGroup = [ { title: 'GroupA', children: [ { label: 'A-1', value: 1, disabled: false, key: 1 }, { label: 'A-2', value: 2, disabled: false, key: 2 }, { label: 'A-3', value: 3, disabled: false, key: 3 }, ], }, { title: 'GroupB', children: [ { label: 'B-1', value: 4, disabled: false, key: 4 }, { label: 'B-2', value: 5, disabled: false, key: 5 }, { label: 'B-3(disabled)', value: 6, disabled: true, key: 6 }, ], }, { title: 'GroupC', children: [ { label: 'C-1', value: 7, disabled: false, key: 7 }, { label: 'C-2', value: 8, disabled: false, key: 8 }, { label: 'C-3', value: 9, disabled: false, key: 9 }, { label: 'C-4', value: 10, disabled: false, key: 10 }, { label: 'C-5', value: 11, disabled: false, key: 11 }, { label: 'C-6', value: 12, disabled: false, key: 12 }, { label: 'C-7', value: 13, disabled: false, key: 13 }, ], }, ]; return ( console.log(values, items)} /> ); }; ``` ### Custom filtering logic, custom option data rendering Use `filter` to customize the search logic. When it returns true, it means that the current item meets the filter rules and keeps the display of the current item in the list. If it returns false, it means it does not match, and the current item will be hidden. Using `renderSourceItem`, you can customize the rendering structure of each source data on the left Using `renderSelectedItem` you can customize the rendering structure of each selected item on the right ```jsx live=true dir="column" import React from 'react'; import { Transfer, Avatar, Checkbox } from '@douyinfe/semi-ui'; import { IconClose } from '@douyinfe/semi-icons'; () => { const renderSourceItem = item => { return (
{ item.onChange(); }} key={item.label} checked={item.checked} style={{ height: 52 }} > {item.abbr}
{item.label}
{item.value}
); }; const renderSelectedItem = item => { return (
{item.abbr}
{item.label}
{item.value}
); }; const customFilter = (sugInput, item) => { return item.value.includes(sugInput) || item.label.includes(sugInput); }; const data = [ { label: 'Xiakeman', value: 'xiakeman@example.com', abbr: 'Xia', color: 'amber', area: 'US', key: 1 }, { label: 'Shenyue', value: 'shenyue@example.com', abbr: 'Shen', color: 'indigo', area: 'UK', key: 2 }, { label: 'Wenjiamao', value: 'wenjiamao@example.com', abbr: 'Wen', color: 'cyan', area: 'HK', key: 3 }, { label: 'Quchenyi', value: 'quchenyi@example.com', abbr: 'Qu', color: 'blue', area: 'India', key: 4 }, { label: 'Quchener', value: 'quchener@example.com', abbr: 'Qu', color: 'blue', area: 'India', key: 5 }, { label: 'Quchensan', value: 'quchensan@example.com', abbr: 'Qu', color: 'blue', area: 'India', key: 6 }, ]; return ( console.log(values, items)} /> ); }; ``` ```css .components-transfer-demo-selected-item { .semi-icon-close { visibility: hidden; color: var(--semi-color-tertiary); } &:hover { .semi-icon-close { visibility: visible; } } } .components-transfer-demo-selected-item, .components-transfer-demo-source-item { height: 52px; box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; &:hover { background-color: var(--semi-color-fill-0); } .info { margin-left: 8px; flex-grow: 1; } .name { font-size: 14px; line-height: 20px; } .email { font-size: 12px; line-height: 16px; color: var(--semi-color-text-2); } } ``` ### Disabled ```jsx live=true dir="column" import React from 'react'; import { Transfer } from '@douyinfe/semi-ui'; () => { const data = Array.from({ length: 20 }, (v, i) => { return { label: `Item ${i}`, value: i, disabled: false, key: i, }; }); return ( console.log(values, items)} /> ); }; ``` ### Drag and drop sort Set `draggable` to true to enable the drag sort function. Support after v1.11.0 ```jsx live=true dir="column" import React from 'react'; import { Transfer } from '@douyinfe/semi-ui'; () => { const data = Array.from({ length: 30 }, (v, i) => { return { label: `Item ${i}`, value: i, disabled: false, key: i, }; }); return ( console.log(values, items)} /> ); }; ``` ### Drag and drop + custom selected rendering Set `draggable` to true to enable the drag and drop sorting function; use `renderSelectedItem` to customize the rendering of the selected items on the right; You can define the trigger as any ReactNode you want and add styles. Drag the trigger and use `sortableHandle` to wrap it (sortableHandle is provided after v 1.22.0) ```jsx live=true dir="column" import React from 'react'; import { Transfer, Checkbox, Avatar } from '@douyinfe/semi-ui'; import { IconHandle, IconClose } from '@douyinfe/semi-icons'; () => { const renderSourceItem = item => { return (
{ item.onChange(); }} key={item.label} checked={item.checked} style={{ height: 52 }} > {item.abbr}
{item.label}
{item.value}
); }; const renderSelectedItem = item => { const { sortableHandle } = item; const DragHandle = sortableHandle(() => ); return (
{item.abbr}
{item.label}
{item.value}
); }; const customFilter = (sugInput, item) => { return item.value.includes(sugInput) || item.label.includes(sugInput); }; const data = [ { label: 'Xiakeman', value: 'xiakeman@example.com', abbr: 'Xia', color: 'amber', area: 'US', key: 1 }, { label: 'Shenyue', value: 'shenyue@example.com', abbr: 'Shen', color: 'indigo', area: 'UK', key: 2 }, { label: 'Wenjiamao', value: 'wenjiamao@example.com', abbr: 'Wen', color: 'cyan', area: 'HK', key: 3 }, { label: 'Quchenyi', value: 'quchenyi@example.com', abbr: 'Qu', color: 'blue', area: 'India', key: 4 }, { label: 'Quchener', value: 'quchener@example.com', abbr: 'Er', color: 'blue', area: 'India', key: 5 }, { label: 'Quchensan', value: 'quchensan@example.com', abbr: 'San', color: 'blue', area: 'India', key: 6 }, ]; return ( console.log(values, items)} /> ); }; ``` ### Fully custom rendering Semi provides `renderSourcePanel` and `renderSelectedPanel` input parameters, allowing you to completely customize the rendering structure of the left and right panels With this function, you can directly reuse the logic capabilities inside the Transfer to implement the `Transfer` component with a highly customized style structure `renderSourcePanel: (sourcePanelProps: SourcePanelProps) => ReactNode` `SourcePanelProps` contains the following parameters, from which you can get data to render your Panel structure ```ts interface SourcePanelProps { value: Array; // key of all selected items loading: boolean; // Whether loading noMatch: boolean; // Whether there is no matching item that matches the current search value filterData: Array // items that match the current search value sourceData: Array; // All items allChecked: boolean; // Whether to select all showNumber: number; // Filter the number of results inputValue: string; // the value of the input search box onSearch: (searchString: string) => any; // The function that should be called when the search box changes, the input parameter is the search value onAllClick: () => void; // The function that should be called when all the buttons on the left are clicked onSelectOrRemove: (item: Item) => any; //The function that should be called when selecting or deleting a single option, the input should be the current operation item onSelect: (value:Array)=>void; // controlled batch selection key selectedItem: Map, // collection of all selected items } ``` `renderSelectedPanel: (selectedPanelProps: SelectedPanelProps) => ReactNode` `SelectedPanelProps` contains the following parameters ```ts interface SelectedPanelProps { length: number; // number of selected options onClear: () => void; // The callback function that should be called when clicking to clear onRemove: (item: Item) => void; // The function that should be called when deleting a single option onSortEnd: (( oldIndex, newIndex)) => void; // The function that should be called when reordering the results selectedData: Array; // All selected items collection } ``` ```jsx live=true dir="column" import React from 'react'; import { Transfer, Input, Spin, Button } from '@douyinfe/semi-ui'; import { IconSearch } from '@douyinfe/semi-icons'; class CustomRenderDemo extends React.Component { constructor(props) { super(props); this.state = { dataSource: Array.from({ length: 100 }, (v, i) => ({ label: `Hdl Store ${i}`, value: i, disabled: false, key: i, })), }; this.renderSourcePanel = this.renderSourcePanel.bind(this); this.renderSelectedPanel = this.renderSelectedPanel.bind(this); this.renderItem = this.renderItem.bind(this); } renderItem(type, item, onItemAction, selectedItems) { let buttonText = 'delete'; if (type === 'source') { let checked = selectedItems.has(item.key); buttonText = checked ? 'delete' : 'add'; } return (

{item.label}

); } renderSourcePanel(props) { const { loading, noMatch, filterData, selectedItems, allChecked, onAllClick, inputValue, onSearch, onSelectOrRemove, } = props; let content; switch (true) { case loading: content = ; break; case noMatch: content =
{inputValue ? 'No search results' : 'No content yet'}
; break; case !noMatch: content = filterData.map(item => this.renderItem('source', item, onSelectOrRemove, selectedItems)); break; default: content = null; break; } return (
Store list
} onChange={onSearch} showClear />
Store to be selected: {filterData.length}
{content}
); } renderSelectedPanel(props) { const { selectedData, onClear, clearText, onRemove } = props; let mainContent = selectedData.map(item => this.renderItem('selected', item, onRemove)); if (!selectedData.length) { mainContent =
No data, please filter from the left
; } return (
Selected: {selectedData.length}
{mainContent}
); } render() { const { dataSource } = this.state; return ( console.log(values)} className="component-transfer-demo-custom-panel" renderSourcePanel={this.renderSourcePanel} renderSelectedPanel={this.renderSelectedPanel} dataSource={dataSource} /> ); } } ``` ```scss .component-transfer-demo-custom-panel { .sp-font { color: rgba(var(--semi-grey-9), 1); font-size: 12px; font-weight: 500; line-height: 20px; } .empty { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } .panel-item { flex-shrink: 0; height: 56px; border-radius: 4px; padding: 8px 12px; flex-wrap: wrap; background-color: rgba(22, 24, 35, .03); &-main { flex-grow: 1; } p { margin: 0 12px; flex-basis: 100%; } .panel-item-remove { cursor: pointer; color: var(--semi-color-primary); } } .panel-header { padding: 10px 12px; border: 1px solid rgba(22, 24, 35, .16); border-radius: 4px 4px 0 0; height: 38px; box-sizing: border-box; background-color: var(--semi-color-tertiary-light-default); display: flex; align-items: center; justify-content: space-between; .clear { cursor: pointer; color: var(--semi-color-primary); } } .source-panel { display: flex; flex-direction: column; width: 482px; height: 353px; .panel-main { border: 1px solid var(--semi-color-border); border-top: none; .panel-list { display: flex; flex-wrap: wrap; row-gap: 8px; column-gap: 8px; overflow-y: auto; height: 214px; margin-left: 12px; margin-right: 12px; padding-bottom: 8px; } } .panel-controls { margin: 10px 12px; font-size: 12px; line-height: 20px; .semi-button { margin-left: 8px; font-size: 12px; } } .panel-item { width: 176px; } margin-right: 16px; } .selected-panel { width: 200px; height: 353px; .panel-main { display: flex; flex-direction: column; overflow-y: auto; padding: 12px; border: 1px solid var(--semi-color-border); border-top: none; height: 323px; box-sizing: border-box; row-gap: 8px; } } } ``` ### Fully custom rendering, drag and drop sorting In a completely custom rendering scene, since the rendering of the drag area has also been completely taken over by you, you do not need to declare draggable. But you need to implement the drag and drop logic yourself, we recommend using `react-sortable-hoc` directly To support drag sorting, you need to call onSortEnd with oldIndex and newIndex as the input parameters after the drag sorting is over ```jsx live=true dir="column" import React from 'react'; import { SortableContainer, SortableElement, sortableHandle } from 'react-sortable-hoc'; import { Transfer, Button, Spin, Input } from '@douyinfe/semi-ui'; import { IconHandle, IconSearch } from '@douyinfe/semi-icons'; class CustomRenderDragDemo extends React.Component { constructor(props) { super(props); this.state = { dataSource: Array.from({ length: 100 }, (v, i) => ({ label: `Hdl Store ${i}`, value: i, disabled: false, key: i, })), }; this.renderSourcePanel = this.renderSourcePanel.bind(this); this.renderSelectedPanel = this.renderSelectedPanel.bind(this); this.renderItem = this.renderItem.bind(this); } renderItem(type, item, onItemAction, selectedItems) { let buttonText = 'delete'; let newItem = item; if (type === 'source') { let checked = selectedItems.has(item.key); buttonText = checked ? 'delete' : 'add'; } else { // delete newItem._optionKey; newItem = { ...item, key: item._optionKey }; delete newItem._optionKey; } const DragHandle = sortableHandle(() => ); return (
{type === 'source' ? null : }

{item.label}

); } renderSourcePanel(props) { const { loading, noMatch, filterData, selectedItems, allChecked, onAllClick, inputValue, onSearch, onSelectOrRemove, } = props; let content; switch (true) { case loading: content = ; break; case noMatch: content =
{inputValue ? 'No search results' : 'No content yet'}
; break; case !noMatch: content = filterData.map(item => this.renderItem('source', item, onSelectOrRemove, selectedItems)); break; default: content = null; break; } return (
Store list
} onChange={onSearch} showClear />
Store to be selected: {filterData.length}
{content}
); } renderSelectedPanel(props) { const { selectedData, onClear, clearText, onRemove, onSortEnd } = props; let mainContent = null; if (!selectedData.length) { mainContent =
No data, please filter from the left
; } const SortableItem = SortableElement(item => this.renderItem('selected', item, onRemove)); const SortableList = SortableContainer( ({ items }) => { return (
{items.map((item, index) => ( // sortableElement will take over the property 'key', so use another '_optionKey' to pass // otherwise you can't get `key` property in this.renderItem ))}
); }, { distance: 10 } ); mainContent = ; return (
Selected: {selectedData.length}
{mainContent}
); } render() { const { dataSource } = this.state; return ( console.log(values)} className="component-transfer-demo-custom-panel" renderSourcePanel={this.renderSourcePanel} renderSelectedPanel={this.renderSelectedPanel} dataSource={dataSource} /> ); } } ``` ### Tree Transfer The input type is `treeList`, and the [`Tree`](/en-US/navigation/tree) component is used as a custom rendering list. **v1.20.0 available** The properties of the default tree can be overridden by treeProps([TreeProps](/en-US/navigation/tree)). The default properties of the tree on the left are ```ts interface Default TreeProps { multiple:true, disableStrictly:true, leafOnly:true, filterTreeNode:true, searchRender:flase, } ``` ```jsx live=true dir="column" import React, { useState } from 'react'; import { Transfer } from '@douyinfe/semi-ui'; () => { const treeData = [ { label: 'Asia', value: 'Asia', key: '0', children: [ { label: 'China', value: 'China', key: '0-0', children: [ { label: 'Beijing', value: 'Beijing', key: '0-0-0', }, { label: 'Shanghai', value: 'Shanghai', key: '0-0-1', }, { label: 'Chengdu', value: 'Chengdu', key: '0-0-2', }, ], }, { label: 'Japan', value: 'Japan', key: '0-1', children: [ { label: 'Osaka', value: 'Osaka', key: '0-1-0', }, ], }, ], }, { label: 'North America', value: 'North America', key: '1', children: [ { label: 'United States', value: 'United States', key: '1-0', }, { label: 'Canada', value: 'Canada', key: '1-1', }, { label: 'Mexico', value: 'Mexico', disabled: true, key: '1-2', }, { label: 'Cuba', value: 'Cuba', key: '1-3', }, ], }, ]; const [v, $v] = useState(['Shanghai']); return (
); }; ``` ## Accessibility ### ARIA - Add `role` `search` to the search box - Add `role` `list` to the selected list on the right, add `role` `listitem` to the selected item ## API Reference ### Trasnfer Props | props | description | data type | default | version | | --- | --- | --- | --- | --- | | className | Style class name | string | | | | dataSource | Data Source | Array\|Array\|Array | [] | | | defaultValue | Default selected value | Array | | | | disabled | Whether to disable | boolean | false | | | draggable | Whether to enable drag sorting | boolean | false | | | emptyContent | Custom empty state prompt text, search is the text displayed when there are no search results, left is the text when there is no source data on the left, and right is the prompt text when no data is checked | {left: ReactNode; right: ReactNode; search: ReactNode;} | | | | filter | Custom filter logic, when false, the search box is not displayed | boolean \| (input:string, item: Item) => boolean | true | | | inputProps | Can be used to customize the search box Input, the configurable properties refer to the Input component, the value and onChange parameters will be used inside Transfer, users should not use them. If you want to search through external data, you can call the search method of Transfer | [InputProps](/en-US/input/input#Input) | | | | loading | Whether the left option is being loaded | boolean |-| | | onChange | The callback that is triggered when the selected value changes, and the callback is also triggered after the drag sort changes | (values: Array, items: Array) => void | | | | onDeselect | Callback when unchecking | (item: Item) => void | | | | onSearch | Called when the input content of the search box changes | (inputValue: string) => void | | | | onSelect | Callback when checked | (item: Item) => void | | | | renderSelectedItem | Customize the rendering of a single selected item on the right | (item: {onRemove, sortableHandle} & Item) => ReactNode | | | | renderSelectedPanel | Customize the rendering of the selected panel on the right | (selectedPanelProps) => ReactNode | | 1.11.0 | | renderSourceItem | Customize the rendering of a single candidate item on the left | (item: {onChange, checked} & Item) => ReactNode | | | | renderSourcePanel | Customize the rendering of the left candidate panel | (sourcePanelProps) => ReactNode | | 1.11.0 | | showPath | When the type is `treeList`, control whether the selected item on the right shows the selection path | boolean | false | 1.20.0 | | style | Inline style | CSSProperties | | | | treeProps | When the type is `treeList`, it can be passed as TreeProps to the Tree component on the left | [TreeProps](/en-US/navigation/tree#Tree) | | 1.20.0 | | type | Transfer type, optional `list`, `groupList`, `treeList` | string |'list' | 1.20.0 | | value | The selected value, when the item is passed in, it will be used as a controlled component | Array | | | ### Item Interface | props | description | data type | default | version | | --- | --- | --- | --- | --- | | className | Style class name | string | | | disabled | Whether to disable | boolean | false | | key | Required, unique identification of each option, no repetition is allowed | string \| number | | | label | Options display content | ReactNode | | | style | Inline style | CSSProperties | | | value | The value represented by the option | string \| number | | ### GroupItem Interface GroupItem inherits all the properties of Item | props | description | data type | default | version | | -------- | --------------------- | --------------------------------- | ------- | ------- | | children | Elements of the group | array | | | | title | Group Name | string | | | ### TreeItem Interface TreeItem inherits all the properties of Item | props | description | data type | default | | -------- | -------------- | ---------------- | ------- | | children | Children Items | array | | ### Method - search(value: string): You can call this method through ref to search, and the search value will be set to Input. ## Design Tokens ## Related materials ```material 52 ```