Skip to content

Commit 93d450b

Browse files
EmilyyyLiu刘欢
andauthored
feat: combine search props (#646)
* feat: combine showSearch * feat: use hooks (add useSearchConfig) * feat: change useSearchConfig memo value * test: add test * fix: delete inputValue in ShowSearch * feat: inputValue Use `showSearch.searchValue` instead * feat: change ts * feat: change useSearchConfig * feat: change useSearchConfig1 * feat: change useSearchConfig * test: test showSearch * test: test showSearch * build: Upgrade @rc-component/select to 1.1.0 --------- Co-authored-by: 刘欢 <[email protected]>
1 parent 51d13d2 commit 93d450b

File tree

4 files changed

+213
-11
lines changed

4 files changed

+213
-11
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
},
4646
"dependencies": {
4747
"classnames": "2.x",
48-
"@rc-component/select": "~1.0.7",
48+
"@rc-component/select": "~1.1.0",
4949
"@rc-component/tree": "~1.0.1",
5050
"@rc-component/util": "^1.2.1"
5151
},

src/TreeSelect.tsx

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,19 @@ import type {
3434
FieldNames,
3535
LegacyDataNode,
3636
} from './interface';
37+
import useSearchConfig from './hooks/useSearchConfig';
3738

3839
export type SemanticName = 'input' | 'prefix' | 'suffix';
3940
export type PopupSemantic = 'item' | 'itemTitle';
41+
export interface SearchConfig {
42+
searchValue?: string;
43+
onSearch?: (value: string) => void;
44+
autoClearSearchValue?: boolean;
45+
filterTreeNode?: boolean | ((inputValue: string, treeNode: DataNode) => boolean);
46+
treeNodeFilterProp?: string;
47+
}
4048
export interface TreeSelectProps<ValueType = any, OptionType extends DataNode = DataNode>
41-
extends Omit<BaseSelectPropsWithoutPrivate, 'mode' | 'classNames' | 'styles'> {
49+
extends Omit<BaseSelectPropsWithoutPrivate, 'mode' | 'classNames' | 'styles' | 'showSearch'> {
4250
prefixCls?: string;
4351
id?: string;
4452
children?: React.ReactNode;
@@ -54,12 +62,18 @@ export interface TreeSelectProps<ValueType = any, OptionType extends DataNode =
5462
onChange?: (value: ValueType, labelList: React.ReactNode[], extra: ChangeEventExtra) => void;
5563

5664
// >>> Search
65+
showSearch?: boolean | SearchConfig;
66+
/** @deprecated Use `showSearch.searchValue` instead */
5767
searchValue?: string;
58-
/** @deprecated Use `searchValue` instead */
68+
/** @deprecated Use `showSearch.searchValue` instead */
5969
inputValue?: string;
70+
/** @deprecated Use `showSearch.onSearch` instead */
6071
onSearch?: (value: string) => void;
72+
/** @deprecated Use `showSearch.autoClearSearchValue` instead */
6173
autoClearSearchValue?: boolean;
74+
/** @deprecated Use `showSearch.filterTreeNode` instead */
6275
filterTreeNode?: boolean | ((inputValue: string, treeNode: DataNode) => boolean);
76+
/** @deprecated Use `showSearch.treeNodeFilterProp` instead */
6377
treeNodeFilterProp?: string;
6478

6579
// >>> Select
@@ -127,12 +141,7 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
127141
onDeselect,
128142

129143
// Search
130-
searchValue,
131-
inputValue,
132-
onSearch,
133-
autoClearSearchValue = true,
134-
filterTreeNode,
135-
treeNodeFilterProp = 'value',
144+
showSearch,
136145

137146
// Selector
138147
showCheckedStrategy,
@@ -193,6 +202,15 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
193202
const mergedLabelInValue = treeCheckStrictly || labelInValue;
194203
const mergedMultiple = mergedCheckable || multiple;
195204

205+
const [mergedShowSearch, searchConfig] = useSearchConfig(showSearch, props);
206+
const {
207+
searchValue,
208+
onSearch,
209+
autoClearSearchValue = true,
210+
filterTreeNode,
211+
treeNodeFilterProp = 'value',
212+
} = searchConfig;
213+
196214
const [internalValue, setInternalValue] = useMergedState(defaultValue, { value });
197215

198216
// `multiple` && `!treeCheckable` should be show all
@@ -219,7 +237,7 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
219237

220238
// =========================== Search ===========================
221239
const [mergedSearchValue, setSearchValue] = useMergedState('', {
222-
value: searchValue !== undefined ? searchValue : inputValue,
240+
value: searchValue,
223241
postState: search => search || '',
224242
});
225243

@@ -725,6 +743,8 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
725743
displayValues={cachedDisplayValues}
726744
onDisplayValuesChange={onDisplayValuesChange}
727745
// >>> Search
746+
{...searchConfig}
747+
showSearch={mergedShowSearch}
728748
searchValue={mergedSearchValue}
729749
onSearch={onInternalSearch}
730750
// >>> Options

src/hooks/useSearchConfig.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { SearchConfig, TreeSelectProps } from '@/TreeSelect';
2+
import * as React from 'react';
3+
4+
// Convert `showSearch` to unique config
5+
export default function useSearchConfig(
6+
showSearch: boolean | SearchConfig,
7+
props: TreeSelectProps,
8+
) {
9+
const {
10+
searchValue,
11+
inputValue,
12+
onSearch,
13+
autoClearSearchValue,
14+
filterTreeNode,
15+
treeNodeFilterProp,
16+
} = props;
17+
return React.useMemo<[boolean | undefined, SearchConfig]>(() => {
18+
const isObject = typeof showSearch === 'object';
19+
20+
const searchConfig: SearchConfig = {
21+
searchValue: searchValue ?? inputValue,
22+
onSearch,
23+
autoClearSearchValue,
24+
filterTreeNode,
25+
treeNodeFilterProp,
26+
...(isObject ? showSearch : {}),
27+
};
28+
29+
return [isObject ? true : showSearch, searchConfig];
30+
}, [
31+
showSearch,
32+
searchValue,
33+
inputValue,
34+
onSearch,
35+
autoClearSearchValue,
36+
filterTreeNode,
37+
treeNodeFilterProp,
38+
]);
39+
}

tests/Select.spec.tsx

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render } from '@testing-library/react';
1+
import { render, fireEvent } from '@testing-library/react';
22
import { mount } from 'enzyme';
33
import KeyCode from '@rc-component/util/lib/KeyCode';
44
import React from 'react';
@@ -709,4 +709,147 @@ describe('TreeSelect.basic', () => {
709709
expect(itemTitle).toHaveStyle(customStyles.popup.itemTitle);
710710
expect(item).toHaveStyle(customStyles.popup.item);
711711
});
712+
713+
describe('combine showSearch', () => {
714+
const treeData = [
715+
{ key: '0', value: 'a', title: '0 label' },
716+
{
717+
key: '1',
718+
value: 'b',
719+
title: '1 label',
720+
children: [
721+
{ key: '10', value: 'ba', title: '10 label' },
722+
{ key: '11', value: 'bb', title: '11 label' },
723+
],
724+
},
725+
];
726+
it('searchValue and onSearch', () => {
727+
const currentSearchFn = jest.fn();
728+
const legacySearchFn = jest.fn();
729+
const { container } = render(
730+
<>
731+
<div id="test1">
732+
<TreeSelect
733+
showSearch
734+
searchValue="1-10"
735+
onSearch={currentSearchFn}
736+
open
737+
treeDefaultExpandAll
738+
treeData={treeData}
739+
/>
740+
</div>
741+
<div id="test2">
742+
<TreeSelect
743+
showSearch={{ searchValue: '1-10', onSearch: legacySearchFn }}
744+
open
745+
treeDefaultExpandAll
746+
treeData={treeData}
747+
/>
748+
</div>
749+
</>,
750+
);
751+
const legacyInput = container.querySelector<HTMLInputElement>('#test1 input');
752+
const currentInput = container.querySelector<HTMLInputElement>('#test2 input');
753+
fireEvent.change(legacyInput, { target: { value: '2' } });
754+
fireEvent.change(currentInput, { target: { value: '2' } });
755+
expect(currentSearchFn).toHaveBeenCalledWith('2');
756+
expect(legacySearchFn).toHaveBeenCalledWith('2');
757+
});
758+
it('treeNodeFilterProp and autoClearSearchValue', () => {
759+
const { container } = render(
760+
<>
761+
<div id="test1">
762+
<TreeSelect
763+
showSearch
764+
open
765+
treeDefaultExpandAll
766+
treeData={treeData}
767+
autoClearSearchValue={false}
768+
treeNodeFilterProp="value"
769+
/>
770+
</div>
771+
<div id="test2">
772+
<TreeSelect
773+
showSearch={{
774+
autoClearSearchValue: false,
775+
treeNodeFilterProp: 'value',
776+
}}
777+
open
778+
treeDefaultExpandAll
779+
treeData={treeData}
780+
/>
781+
</div>
782+
</>,
783+
);
784+
const legacyInput = container.querySelector<HTMLInputElement>('#test1 input');
785+
const currentInput = container.querySelector<HTMLInputElement>('#test2 input');
786+
fireEvent.change(legacyInput, { target: { value: 'a' } });
787+
fireEvent.change(currentInput, { target: { value: 'a' } });
788+
const legacyItem = container.querySelector<HTMLInputElement>(
789+
'#test1 .rc-tree-select-tree-title',
790+
);
791+
const currentItem = container.querySelector<HTMLInputElement>(
792+
'#test2 .rc-tree-select-tree-title',
793+
);
794+
795+
expect(legacyInput).toHaveValue('a');
796+
expect(currentInput).toHaveValue('a');
797+
fireEvent.click(legacyItem);
798+
fireEvent.click(currentItem);
799+
const valueSpan = container.querySelectorAll<HTMLSpanElement>(
800+
' .rc-tree-select-selection-item',
801+
);
802+
803+
expect(valueSpan[0]).toHaveTextContent('0 label');
804+
expect(valueSpan[1]).toHaveTextContent('0 label');
805+
});
806+
it('filterTreeNode', () => {
807+
const { container } = render(
808+
<>
809+
<div id="test1">
810+
<TreeSelect
811+
showSearch
812+
open
813+
treeDefaultExpandAll
814+
treeData={treeData}
815+
filterTreeNode={(inputValue, node) => node.value === inputValue}
816+
/>
817+
</div>
818+
<div id="test2">
819+
<TreeSelect
820+
showSearch={{
821+
filterTreeNode: (inputValue, node) => node.value === inputValue,
822+
}}
823+
open
824+
treeDefaultExpandAll
825+
treeData={treeData}
826+
/>
827+
</div>
828+
</>,
829+
);
830+
const legacyInput = container.querySelector<HTMLInputElement>('#test1 input');
831+
const currentInput = container.querySelector<HTMLInputElement>('#test2 input');
832+
fireEvent.change(legacyInput, { target: { value: 'bb' } });
833+
fireEvent.change(currentInput, { target: { value: 'bb' } });
834+
const options = container.querySelectorAll<HTMLDivElement>(' .rc-tree-select-tree-title');
835+
836+
expect(options).toHaveLength(4);
837+
});
838+
it.each([
839+
// [description, props, shouldExist]
840+
['showSearch=false ', { showSearch: false }, false],
841+
['showSearch=undefined ', {}, false],
842+
['showSearch=true', { showSearch: true }, true],
843+
])('%s', (_, props: { showSearch?: boolean; mode?: 'tags' }, shouldExist) => {
844+
const { container } = render(
845+
<TreeSelect open treeDefaultExpandAll treeData={treeData} {...props} />,
846+
);
847+
const inputNode = container.querySelector('input');
848+
if (shouldExist) {
849+
expect(inputNode).not.toHaveAttribute('readonly');
850+
} else {
851+
expect(inputNode).toHaveAttribute('readonly');
852+
}
853+
});
854+
});
712855
});

0 commit comments

Comments
 (0)