Skip to content

Commit

Permalink
Add a Search component
Browse files Browse the repository at this point in the history
  • Loading branch information
ickg5 committed Aug 25, 2021
1 parent 39357a3 commit 2acc5cb
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/core/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@import "./password-input";
@import "./radio";
@import "./rate";
@import "./search";
@import "./slider";
@import "./stepper";
@import "./switch";
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export { default as NumberKeyboard } from "./number-keyboard"
export { default as PasswordInput } from "./password-input"
export { default as Radio } from "./radio"
export { default as Rate } from "./rate"
export { default as Search } from "./search"
export { default as Slider } from "./slider"
export { default as Stepper } from "./stepper"
export { default as Switch } from "./switch"
Expand Down
156 changes: 156 additions & 0 deletions packages/core/src/search/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Search 搜索

### 介绍

用于搜索场景的输入框组件。

### 引入

```tsx
import { Search } from "@taroify/core"
```

## 代码演示

### 基础用法

`value` 用于控制搜索框中的文字,`onChange` 可以获得改变的值。

```tsx
function BasicSearch() {
const [value, setValue] = useState("")
return (
<Search
value={value}
placeholder="请输入搜索关键词"
onChange={(e) => setValue(e.detail.value)}
/>
)
}
```

### 事件监听

Search 组件提供了 `onSearch``onCancel` 事件,`onSearch` 事件在点击键盘上的搜索/回车按钮后触发,`onCancel` 事件在点击搜索框右侧取消按钮时触发。

```tsx
function SearchWithEvents() {
const [value, setValue] = useState("")
const [open, setOpen] = useState(false)

return (
<>
<Toast open={open} onClose={() => setOpen(false)}>
取消
</Toast>
<Search
value={value}
placeholder="请输入搜索关键词"
action
onChange={(e) => setValue(e.detail.value)}
onCancel={() => setOpen(true)}
/>
</>
)
}

```

### 搜索框内容对齐

通过 `inputAlign` 属性设置搜索框内容的对齐方式,可选值为 `center``right`

```tsx
function InputCenterSearch() {
const [value, setValue] = useState("")
return (
<Search
value={value}
placeholder="请输入搜索关键词"
inputAlign="center"
onChange={(e) => setValue(e.detail.value)}
/>
)
}
```

### 禁用搜索框

通过 `disabled` 属性禁用搜索框。

```tsx
<Search disabled placeholder="请输入搜索关键词" />
```

### 自定义背景色

通过 `className` 属性可以设置搜索框外部的背景色,通过 `shape` 属性设置搜索框的形状,可选值为 `round`

```tsx
<Search className="background" shape="round" disabled placeholder="请输入搜索关键词" />
```

```scss
.background {
background: #4fc08d;
}
```

### 自定义按钮

使用 `action` 插槽可以自定义右侧按钮的内容。使用插槽后,`cancel` 事件将不再触发。

```tsx
function CustomSearch() {
const [value, setValue] = useState("")
const [open, setOpen] = useState(false)
return (
<>
<Toast open={open} onClose={() => setOpen(false)}>
搜索
</Toast>
<Search
value={value}
label="地址"
placeholder="请输入搜索关键词"
action={<View onClick={() => setOpen(true)}>搜索</View>}
onChange={(e) => setValue(e.detail.value)}
/>
</>
)
}
```

## API

### Props

| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| label | 搜索框左侧文本 | _string_ | - |
| shape | 搜索框形状,可选值为 `round` | _string_ | `square` |
| maxlength | 输入的最大字符数 | _number \| string_ | - |
| placeholder | 占位提示文字 | _string_ | - |
| clearable | 是否启用清除图标,点击清除图标后会清空输入框 | _boolean_ | `true` |
| clearIcon| 清除图标 | _string_ | `<Clear />` |
| clearTrigger | 显示清除图标的时机,`always` 表示输入框不为空时展示,<br>`focus` 表示输入框聚焦且不为空时展示 | _string_ | `focus` |
| autofocus | 是否自动聚焦,iOS 系统不支持该属性 | _boolean_ | `false` |
| action | 是否在搜索框右侧显示取消按钮 | _boolean \| ReactNode_ | `false` |
| disabled | 是否禁用输入框 | _boolean_ | `false` |
| readonly | 是否将输入框设为只读状态,只读状态下无法输入内容 | _boolean_ | `false` |
| error | 是否将输入内容标红 | _boolean_ | `false` |
| message | 底部错误提示文案,为空时不展示 | _string_ | - |
| inputAlign | 输入框内容对齐方式,可选值为 `center` `right` | _string_ | `left` |
| icon | 输入框左侧图标 | _ReactNode_ | `<Search />` |
| rightIcon | 输入框右侧图标 | _ReactNode_ | - |

### Events

| 事件名 | 说明 | 回调参数 |
| ------------------ | -------------------- | ------------------------------ |
| onSearch | 确定搜索时触发 | _event: BaseEventOrig<InputProps.inputValueEventDetail>_ |
| onChange | 输入框内容变化时触发 | _event: BaseEventOrig<InputProps.inputEventDetail>_ |
| onFocus | 输入框获得焦点时触发 | _event: BaseEventOrig<InputProps.inputForceEventDetail>_ |
| onBlur | 输入框失去焦点时触发 | _event: BaseEventOrig<InputProps.inputForceEventDetail>_ |
| onClear | 点击清除按钮后触发 | _event: event: ITouchEvent_ |
| onCancel | 点击取消按钮时触发 | _event: event: ITouchEvent_ |
16 changes: 16 additions & 0 deletions packages/core/src/search/_variables.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@import "../styles/prefix";
@import "../styles/ellipsis";
@import "../styles/hairline";
@import "../styles/variables";

$search-padding: 10px * $hd $padding-sm;
$search-background-color: $white;
$search-content-background-color: $gray-1;
$search-input-height: 34px * $hd;
$search-label-padding: 0 5px * $hd;
$search-label-color: $text-color;
$search-label-font-size: $font-size-md;
$search-left-icon-color: $gray-6;
$search-action-padding: 0 $padding-xs;
$search-action-text-color: $text-color;
$search-action-font-size: $font-size-md;
4 changes: 4 additions & 0 deletions packages/core/src/search/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// style dependencies
@import "../field";
//
@import "./search";
1 change: 1 addition & 0 deletions packages/core/src/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./search"
64 changes: 64 additions & 0 deletions packages/core/src/search/search.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@import "./variables";

.#{$component-prefix}search {
display: flex;
align-items: center;
box-sizing: border-box;
padding: $search-padding;
background-color: $search-background-color;

&__content {
display: flex;
flex: 1;
padding-left: $padding-sm;
background-color: $search-content-background-color;
border-radius: $border-radius-sm;

&--round {
border-radius: $border-radius-max;
}
}

&__label {
padding: $search-label-padding;
color: $search-label-color;
font-size: $search-label-font-size;
line-height: $search-input-height;
}

&__field {
flex: 1;
padding: 5px * $hd $padding-xs 5px * $hd 0;
background-color: transparent;

.#{$component-prefix}field__icon {
color: $search-left-icon-color;
}
}

&--action {
padding-right: 0;
}

input {
&::-webkit-search-decoration,
&::-webkit-search-cancel-button,
&::-webkit-search-results-button,
&::-webkit-search-results-decoration {
display: none;
}
}

&__action {
padding: $search-action-padding;
color: $search-action-text-color;
font-size: $search-action-font-size;
line-height: $search-input-height;
cursor: pointer;
user-select: none;

&:active {
background-color: $active-color;
}
}
}
135 changes: 135 additions & 0 deletions packages/core/src/search/search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Search as SearchIcon } from "@taroify/icons"
import { ITouchEvent, View } from "@tarojs/components"
import { BaseEventOrig } from "@tarojs/components/types/common"
import { InputProps } from "@tarojs/components/types/Input"
import classNames from "classnames"
import * as _ from "lodash"
import * as React from "react"
import { ReactNode } from "react"
import Field, { FieldClearTrigger, FieldClearTriggerString } from "../field"
import { FieldInputAlign, FieldInputAlignString } from "../field/field.shared"
import { prefixClassname } from "../styles"
import { preventDefault } from "../utils/dom/event"

enum SearchShape {
Square = "square",
Round = "round",
}

type SearchShapeString = "square" | "round"

interface SearchProps {
className?: string
value?: string
label?: ReactNode
shape?: SearchShape | SearchShapeString
maxlength?: number
placeholder?: string
placeholderClassName?: string
clearable?: boolean
clearIcon?: ReactNode
clearTrigger?: FieldClearTrigger | FieldClearTriggerString
inputAlign?: FieldInputAlign | FieldInputAlignString
autoFocus?: boolean
disabled?: boolean
readonly?: boolean
error?: boolean
message?: ReactNode
action?: boolean | ReactNode

onClear?(event: ITouchEvent): void

onCancel?(event: ITouchEvent): void

onSearch?(event: BaseEventOrig<InputProps.inputValueEventDetail>): void

onChange?(event: BaseEventOrig<InputProps.inputEventDetail>): void

onFocus?(event: BaseEventOrig<InputProps.inputForceEventDetail>): void

onBlur?(event: BaseEventOrig<InputProps.inputValueEventDetail>): void
}

function Search(props: SearchProps) {
const {
className,
value,
label,
shape = SearchShape.Square,
maxlength,
placeholder,
placeholderClassName,
clearable = true,
clearIcon,
clearTrigger,
inputAlign,
disabled,
readonly,
error,
message,
action,
onClear,
onCancel,
onSearch,
onChange,
onFocus,
onBlur,
} = props

function handleSearch(event: BaseEventOrig<InputProps.inputValueEventDetail>) {
preventDefault(event)
onSearch?.(event)
}

return (
<View
className={classNames(
prefixClassname("search"),
{
[prefixClassname("search--action")]: action,
},
className,
)}
>
<View
className={classNames(
prefixClassname("search__content"),
shape && prefixClassname(`search__content--${shape}`),
)}
>
{label && <View className={prefixClassname("search__label")} children={label} />}
<Field
className={prefixClassname("search__field")}
placeholderClassName={placeholderClassName}
value={value}
icon={<SearchIcon />}
maxlength={maxlength}
placeholder={placeholder}
clearable={clearable}
clearIcon={clearIcon}
clearTrigger={clearTrigger}
inputAlign={inputAlign}
disabled={disabled}
readonly={readonly}
error={error}
message={message}
confirmType="search"
onConfirm={handleSearch}
onClear={onClear}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
/>
</View>
{action && (
<View
className={prefixClassname("search__action")}
children={_.isBoolean(action) ? "取消" : action}
onClick={_.isBoolean(action) ? onCancel : undefined}
/>
)}
</View>
)
}

export default Search
Loading

0 comments on commit 2acc5cb

Please sign in to comment.