| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787 |
- import React, { useState, useRef } from 'react';
- import { Transfer, Button } from '../../index';
- import Table from '../../table';
- import Avatar from '../../avatar';
- import Checkbox from '../../checkbox';
- import Icon from '../../icons';
- import Tree from '../../tree';
- import Input from '../../input';
- import { omit, values } from 'lodash';
- import './transfer.scss';
- import { SortableContainer, SortableElement, sortableHandle } from 'react-sortable-hoc';
- import { IconClose, IconSearch, IconHandle } from '@douyinfe/semi-icons';
- export default {
- title: 'Transfer'
- }
- const commonProps = {
- onSelect: (...args) => {
- console.log('onSelect');
- console.log(...args);
- },
- onChange: (...args) => {
- console.log('onChange');
- console.log(...args);
- },
- onDeselect: (...args) => {
- console.log('onDeselect');
- console.log(...args);
- },
- onSearch: (...args) => {
- console.log('onSearch');
- console.log(...args);
- },
- };
- const data = Array.from({ length: 100 }, (v, i) => {
- return {
- label: `选项名称${i}`,
- value: i,
- disabled: false,
- key: i,
- };
- });
- 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',
- disabled: true,
- },
- {
- label: 'ShanghaionChangeonChangeonChange',
- value: 'Shanghai',
- key: '0-0-1',
- },
- {
- label: 'Chengdu',
- value: 'Chengdu',
- key: '0-0-2',
- },
- {
- label: 'Chongqing',
- value: 'Chongqing',
- key: '0-0-3',
- },
- ],
- },
- {
- 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',
- },
- ],
- },
- ];
- const dataWithGroup = [
- {
- title: '类别A',
- children: [
- { label: '选项名称1', value: 1, disabled: false, key: 1 },
- { label: '选项名称2', value: 2, disabled: false, key: 2 },
- { label: '选项名称3', value: 3, disabled: false, key: 3 },
- ],
- },
- {
- title: '类别B',
- children: [
- { label: '选项名称1', value: 4, disabled: true, key: 4 },
- { label: '选项名称2', value: 5, disabled: false, key: 5 },
- { label: '选项名称3', value: 6, disabled: false, key: 6 },
- ],
- },
- {
- title: '类别C',
- children: [
- { label: '选项名称1', value: 7, disabled: false, key: 7 },
- { label: '选项名称2', value: 8, disabled: false, key: 8 },
- { label: '选项名称3', value: 9, disabled: false, key: 9 },
- { label: '选项名称3', value: 10, disabled: false, key: 10 },
- { label: '选项名称3', value: 11, disabled: false, key: 11 },
- { label: '选项名称3', value: 12, disabled: false, key: 12 },
- { label: '选项名称3', value: 13, disabled: false, key: 13 },
- ],
- },
- ];
- const DefaultTransfer = () => {
- const [dataSource, setDataSource] = useState([]);
- const [treeDataSource, setTreeDataSource] = useState([]);
- return (
- <div style={{ margin: 10, padding: 10, width: 600 }}>
- <Button onClick={() => setDataSource(data)}>更改列表数据源</Button>
- <Transfer {...commonProps} dataSource={dataSource} defaultValue={[]} />
- <Button onClick={() => setTreeDataSource(treeData)}>更改树数据源</Button>
- <Transfer type="treeList" dataSource={treeDataSource}></Transfer>
- </div>
- );
- };
- export const _Transfer = DefaultTransfer;
- export const TransferDraggable = () => (
- <div style={{ margin: 10, padding: 10, width: 600 }}>
- <Transfer {...commonProps} dataSource={data} defaultValue={[2, 4]} draggable />
- </div>
- );
- TransferDraggable.story = {
- name: 'Transfer draggable',
- };
- export const TransferDraggableAndDisabled = () => {
- const data = Array.from({ length: 30 }, (v, i) => {
- return {
- label: `选项名称 ${i}`,
- value: i,
- key: i,
- disabled: true,
- };
- });
- return (
- <>
- <div>Transfer设置draggable, 并且左侧面板中的选项disabled </div>
- <div>符合预期的行为: 右侧面板hover不会出现删除按钮,因此不可以点击删除,但是可以拖拽 </div>
- <Transfer
- style={{ width: 568, height: 416 }}
- dataSource={data}
- defaultValue={[2, 4]}
- draggable
- onChange={(values, items) => console.log(values, items)}
- />
- </>
- );
- };
- TransferDraggableAndDisabled.story = {
- name: 'transfer draggable and disabled',
- }
- const ControlledTransfer = () => {
- const [value, setValue] = useState([2, 3]);
- const handleChange = value => {
- setValue(value);
- };
- return (
- <div style={{ margin: 10, padding: 10, width: 600 }}>
- <Transfer {...commonProps} dataSource={data} value={value} onChange={handleChange} />
- </div>
- );
- };
- export const ControlledTransferDemo = () => <ControlledTransfer />;
- ControlledTransferDemo.story = {
- name: '受控Transfer',
- };
- export const Loading = () => <Transfer loading />;
- Loading.story = {
- name: 'loading',
- };
- export const GroupTransfer = () => (
- <div style={{ margin: 10, padding: 10, width: 600 }}>
- <Transfer {...commonProps} dataSource={dataWithGroup} type="groupList" />
- <Transfer {...commonProps} dataSource={dataWithGroup} defaultValue={[2, 4]} type="groupList" />
- </div>
- );
- GroupTransfer.story = {
- name: '分组Transfer',
- };
- const customFilter = (sugInput, item) => {
- return item.value.includes(sugInput) || item.label.includes(sugInput);
- };
- export const CustomFilterRenderSourceItemRenderSelectedItem = () => {
- const data = [
- {
- label: '夏可漫',
- value: '[email protected]',
- abbr: '夏',
- color: 'amber',
- area: 'US',
- key: 1,
- },
- {
- label: '申悦',
- value: '[email protected]',
- abbr: '申',
- color: 'indigo',
- area: 'UK',
- key: 2,
- },
- {
- label: '文嘉茂',
- value: '[email protected]',
- abbr: '文',
- color: 'cyan',
- area: 'HK',
- key: 3,
- },
- {
- label: '曲晨一',
- value: '[email protected]',
- abbr: '一',
- color: 'blue',
- area: 'India',
- key: 4,
- },
- {
- label: '曲晨二',
- value: '[email protected]',
- abbr: '二',
- color: 'blue',
- area: 'India',
- key: 5,
- },
- {
- label: '曲晨三',
- value: '[email protected]',
- abbr: '三',
- color: 'blue',
- area: 'India',
- key: 6,
- },
- ];
- const renderSourceItem = item => {
- return (
- <div className="components-transfer-demo-source-item">
- <Checkbox
- onChange={() => {
- item.onChange();
- }}
- key={item.label}
- checked={item.checked}
- style={{ paddingLeft: 12, height: 52 }}
- >
- <Avatar color={item.color} size="small">
- {item.abbr}
- </Avatar>
- <div className="info">
- <div className="name">{item.label}</div>
- <div className="email">{item.value}</div>
- </div>
- </Checkbox>
- </div>
- );
- };
- const renderSelectedItem = item => {
- return (
- <div className="components-transfer-demo-selected-item" key={item.label}>
- <Avatar color={item.color} size="small">
- {item.abbr}
- </Avatar>
- <div className="info">
- <div className="name">{item.label}</div>
- <div className="email">{item.value}</div>
- </div>
- <IconClose onClick={item.onRemove} />
- </div>
- );
- };
- return (
- <div style={{ margin: 10, padding: 10, width: 600 }}>
- <Transfer
- {...commonProps}
- dataSource={data}
- filter={customFilter}
- defaultValue={['[email protected]']}
- renderSelectedItem={renderSelectedItem}
- renderSourceItem={renderSourceItem}
- inputProps={{ placeholder: '可通过邮箱或者姓名搜索' }}
- />
- </div>
- );
- };
- CustomFilterRenderSourceItemRenderSelectedItem.story = {
- name: 'custom filter, renderSourceItem, renderSelectedItem',
- };
- const TreeTransferDemo = () => {
- 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',
- disabled: true,
- },
- {
- label: 'ShanghaionChangeonChangeonChange',
- value: 'Shanghai',
- key: '0-0-1',
- },
- {
- label: 'Chengdu',
- value: 'Chengdu',
- key: '0-0-2',
- },
- {
- label: 'Chongqing',
- value: 'Chongqing',
- key: '0-0-3',
- },
- ],
- },
- {
- 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',
- },
- ],
- },
- ];
- const [value, $value] = useState(['Shanghai']);
- const onSearch = v => {
- console.log(v);
- };
- const onChange = v => {
- console.log(v);
- $value(v);
- };
- const flatTreeData = dataSource => {
- let newData = [];
- let stack = [...dataSource].reverse();
- while (stack.length) {
- const current = stack.pop();
- if (current.children && Array.isArray(current.children)) {
- const nodes = current.children;
- for (let i = nodes.length - 1; i >= 0; i--) {
- const child = { ...nodes[i] };
- stack.push(child);
- }
- } else {
- current.isLeaf = true;
- }
- newData.push(omit(current, ['children']));
- }
- return newData;
- };
- const flatNodes = flatTreeData(treeData);
- const renderSourcePanel = ({ value, onSelect }) => {
- return (
- <section style={{ width: '50%' }}>
- <Tree
- defaultExpandAll
- multiple
- treeData={treeData}
- disableStrictly
- value={value}
- onChange={onSelect}
- ></Tree>
- </section>
- );
- };
- return (
- <div style={{ margin: 10, padding: 10, width: 600 }}>
- <Transfer
- type="treeList"
- draggable
- dataSource={treeData}
- value={value}
- onChange={onChange}
- onSearch={onSearch}
- ></Transfer>
- <Transfer
- type="treeList"
- draggable
- dataSource={treeData}
- value={value}
- renderSourcePanel={renderSourcePanel}
- onChange={onChange}
- onSearch={onSearch}
- ></Transfer>
- </div>
- );
- };
- export const TreeTransfer = () => <TreeTransferDemo />;
- TreeTransfer.story = {
- name: 'tree transfer',
- };
- class CustomRenderDemo extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- dataSource: Array.from({ length: 100 }, (v, i) => ({
- label: `海底捞门店 ${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 = '删除';
- if (type === 'source') {
- let checked = selectedItems.has(item.key);
- buttonText = checked ? '删除' : '添加';
- }
- return (
- <div className="semi-transfer-item panel-item" key={item.label}>
- <p>{item.label}</p>
- <Button
- theme="borderless"
- type="primary"
- onClick={() => onItemAction(item)}
- className="panel-item-remove"
- size="small"
- >
- {buttonText}
- </Button>
- </div>
- );
- }
- renderSourcePanel(props) {
- const {
- loading,
- noMatch,
- filterData,
- selectedItems,
- allChecked,
- onAllClick,
- inputValue,
- onSearch,
- onSelectOrRemove,
- } = props;
- let content;
- switch (true) {
- case loading:
- content = <Spin loading />;
- break;
- case noMatch:
- content = <div className="empty sp-font">{inputValue ? '无搜索结果' : '暂无内容'}</div>;
- break;
- case !noMatch:
- content = filterData.map(item =>
- this.renderItem('source', item, onSelectOrRemove, selectedItems)
- );
- break;
- default:
- content = null;
- break;
- }
- return (
- <section className="source-panel">
- <div className="panel-header sp-font">门店列表</div>
- <div className="panel-main">
- <Input
- style={{ width: 454, margin: '12px 14px' }}
- prefix={<IconSearch />}
- onChange={onSearch}
- showClear
- />
- <div className="panel-controls sp-font">
- <span>待选门店: {filterData.length}</span>
- <Button onClick={onAllClick} theme="borderless" size="small">
- {allChecked ? '取消全选' : '全选'}
- </Button>
- </div>
- <div className="panel-list">{content}</div>
- </div>
- </section>
- );
- }
- renderSelectedPanel(props) {
- const { selectedData, onClear, clearText, onRemove } = props;
- let mainContent = selectedData.map(item => this.renderItem('selected', item, onRemove));
- if (!selectedData.length) {
- mainContent = <div className="empty sp-font">暂无数据,请从左侧筛选</div>;
- }
- return (
- <section className="selected-panel">
- <div className="panel-header sp-font">
- <div>已选同步门店: {selectedData.length}</div>
- <Button theme="borderless" type="primary" onClick={onClear} size="small">
- {clearText || '清空 '}
- </Button>
- </div>
- <div className="panel-main">{mainContent}</div>
- </section>
- );
- }
- render() {
- const { dataSource } = this.state;
- return (
- <Transfer
- onChange={values => console.log(values)}
- className="component-transfer-demo-custom-panel"
- renderSourcePanel={this.renderSourcePanel}
- renderSelectedPanel={this.renderSelectedPanel}
- dataSource={dataSource}
- />
- );
- }
- }
- export const CustomRender = () => <CustomRenderDemo />;
- CustomRender.story = {
- name: 'customRender',
- };
- class CustomRenderDragDemo extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- dataSource: Array.from({ length: 100 }, (v, i) => ({
- label: `海底捞门店 ${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 = '删除';
- let newItem = item;
- if (type === 'source') {
- let checked = selectedItems.has(item.key);
- buttonText = checked ? '删除' : '添加';
- } else {
- // delete newItem._optionKey;
- newItem = { ...item, key: item._optionKey };
- delete newItem._optionKey;
- }
- const DragHandle = sortableHandle(() => <IconHandle className="pane-item-drag-handler" />);
- return (
- <div className="semi-transfer-item panel-item" key={item.label}>
- {type === 'source' ? null : <DragHandle />}
- <div className="panel-item-main" style={{ flexGrow: 1 }}>
- <p>{item.label}</p>
- <Button
- theme="borderless"
- type="primary"
- onClick={() => onItemAction(newItem)}
- className="panel-item-remove"
- size="small"
- >
- {buttonText}
- </Button>
- </div>
- </div>
- );
- }
- renderSourcePanel(props) {
- const {
- loading,
- noMatch,
- filterData,
- selectedItems,
- allChecked,
- onAllClick,
- inputValue,
- onSearch,
- onSelectOrRemove,
- } = props;
- let content;
- switch (true) {
- case loading:
- content = <Spin loading />;
- break;
- case noMatch:
- content = <div className="empty sp-font">{inputValue ? '无搜索结果' : '暂无内容'}</div>;
- break;
- case !noMatch:
- content = filterData.map(item =>
- this.renderItem('source', item, onSelectOrRemove, selectedItems)
- );
- break;
- default:
- content = null;
- break;
- }
- return (
- <section className="source-panel">
- <div className="panel-header sp-font">门店列表</div>
- <div className="panel-main">
- <Input
- style={{ width: 454, margin: '12px 14px' }}
- prefix={<IconSearch />}
- onChange={onSearch}
- showClear
- />
- <div className="panel-controls sp-font">
- <span>待选门店: {filterData.length}</span>
- <Button onClick={onAllClick} theme="borderless" size="small">
- {allChecked ? '取消全选' : '全选'}
- </Button>
- </div>
- <div className="panel-list">{content}</div>
- </div>
- </section>
- );
- }
- renderSelectedPanel(props) {
- const { selectedData, onClear, clearText, onRemove, onSortEnd } = props;
- let mainContent = null;
- if (!selectedData.length) {
- mainContent = <div className="empty sp-font">暂无数据,请从左侧筛选</div>;
- }
- const SortableItem = SortableElement(item => this.renderItem('selected', item, onRemove));
- const SortableList = SortableContainer(
- ({ items }) => {
- return (
- <div className="panel-main">
- {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
- <SortableItem
- key={item.label}
- index={index}
- {...item}
- _optionKey={item.key}
- ></SortableItem>
- ))}
- </div>
- );
- },
- { distance: 10 }
- );
- mainContent = (
- <SortableList useDragHandle onSortEnd={onSortEnd} items={selectedData}></SortableList>
- );
- return (
- <section className="selected-panel">
- <div className="panel-header sp-font">
- <div>已选同步门店: {selectedData.length}</div>
- <Button theme="borderless" type="primary" onClick={onClear} size="small">
- {clearText || '清空 '}
- </Button>
- </div>
- {mainContent}
- </section>
- );
- }
- render() {
- const { dataSource } = this.state;
- return (
- <Transfer
- defaultValue={[2, 4]}
- onChange={values => console.log(values)}
- className="component-transfer-demo-custom-panel"
- renderSourcePanel={this.renderSourcePanel}
- renderSelectedPanel={this.renderSelectedPanel}
- dataSource={dataSource}
- />
- );
- }
- }
- export const CustomRenderWithDragSort = () => <CustomRenderDragDemo />;
- CustomRenderWithDragSort.story = {
- name: 'customRender with drag sort',
- };
|