Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useTransfer): add useTransfer hooks #2370

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const menus = [
'useCounter',
'useTextSelection',
'useWebSocket',
'useTransfer',
],
},
{
Expand Down
2 changes: 2 additions & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import useUpdateEffect from './useUpdateEffect';
import useUpdateLayoutEffect from './useUpdateLayoutEffect';
import useVirtualList from './useVirtualList';
import useWebSocket from './useWebSocket';
import useTransfer from './useTransfer';
import useWhyDidYouUpdate from './useWhyDidYouUpdate';
import useMutationObserver from './useMutationObserver';

Expand Down Expand Up @@ -156,4 +157,5 @@ export {
useRafTimeout,
useResetState,
useMutationObserver,
useTransfer,
};
51 changes: 51 additions & 0 deletions packages/hooks/src/useTransfer/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { renderHook, act, RenderHookResult } from '@testing-library/react-hooks';
import useTarnsfer, { ReturnValue } from '../index';

describe('useTarnsfer', () => {
let hook: RenderHookResult<any, ReturnValue<any>>;
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
value: `content${i}`,
description: `description of content${i}`,
disabled: i % 3 < 1,
});
}

const setUp = () => renderHook(() => useTarnsfer(mockData));

beforeEach(() => {
hook = setUp();
});

it('selectAll and unSelectAll should work correct', async () => {
expect(hook.result.current.transferProps.selectedKeys.length).toBe(0);
expect(hook.result.current.unSelectedKeys.length).toBe(mockData.length);
act(() => {
hook.result.current.selectAll();
});
expect(hook.result.current.transferProps.selectedKeys.length).toBe(mockData.length);
expect(hook.result.current.unSelectedKeys.length).toBe(0);
act(() => {
hook.result.current.unSelectAll();
});
expect(hook.result.current.transferProps.selectedKeys.length).toBe(0);
expect(hook.result.current.unSelectedKeys.length).toBe(mockData.length);
});

it('leftAll and rightAll should work correct', async () => {
expect(hook.result.current.transferProps.targetKeys.length).toBe(0);
expect(hook.result.current.noTargetKeys.length).toBe(mockData.length);
act(() => {
hook.result.current.rightAll();
});
expect(hook.result.current.transferProps.targetKeys.length).toBe(mockData.length);
expect(hook.result.current.noTargetKeys.length).toBe(0);
act(() => {
hook.result.current.leftAll();
});
expect(hook.result.current.transferProps.targetKeys.length).toBe(0);
expect(hook.result.current.noTargetKeys.length).toBe(mockData.length);
});
});
30 changes: 30 additions & 0 deletions packages/hooks/src/useTransfer/demo/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { Transfer } from 'antd';
import { useTransfer } from 'ahooks';

const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
value: `content${i}`,
description: `description of content${i}`,
disabled: i % 3 < 1,
});
}

export default () => {
const { transferProps, noTargetKeys, unSelectedKeys } = useTransfer(mockData);
return (
<div>
<div>leftKeys : {noTargetKeys.join(',')}</div>
<div>rightKeys : {transferProps.targetKeys.join(',')}</div>
<div>selectedKeys : {transferProps.selectedKeys.join(',')}</div>
<div>unSelectedKeys : {unSelectedKeys.join(',')}</div>
<Transfer
titleTexts={['Source', 'Target']}
render={(item) => item.value}
{...transferProps}
/>
</div>
);
};
61 changes: 61 additions & 0 deletions packages/hooks/src/useTransfer/demo/demo2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { Transfer, Button, Switch } from 'antd';
import { useTransfer } from 'ahooks';

const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
value: `content${i}`,
description: `description of content${i}`,
disabled: i % 3 < 1,
});
}

export default () => {
const {
transferProps,
setTargetKeys,
setSelectedKeys,
setDisabled,
setShowSearch,
selectAll,
unSelectAll,
leftAll,
rightAll,
} = useTransfer(mockData);

return (
<div>
<Button style={{ marginRight: '15px' }} onClick={unSelectAll}>
清空全部
</Button>
<Button type="primary" onClick={selectAll}>
选中全部
</Button>
<Button style={{ margin: '0 15px 0 40px' }} onClick={leftAll}>
全部左边
</Button>
<Button type="primary" onClick={rightAll}>
全部右边
</Button>
<Button
style={{ margin: '0 15px 0 40px' }}
onClick={() => setSelectedKeys((keys) => [...keys, '2', '3', '5'])}
>
选中2,3,5
</Button>
<Button type="primary" onClick={() => setTargetKeys((keys) => [...keys, '6', '8', '9'])}>
加入6,8,9
</Button>
<Transfer render={(item) => item.value} {...transferProps} />
<Switch
onChange={setDisabled}
style={{ margin: '0 15px' }}
checkedChildren="开"
unCheckedChildren="关"
/>
<Switch onChange={setShowSearch} checkedChildren="带搜索" unCheckedChildren="无搜索" />
</div>
);
};
67 changes: 67 additions & 0 deletions packages/hooks/src/useTransfer/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
nav:
path: /hooks
---

# useTransfer

Encapsulates the common logic of Antd Transfer.

## Examples

### Default Usage

<code src="./demo/demo1.tsx" />

### Advanced Usage

<code src="./demo/demo2.tsx" />

## API

```typescript
const result: ReturnValue<Item> = useTransfer<Item>(
dataSource: Item[],
options: Options = {}
);
```

### Params

| Property | Description | Type |
| ---------- | ------------------------------------------------------------------------------------------- | ------- |
| dataSource | Transfer Component data source | Item[] |
| options | Optional configuration item that can be used to configure default data for server rendering | Options |

### Result

| Property | Description | Type |
| ---------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| transferProps.dataSource | Transfer Component data source | Item[] |
| transferProps.targetKeys | Right data item | string[] |
| transferProps.selectedKeys | selected item | string[] |
| transferProps.disabled | Whether to disable | boolean |
| transferProps.showSearch | Searchable or not | boolean |
| transferProps.filterOption | Antd Transfer searches using filter properties | any |
| transferProps.onChange | The callback function when the option is transferred between two columns | (nextTargetKeys: string[], direction: string, moveKeys: string[]) => void |
| transferProps.onSelectChange | The callback function when the selected item changes | (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => void |
| noTargetKeys | Left data item | string[] |
| unSelectedKeys | Unselected item | string[] |
| setTargetKeys | Set the data item on the right | (keys: string[]) => void |
| setSelectedKeys | Set check item | (keys: string[]) => void |
| setDisabled | Set whether to disable | (disabled: boolean) => void |
| setShowSearch | Set whether to search | (showSearch: boolean) => void |
| leftAll | Move all data to the left | () => void |
| rightAll | Move all data to the right | () => void |
| selectAll | check all | () => void |
| unSelectAll | All reverse selection | () => void |

### Options

| Property | Description | Type | Default |
| ------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------- | ------- |
| defaultTargetKeys | Default right data item | string[] | [] |
| defaultSelectedKeys | Default check item | string[] | [] |
| defaultDisabled | Disabled by default | boolean | false |
| deafultShowSearch | Default whether to search | boolean | false |
| onChange | The callback function when the option is transferred between two columns | (nextTargetKeys: string[], direction: string, moveKeys: string[]) => void | - |
128 changes: 128 additions & 0 deletions packages/hooks/src/useTransfer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useState, useMemo, useCallback } from 'react';

export interface ReturnValue<Item> {
transferProps: {
dataSource: Item[];
targetKeys: string[];
selectedKeys: string[];
disabled: boolean;
showSearch: boolean;
filterOption: any;
onChange: (nextTargetKeys: string[], direction: string, moveKeys: string[]) => void;
onSelectChange: (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => void;
};
noTargetKeys: string[];
unSelectedKeys: string[];
setTargetKeys: React.Dispatch<React.SetStateAction<string[]>>;
setSelectedKeys: React.Dispatch<React.SetStateAction<string[]>>;
setDisabled: React.Dispatch<React.SetStateAction<boolean>>;
setShowSearch: React.Dispatch<React.SetStateAction<boolean>>;
leftAll: () => void;
rightAll: () => void;
selectAll: () => void;
unSelectAll: () => void;
}

export interface TransferItem {
key: string;
[name: string]: any;
}

export interface Options {
defaultTargetKeys?: string[];
defaultSelectedKeys?: string[];
defaultDisabled?: boolean;
deafultShowSearch?: boolean;
onChange?: (nextTargetKeys: string[], direction: string, moveKeys: string[]) => void;
}

const useTransfer = <Item extends TransferItem>(
dataSource: Item[],
options: Options = {},
): ReturnValue<Item> => {
const {
defaultTargetKeys = [],
defaultSelectedKeys = [],
defaultDisabled = false,
deafultShowSearch = false,
} = options;
const [targetKeys, setTargetKeys] = useState(defaultTargetKeys);
const [selectedKeys, setSelectedKeys] = useState(defaultSelectedKeys);
const [disabled, setDisabled] = useState(defaultDisabled);
const [showSearch, setShowSearch] = useState(deafultShowSearch);
const noTargetKeys = useMemo(
() => dataSource.filter((d) => !targetKeys.includes(d.key)).map((d) => d.key),
[dataSource, targetKeys],
);
const unSelectedKeys = useMemo(
() => dataSource.filter((d) => !selectedKeys.includes(d.key)).map((d) => d.key),
[dataSource, selectedKeys],
);

const { leftAll, unSelectAll } = useMemo(() => {
const leftAll = () => {
setTargetKeys([]);
};

const unSelectAll = () => {
setSelectedKeys([]);
};

return { leftAll, unSelectAll };
}, []);

const { rightAll, selectAll } = useMemo(() => {
const rightAll = () => {
setTargetKeys(dataSource.map((d) => d.key));
};

const selectAll = () => {
setSelectedKeys(dataSource.map((d) => d.key));
};

return { rightAll, selectAll };
}, []);

const handleChange = useCallback(
(nextTargetKeys: string[], direction: string, moveKeys: string[]) => {
setTargetKeys(nextTargetKeys);
if (options.onChange) {
options.onChange(nextTargetKeys, direction, moveKeys);
}
},
[options.onChange],
);

const handleSelectChange = useCallback(
(sourceSelectedKeys: string[], targetSelectedKeys: string[]) => {
setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys]);
},
[],
);

return {
transferProps: {
dataSource,
targetKeys,
selectedKeys,
disabled,
filterOption: (inputValue: string, option: any) =>
option.description.indexOf(inputValue) > -1,
showSearch,
onChange: handleChange,
onSelectChange: handleSelectChange,
},
noTargetKeys,
unSelectedKeys,
setTargetKeys,
setSelectedKeys,
setDisabled,
setShowSearch,
leftAll,
rightAll,
selectAll,
unSelectAll,
};
};

export default useTransfer;
Loading
Loading