diff --git a/src/search/Search.tsx b/src/search/Search.tsx index 4e7346d8f..490bd27a2 100644 --- a/src/search/Search.tsx +++ b/src/search/Search.tsx @@ -11,12 +11,14 @@ import { searchDefaultProps } from './defaultProps'; import { ENTER_REG } from '../_common/js/common'; import useDefaultProps from '../hooks/useDefaultProps'; import { usePrefixClass } from '../hooks/useClass'; +import Cell from '../cell/Cell'; export interface SearchProps extends TdSearchProps, StyledProps {} const Search: FC = (props) => { const { clearable, + clearTrigger, action, center, disabled, @@ -26,6 +28,7 @@ const Search: FC = (props) => { readonly, shape, value, + resultList, onActionClick, onBlur, onChange, @@ -36,6 +39,7 @@ const Search: FC = (props) => { const [focusState, setFocus] = useState(focus); const inputRef = useRef(null); const [searchValue, setSearchValue] = useDefault(value, '', onChange); + const [showResultList, setShowResultList] = useState(false); const { classPrefix } = useConfig(); const searchClass = usePrefixClass('search'); @@ -53,6 +57,7 @@ const Search: FC = (props) => { }; const handleInput = (e: FormEvent) => { + setShowResultList(true); if (e instanceof InputEvent) { // 中文输入的时候inputType是insertCompositionText所以中文输入的时候禁止触发。 const checkInputType = e.inputType && e.inputType === 'insertCompositionText'; @@ -63,16 +68,18 @@ const Search: FC = (props) => { }; const handleClear = (e: MouseEvent) => { - setSearchValue('', { trigger: 'input-change' }); + setSearchValue('', { trigger: 'clear', e }); setFocus(true); onClear?.({ e }); }; const handleFocus = (e: FocusEvent) => { + setFocus(true); onFocus?.({ value: searchValue, e }); }; const handleBlur = (e: FocusEvent) => { + setFocus(false); onBlur?.({ value: searchValue, e }); }; @@ -88,6 +95,7 @@ const Search: FC = (props) => { // 如果按的是 enter 键, 13是 enter if (ENTER_REG.test(e.code) || ENTER_REG.test(e.key)) { e.preventDefault(); + setShowResultList(false); onSubmit?.({ value: searchValue, e }); } }; @@ -100,7 +108,7 @@ const Search: FC = (props) => { }; const renderClear = () => { - if (clearable && searchValue) { + if (clearable && searchValue && (clearTrigger === 'always' || (clearTrigger === 'focus' && focusState))) { return (
@@ -121,31 +129,71 @@ const Search: FC = (props) => { return null; }; + const highlightSearchValue = (item: string, value: string) => { + const parts = item.split(new RegExp(`(${value})`, 'gi')); + return parts.map((part, index) => + part.toLowerCase() === value.toLowerCase() ? ( + + {part} + + ) : ( + part + ), + ); + }; + + const handleSelectOption = (item: string, e: MouseEvent) => { + setShowResultList(false); + setSearchValue(item, { trigger: 'option-click', e }); + }; + + const renderResultList = () => { + if (!showResultList || !resultList || resultList.length === 0) { + return null; + } + + return ( +
+ {resultList.map((item, index) => ( + handleSelectOption(item, context.e)} + title={highlightSearchValue(item, searchValue)} + /> + ))} +
+ ); + }; + return ( -
-
- {renderLeftIcon()} - - {renderClear()} +
+
+
+ {renderLeftIcon()} + + {renderClear()} +
+ {renderAction()}
- {renderAction()} + {renderResultList()}
); }; diff --git a/src/search/_example/base.tsx b/src/search/_example/base.tsx index ab64bb903..d1b6e4811 100644 --- a/src/search/_example/base.tsx +++ b/src/search/_example/base.tsx @@ -1,40 +1,37 @@ -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { Search } from 'tdesign-mobile-react'; +const list = [ + 'tdesign-vue', + 'tdesign-react', + 'tdesign-miniprogram', + 'tdesign-angular', + 'tdesign-mobile-vue', + 'tdesign-mobile-react', +]; + export default function Base() { const [value, setValue] = useState(''); - const onChange = (val: string) => { - console.log('change: ', val); + const [resultList, setResultList] = useState([]); + + const onChange = useCallback((val: string, context: any) => { + console.log('onChange: ', val, context); setValue(val); - }; - const onBlur = () => { - console.log('blur'); - }; - const onClear = () => { - console.log('clear'); - }; - const onFocus = () => { - console.log('focus'); - }; + if (val) { + setResultList(list.filter((item) => item.toLowerCase().includes(val.toLowerCase()))); + } else { + setResultList([]); + } + }, []); - const onSubmit = () => { - console.log('submit'); - }; - const onActionClick = () => { - console.log('action-click'); - }; return ( -
- +
+
+ +
+
+ +
); } diff --git a/src/search/defaultProps.ts b/src/search/defaultProps.ts index a71a5fbd7..7dc60e71d 100644 --- a/src/search/defaultProps.ts +++ b/src/search/defaultProps.ts @@ -7,6 +7,7 @@ import { TdSearchProps } from './type'; export const searchDefaultProps: TdSearchProps = { action: '', center: false, + clearTrigger: 'always', clearable: true, cursorColor: '#0052d9', disabled: false, @@ -14,5 +15,6 @@ export const searchDefaultProps: TdSearchProps = { leftIcon: 'search', placeholder: '', readonly: undefined, + resultList: [], shape: 'square', }; diff --git a/src/search/search.en-US.md b/src/search/search.en-US.md index 1595d1811..1565ae90b 100644 --- a/src/search/search.en-US.md +++ b/src/search/search.en-US.md @@ -11,6 +11,7 @@ style | Object | - | CSS(Cascading Style Sheets),Typescript: `React.CSSPropert action | TNode | '' | Typescript: `string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N autocompleteOptions | Array | - | autocomplete words list。Typescript: `Array` `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 center | Boolean | false | \- | N +clearTrigger | String | always | show clear icon, clicked to clear input value。options: always / focus | N clearable | Boolean | true | \- | N cursorColor | String | #0052d9 | `0.21.2` | N disabled | Boolean | false | \- | N @@ -19,13 +20,14 @@ leftIcon | TNode | 'search' | Typescript: `string \| TNode`。[see more ts defin placeholder | String | '' | \- | N prefixIcon | TElement | - | `deprecated`。Typescript: `TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N readonly | Boolean | undefined | \- | N +resultList | Array | [] | Typescript: `Array` | N shape | String | 'square' | options: square/round | N suffixIcon | TElement | - | `deprecated`。Typescript: `TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N value | String | - | \- | N defaultValue | String | - | uncontrolled property | N onActionClick | Function | | Typescript: `({}) => void`
| N onBlur | Function | | Typescript: `(context: { value: string; e: FocusEvent }) => void`
| N -onChange | Function | | Typescript: `(value: string, context: { trigger: 'input-change' \| 'option-click'; e?: InputEvent \| MouseEvent }) => void`
| N +onChange | Function | | Typescript: `(value: string, context: { trigger: 'input-change' \| 'option-click' \| 'clear'; e?: InputEvent \| MouseEvent }) => void`
| N onClear | Function | | Typescript: `(context: { e: MouseEvent }) => void`
| N onFocus | Function | | Typescript: `(context: { value: string; e: FocusEvent }) => void`
| N onSearch | Function | | Typescript: `(context?: { value: string; trigger: 'submit' \| 'option-click' \| 'clear'; e?: InputEvent \| MouseEvent }) => void`
| N diff --git a/src/search/search.md b/src/search/search.md index 3e7db633d..6b29178d9 100644 --- a/src/search/search.md +++ b/src/search/search.md @@ -11,6 +11,7 @@ style | Object | - | 样式,TS 类型:`React.CSSProperties` | N action | TNode | '' | 自定义右侧操作按钮文字。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N autocompleteOptions | Array | - | 【讨论中】联想词列表,如果不存在或长度为 0 则不显示联想框。可以使用函数 `label` 自定义联想词为任意内容;也可使用插槽 `option` 定义联想词内容,插槽参数为 `{ option: AutocompleteOption; index: number }`。如果 `group` 值为 `true` 则表示当前项为分组标题。TS 类型:`Array` `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 center | Boolean | false | 是否居中 | N +clearTrigger | String | always | 清空图标触发方式,仅在输入框有值时有效。可选项:always / focus | N clearable | Boolean | true | 是否启用清除控件 | N cursorColor | String | #0052d9 | `0.21.2`。光标颜色 | N disabled | Boolean | false | 是否禁用 | N @@ -19,13 +20,14 @@ leftIcon | TNode | 'search' | 左侧图标。TS 类型:`string \| TNode`。[ placeholder | String | '' | 占位符 | N prefixIcon | TElement | - | 已废弃。前置图标,默认为搜索图标。值为 `null` 时则不显示。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N readonly | Boolean | undefined | 只读状态 | N +resultList | Array | [] | 预览结果列表。TS 类型:`Array` | N shape | String | 'square' | 搜索框形状。可选项:square/round | N suffixIcon | TElement | - | 已废弃。后置图标。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N value | String | - | 值 | N defaultValue | String | - | 值。非受控属性 | N onActionClick | Function | | TS 类型:`({}) => void`
点击右侧操作按钮文字时触发 | N onBlur | Function | | TS 类型:`(context: { value: string; e: FocusEvent }) => void`
失去焦点时触发 | N -onChange | Function | | TS 类型:`(value: string, context: { trigger: 'input-change' \| 'option-click'; e?: InputEvent \| MouseEvent }) => void`
搜索关键词发生变化时触发,可能场景有:搜索框内容发生变化、点击联想词 | N +onChange | Function | | TS 类型:`(value: string, context: { trigger: 'input-change' \| 'option-click' \| 'clear'; e?: InputEvent \| MouseEvent }) => void`
搜索关键词发生变化时触发,可能场景有:搜索框内容发生变化、点击联想词 | N onClear | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
点击清除时触发 | N onFocus | Function | | TS 类型:`(context: { value: string; e: FocusEvent }) => void`
获得焦点时触发 | N onSearch | Function | | TS 类型:`(context?: { value: string; trigger: 'submit' \| 'option-click' \| 'clear'; e?: InputEvent \| MouseEvent }) => void`
【讨论中】搜索触发,包含:手机键盘提交健、联想关键词点击、清空按钮点击等 | N diff --git a/src/search/type.ts b/src/search/type.ts index 83076fed8..2a6f4f36b 100644 --- a/src/search/type.ts +++ b/src/search/type.ts @@ -61,6 +61,11 @@ export interface TdSearchProps { * 只读状态 */ readonly?: boolean; + /** + * 预览结果列表 + * @default [] + */ + resultList?: Array; /** * 搜索框形状 * @default 'square' @@ -91,7 +96,10 @@ export interface TdSearchProps { */ onChange?: ( value: string, - context: { trigger: 'input-change' | 'option-click'; e?: FormEvent | MouseEvent }, + context: { + trigger: 'input-change' | 'option-click' | 'clear'; + e?: FormEvent | MouseEvent; + }, ) => void; /** * 点击清除时触发 diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index e837d004e..2a93494f8 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -77090,50 +77090,52 @@ exports[`csr snapshot test > csr test src/navbar/_example/index.tsx 1`] = `
-
@@ -78181,50 +78183,52 @@ exports[`csr snapshot test > csr test src/navbar/_example/search.tsx 1`] = `
-
@@ -93045,50 +93049,52 @@ exports[`csr snapshot test > csr test src/search/_example/action.tsx 1`] = `
-
@@ -93097,53 +93103,109 @@ exports[`csr snapshot test > csr test src/search/_example/action.tsx 1`] = ` exports[`csr snapshot test > csr test src/search/_example/base.tsx 1`] = `
-
+
+
+
+
@@ -93189,53 +93251,109 @@ exports[`csr snapshot test > csr test src/search/_example/index.tsx 1`] = `
-
+
+
+
+
@@ -93259,50 +93377,52 @@ exports[`csr snapshot test > csr test src/search/_example/index.tsx 1`] = `
-
@@ -93331,50 +93451,52 @@ exports[`csr snapshot test > csr test src/search/_example/index.tsx 1`] = `
-
@@ -93403,50 +93525,52 @@ exports[`csr snapshot test > csr test src/search/_example/index.tsx 1`] = `
-
@@ -93461,50 +93585,52 @@ exports[`csr snapshot test > csr test src/search/_example/other.tsx 1`] = `
-
@@ -93516,50 +93642,52 @@ exports[`csr snapshot test > csr test src/search/_example/shape.tsx 1`] = `
-
@@ -133423,11 +133551,11 @@ exports[`ssr snapshot test > ssr test src/navbar/_example/custom-color.tsx 1`] = exports[`ssr snapshot test > ssr test src/navbar/_example/img.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/navbar/_example/index.tsx 1`] = `"
Navbar 导航条

Navbar 导航栏

用于不同页面之间切换或者跳转,位于内容区的上方,系统状态栏的下方。

01 组件类型

基础导航栏

标题文字
标题文字
标题文字

带搜索导航栏

带图片导航栏

02 组件样式

标题对齐

标题居中
标题左对齐

标题尺寸

标题文字
返回
大尺寸标题

自定义颜色

标题文字
"`; +exports[`ssr snapshot test > ssr test src/navbar/_example/index.tsx 1`] = `"
Navbar 导航条

Navbar 导航栏

用于不同页面之间切换或者跳转,位于内容区的上方,系统状态栏的下方。

01 组件类型

基础导航栏

标题文字
标题文字
标题文字

带搜索导航栏

带图片导航栏

02 组件样式

标题对齐

标题居中
标题左对齐

标题尺寸

标题文字
返回
大尺寸标题

自定义颜色

标题文字
"`; exports[`ssr snapshot test > ssr test src/navbar/_example/left-title.tsx 1`] = `"
标题居中
标题左对齐
"`; -exports[`ssr snapshot test > ssr test src/navbar/_example/search.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/navbar/_example/search.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/navbar/_example/size.tsx 1`] = `"
标题文字
返回
大尺寸标题
"`; @@ -133557,15 +133685,15 @@ exports[`ssr snapshot test > ssr test src/result/_example/index.tsx 1`] = `" ssr test src/result/_example/theme.tsx 1`] = `"
成功状态
描述文字
失败状态
描述文字
警示状态
描述文字
默认状态
描述文字
"`; -exports[`ssr snapshot test > ssr test src/search/_example/action.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/search/_example/action.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/search/_example/base.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/search/_example/base.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/search/_example/index.tsx 1`] = `"

Search 搜索框

用于用户输入搜索信息,并进行页面内容搜索。

01 组件类型

基础搜索框

输入值后显示取消按钮

02 组件样式

搜索框形状

03 组件状态

默认状态其他对齐方式

"`; +exports[`ssr snapshot test > ssr test src/search/_example/index.tsx 1`] = `"

Search 搜索框

用于用户输入搜索信息,并进行页面内容搜索。

01 组件类型

基础搜索框

输入值后显示取消按钮

02 组件样式

搜索框形状

03 组件状态

默认状态其他对齐方式

"`; -exports[`ssr snapshot test > ssr test src/search/_example/other.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/search/_example/other.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/search/_example/shape.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/search/_example/shape.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/side-bar/_example/base.tsx 1`] = `""`; diff --git a/test/snap/__snapshots__/ssr.test.jsx.snap b/test/snap/__snapshots__/ssr.test.jsx.snap index 1b07755e1..0ed2fae58 100644 --- a/test/snap/__snapshots__/ssr.test.jsx.snap +++ b/test/snap/__snapshots__/ssr.test.jsx.snap @@ -414,11 +414,11 @@ exports[`ssr snapshot test > ssr test src/navbar/_example/custom-color.tsx 1`] = exports[`ssr snapshot test > ssr test src/navbar/_example/img.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/navbar/_example/index.tsx 1`] = `"
Navbar 导航条

Navbar 导航栏

用于不同页面之间切换或者跳转,位于内容区的上方,系统状态栏的下方。

01 组件类型

基础导航栏

标题文字
标题文字
标题文字

带搜索导航栏

带图片导航栏

02 组件样式

标题对齐

标题居中
标题左对齐

标题尺寸

标题文字
返回
大尺寸标题

自定义颜色

标题文字
"`; +exports[`ssr snapshot test > ssr test src/navbar/_example/index.tsx 1`] = `"
Navbar 导航条

Navbar 导航栏

用于不同页面之间切换或者跳转,位于内容区的上方,系统状态栏的下方。

01 组件类型

基础导航栏

标题文字
标题文字
标题文字

带搜索导航栏

带图片导航栏

02 组件样式

标题对齐

标题居中
标题左对齐

标题尺寸

标题文字
返回
大尺寸标题

自定义颜色

标题文字
"`; exports[`ssr snapshot test > ssr test src/navbar/_example/left-title.tsx 1`] = `"
标题居中
标题左对齐
"`; -exports[`ssr snapshot test > ssr test src/navbar/_example/search.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/navbar/_example/search.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/navbar/_example/size.tsx 1`] = `"
标题文字
返回
大尺寸标题
"`; @@ -548,15 +548,15 @@ exports[`ssr snapshot test > ssr test src/result/_example/index.tsx 1`] = `" ssr test src/result/_example/theme.tsx 1`] = `"
成功状态
描述文字
失败状态
描述文字
警示状态
描述文字
默认状态
描述文字
"`; -exports[`ssr snapshot test > ssr test src/search/_example/action.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/search/_example/action.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/search/_example/base.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/search/_example/base.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/search/_example/index.tsx 1`] = `"

Search 搜索框

用于用户输入搜索信息,并进行页面内容搜索。

01 组件类型

基础搜索框

输入值后显示取消按钮

02 组件样式

搜索框形状

03 组件状态

默认状态其他对齐方式

"`; +exports[`ssr snapshot test > ssr test src/search/_example/index.tsx 1`] = `"

Search 搜索框

用于用户输入搜索信息,并进行页面内容搜索。

01 组件类型

基础搜索框

输入值后显示取消按钮

02 组件样式

搜索框形状

03 组件状态

默认状态其他对齐方式

"`; -exports[`ssr snapshot test > ssr test src/search/_example/other.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/search/_example/other.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/search/_example/shape.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/search/_example/shape.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/side-bar/_example/base.tsx 1`] = `""`;