Skip to content

Commit 07b0535

Browse files
committed
feat(Search): support resultList and clearTrigger props
1 parent 4c06787 commit 07b0535

File tree

5 files changed

+90
-28
lines changed

5 files changed

+90
-28
lines changed

src/search/Search.tsx

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import { searchDefaultProps } from './defaultProps';
1111
import { ENTER_REG } from '../_common/js/common';
1212
import useDefaultProps from '../hooks/useDefaultProps';
1313
import { usePrefixClass } from '../hooks/useClass';
14+
import Cell from '../cell/Cell';
1415

1516
export interface SearchProps extends TdSearchProps, StyledProps {}
1617

1718
const Search: FC<SearchProps> = (props) => {
1819
const {
1920
clearable,
21+
clearTrigger,
2022
action,
2123
center,
2224
disabled,
@@ -26,6 +28,7 @@ const Search: FC<SearchProps> = (props) => {
2628
readonly,
2729
shape,
2830
value,
31+
resultList,
2932
onActionClick,
3033
onBlur,
3134
onChange,
@@ -36,6 +39,7 @@ const Search: FC<SearchProps> = (props) => {
3639
const [focusState, setFocus] = useState(focus);
3740
const inputRef = useRef(null);
3841
const [searchValue, setSearchValue] = useDefault(value, '', onChange);
42+
const [showResultList, setShowResultList] = useState(false);
3943

4044
const { classPrefix } = useConfig();
4145
const searchClass = usePrefixClass('search');
@@ -53,6 +57,7 @@ const Search: FC<SearchProps> = (props) => {
5357
};
5458

5559
const handleInput = (e: FormEvent<HTMLInputElement>) => {
60+
setShowResultList(true);
5661
if (e instanceof InputEvent) {
5762
// 中文输入的时候inputType是insertCompositionText所以中文输入的时候禁止触发。
5863
const checkInputType = e.inputType && e.inputType === 'insertCompositionText';
@@ -63,16 +68,18 @@ const Search: FC<SearchProps> = (props) => {
6368
};
6469

6570
const handleClear = (e: MouseEvent<HTMLDivElement>) => {
66-
setSearchValue('', { trigger: 'input-change' });
71+
setSearchValue('', { trigger: 'clear', e });
6772
setFocus(true);
6873
onClear?.({ e });
6974
};
7075

7176
const handleFocus = (e: FocusEvent<HTMLDivElement>) => {
77+
setFocus(true);
7278
onFocus?.({ value: searchValue, e });
7379
};
7480

7581
const handleBlur = (e: FocusEvent<HTMLDivElement>) => {
82+
setFocus(false);
7683
onBlur?.({ value: searchValue, e });
7784
};
7885

@@ -88,6 +95,7 @@ const Search: FC<SearchProps> = (props) => {
8895
// 如果按的是 enter 键, 13是 enter
8996
if (ENTER_REG.test(e.code) || ENTER_REG.test(e.key)) {
9097
e.preventDefault();
98+
setShowResultList(false);
9199
onSubmit?.({ value: searchValue, e });
92100
}
93101
};
@@ -100,7 +108,7 @@ const Search: FC<SearchProps> = (props) => {
100108
};
101109

102110
const renderClear = () => {
103-
if (clearable && searchValue) {
111+
if (clearable && searchValue && (clearTrigger === 'always' || (clearTrigger === 'focus' && focusState))) {
104112
return (
105113
<div className={`${searchClass}__clear`} onClick={handleClear}>
106114
<CloseCircleFilledIcon />
@@ -121,31 +129,71 @@ const Search: FC<SearchProps> = (props) => {
121129
return null;
122130
};
123131

132+
const highlightSearchValue = (item: string, value: string) => {
133+
const parts = item.split(new RegExp(`(${value})`, 'gi'));
134+
return parts.map((part, index) =>
135+
part.toLowerCase() === value.toLowerCase() ? (
136+
<span key={index} className={`${searchClass}__result-item--highLight`}>
137+
{part}
138+
</span>
139+
) : (
140+
part
141+
),
142+
);
143+
};
144+
145+
const handleSelectOption = (item: string, e: MouseEvent<HTMLDivElement>) => {
146+
setShowResultList(false);
147+
setSearchValue(item, { trigger: 'option-click', e });
148+
};
149+
150+
const renderResultList = () => {
151+
if (!showResultList || !resultList || resultList.length === 0) {
152+
return null;
153+
}
154+
155+
return (
156+
<div className={`${searchClass}__result-list`}>
157+
{resultList.map((item, index) => (
158+
<Cell
159+
key={index}
160+
className={`${searchClass}__result-item`}
161+
onClick={(context) => handleSelectOption(item, context.e)}
162+
title={highlightSearchValue(item, searchValue)}
163+
/>
164+
))}
165+
</div>
166+
);
167+
};
168+
124169
return (
125-
<div className={`${searchClass}`}>
126-
<div className={`${boxClasses}`}>
127-
{renderLeftIcon()}
128-
<input
129-
ref={inputRef}
130-
value={searchValue}
131-
type="search"
132-
className={`${inputClasses}`}
133-
style={
134-
props.cursorColor ? ({ '--td-search-cursor-color': props.cursorColor } as React.CSSProperties) : undefined
135-
}
136-
autoFocus={focus}
137-
placeholder={placeholder}
138-
readOnly={readonly}
139-
disabled={disabled}
140-
onKeyDown={handleSearch}
141-
onFocus={handleFocus}
142-
onBlur={handleBlur}
143-
onInput={handleInput}
144-
onCompositionEnd={handleCompositionend}
145-
/>
146-
{renderClear()}
170+
<div>
171+
<div className={`${searchClass}`}>
172+
<div className={`${boxClasses}`}>
173+
{renderLeftIcon()}
174+
<input
175+
ref={inputRef}
176+
value={searchValue}
177+
type="search"
178+
className={`${inputClasses}`}
179+
style={
180+
props.cursorColor ? ({ '--td-search-cursor-color': props.cursorColor } as React.CSSProperties) : undefined
181+
}
182+
autoFocus={focus}
183+
placeholder={placeholder}
184+
readOnly={readonly}
185+
disabled={disabled}
186+
onKeyDown={handleSearch}
187+
onFocus={handleFocus}
188+
onBlur={handleBlur}
189+
onInput={handleInput}
190+
onCompositionEnd={handleCompositionend}
191+
/>
192+
{renderClear()}
193+
</div>
194+
{renderAction()}
147195
</div>
148-
{renderAction()}
196+
{renderResultList()}
149197
</div>
150198
);
151199
};

src/search/defaultProps.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import { TdSearchProps } from './type';
77
export const searchDefaultProps: TdSearchProps = {
88
action: '',
99
center: false,
10+
clearTrigger: 'always',
1011
clearable: true,
1112
cursorColor: '#0052d9',
1213
disabled: false,
1314
focus: false,
1415
leftIcon: 'search',
1516
placeholder: '',
1617
readonly: undefined,
18+
resultList: [],
1719
shape: 'square',
1820
};

src/search/search.en-US.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ style | Object | - | CSS(Cascading Style Sheets),Typescript: `React.CSSPropert
1111
action | TNode | '' | Typescript: `string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
1212
autocompleteOptions | Array | - | autocomplete words list。Typescript: `Array<AutocompleteOption>` `type AutocompleteOption = string \| { label: string \| TNode; group?: boolean }`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/search/type.ts) | N
1313
center | Boolean | false | \- | N
14+
clearTrigger | String | always | show clear icon, clicked to clear input value。options: always / focus | N
1415
clearable | Boolean | true | \- | N
1516
cursorColor | String | #0052d9 | `0.21.2` | N
1617
disabled | Boolean | false | \- | N
@@ -19,13 +20,14 @@ leftIcon | TNode | 'search' | Typescript: `string \| TNode`。[see more ts defin
1920
placeholder | String | '' | \- | N
2021
prefixIcon | TElement | - | `deprecated`。Typescript: `TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
2122
readonly | Boolean | undefined | \- | N
23+
resultList | Array | [] | Typescript: `Array<string>` | N
2224
shape | String | 'square' | options: square/round | N
2325
suffixIcon | TElement | - | `deprecated`。Typescript: `TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
2426
value | String | - | \- | N
2527
defaultValue | String | - | uncontrolled property | N
2628
onActionClick | Function | | Typescript: `({}) => void`<br/> | N
2729
onBlur | Function | | Typescript: `(context: { value: string; e: FocusEvent }) => void`<br/> | N
28-
onChange | Function | | Typescript: `(value: string, context: { trigger: 'input-change' \| 'option-click'; e?: InputEvent \| MouseEvent }) => void`<br/> | N
30+
onChange | Function | | Typescript: `(value: string, context: { trigger: 'input-change' \| 'option-click' \| 'clear'; e?: InputEvent \| MouseEvent }) => void`<br/> | N
2931
onClear | Function | | Typescript: `(context: { e: MouseEvent }) => void`<br/> | N
3032
onFocus | Function | | Typescript: `(context: { value: string; e: FocusEvent }) => void`<br/> | N
3133
onSearch | Function | | Typescript: `(context?: { value: string; trigger: 'submit' \| 'option-click' \| 'clear'; e?: InputEvent \| MouseEvent }) => void`<br/> | N

src/search/search.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
1111
action | TNode | '' | 自定义右侧操作按钮文字。TS 类型:`string \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
1212
autocompleteOptions | Array | - | 【讨论中】联想词列表,如果不存在或长度为 0 则不显示联想框。可以使用函数 `label` 自定义联想词为任意内容;也可使用插槽 `option` 定义联想词内容,插槽参数为 `{ option: AutocompleteOption; index: number }`。如果 `group` 值为 `true` 则表示当前项为分组标题。TS 类型:`Array<AutocompleteOption>` `type AutocompleteOption = string \| { label: string \| TNode; group?: boolean }`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/search/type.ts) | N
1313
center | Boolean | false | 是否居中 | N
14+
clearTrigger | String | always | 清空图标触发方式,仅在输入框有值时有效。可选项:always / focus | N
1415
clearable | Boolean | true | 是否启用清除控件 | N
1516
cursorColor | String | #0052d9 | `0.21.2`。光标颜色 | N
1617
disabled | Boolean | false | 是否禁用 | N
@@ -19,13 +20,14 @@ leftIcon | TNode | 'search' | 左侧图标。TS 类型:`string \| TNode`。[
1920
placeholder | String | '' | 占位符 | N
2021
prefixIcon | TElement | - | 已废弃。前置图标,默认为搜索图标。值为 `null` 时则不显示。TS 类型:`TNode`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
2122
readonly | Boolean | undefined | 只读状态 | N
23+
resultList | Array | [] | 预览结果列表。TS 类型:`Array<string>` | N
2224
shape | String | 'square' | 搜索框形状。可选项:square/round | N
2325
suffixIcon | TElement | - | 已废弃。后置图标。TS 类型:`TNode`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
2426
value | String | - | 值 | N
2527
defaultValue | String | - | 值。非受控属性 | N
2628
onActionClick | Function | | TS 类型:`({}) => void`<br/>点击右侧操作按钮文字时触发 | N
2729
onBlur | Function | | TS 类型:`(context: { value: string; e: FocusEvent }) => void`<br/>失去焦点时触发 | N
28-
onChange | Function | | TS 类型:`(value: string, context: { trigger: 'input-change' \| 'option-click'; e?: InputEvent \| MouseEvent }) => void`<br/>搜索关键词发生变化时触发,可能场景有:搜索框内容发生变化、点击联想词 | N
30+
onChange | Function | | TS 类型:`(value: string, context: { trigger: 'input-change' \| 'option-click' \| 'clear'; e?: InputEvent \| MouseEvent }) => void`<br/>搜索关键词发生变化时触发,可能场景有:搜索框内容发生变化、点击联想词 | N
2931
onClear | Function | | TS 类型:`(context: { e: MouseEvent }) => void`<br/>点击清除时触发 | N
3032
onFocus | Function | | TS 类型:`(context: { value: string; e: FocusEvent }) => void`<br/>获得焦点时触发 | N
3133
onSearch | Function | | TS 类型:`(context?: { value: string; trigger: 'submit' \| 'option-click' \| 'clear'; e?: InputEvent \| MouseEvent }) => void`<br/>【讨论中】搜索触发,包含:手机键盘提交健、联想关键词点击、清空按钮点击等 | N

src/search/type.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ export interface TdSearchProps {
6161
* 只读状态
6262
*/
6363
readonly?: boolean;
64+
/**
65+
* 预览结果列表
66+
* @default []
67+
*/
68+
resultList?: Array<string>;
6469
/**
6570
* 搜索框形状
6671
* @default 'square'
@@ -91,7 +96,10 @@ export interface TdSearchProps {
9196
*/
9297
onChange?: (
9398
value: string,
94-
context: { trigger: 'input-change' | 'option-click'; e?: FormEvent<HTMLInputElement> | MouseEvent<HTMLDivElement> },
99+
context: {
100+
trigger: 'input-change' | 'option-click' | 'clear';
101+
e?: FormEvent<HTMLInputElement> | MouseEvent<HTMLDivElement>;
102+
},
95103
) => void;
96104
/**
97105
* 点击清除时触发

0 commit comments

Comments
 (0)