优秀的编程知识分享平台

网站首页 > 技术文章 正文

antd 搜索历史记录的搜索框(antd怎么清除select选中的项)

nanyue 2024-07-31 12:19:39 技术文章 7 ℃

react:16.13.1 antd:3.26.17

效果图

源码

// SearchBox/index.jsx
/**
 * @desc 自定义带历史记录的搜索框
 * @author darcrand
 * @date 2020-09-07
 */
import React, { Component } from "react"
import { Input, Button } from "antd"
import styles from "./styles.module.less"

const ADD_DELAY = 500

export default class SearchBox extends Component {
  /**
   * @prop {string?} value 绑定的值,如果传入值,则视为完成受控
   * @prop {string[]?} defaultOptions 默认历史搜索记录
   * @prop {function?} onChange 输入内容|选择历史记录项时触发
   * @prop {function?} onSearch 按下回车|点击搜索按钮|选择历史记录项时触发
   * @prop {function?} onOptionsUpdate 历史记录列表更新时触发
   *
   * @prop {number?} maxLength 文本框最大内容长度
   * @prop {string?} placeholder 提示文本
   * @prop {boolean?} allowClear 是否添加清空按钮
   *
   * @prop {string?} width 容器宽度
   * @prop {number?} zIndex 历史记录下拉组件的层级
   * @prop {number?} maxOptionsLength 最大历史记录数量
   * @prop {boolean?} loading 是否在加载中(防止高频操作)
   */
  static defaultProps = {
    value: undefined,
    defaultOptions: [],
    onChange: (value = "") => {},
    onSearch: (value = "") => {},
    onOptionsUpdate: (options = []) => {},

    maxLength: 30,
    placeholder: "输入关键字",
    allowClear: false,

    width: "300px",
    zIndex: 10,
    maxOptionsLength: 10,
    loading: false,
  }

  state = {
    controlled: this.props.value !== undefined && typeof this.props.value === "string",

    value: "",
    options: this.props.defaultOptions.slice(),
    visibleOptions: false,
  }

  refInput = React.createRef()

  onInputChange = (event = {}) => {
    const value = event.target.value
    if (this.state.controlled) {
      this.props.onChange && this.props.onChange(value)
    } else {
      this.setState({ value })
    }
  }

  onHitEnter = (event = {}) => {
    const { keyCode, which } = event

    if (keyCode === 13 || which === 13) {
      this.addOptions(this.useValue)
      this.props.onSearch && this.props.onSearch(this.useValue)
      this.refInput.current.blur()
    }
  }

  onSearchButtonClick = () => {
    this.addOptions(this.useValue)
    if (this.props.onSearch) {
      this.props.onSearch(this.useValue)
    }
  }

  addOptions = (value = "") => {
    if (!value.trim()) {
      return
    }

    let { options = [] } = this.state
    const included = options.some((v) => v === value)
    if (!included) {
      const t = setTimeout(() => {
        clearTimeout(t)
        options.unshift(value)
        options = options.slice(0, Math.max(3, this.props.maxOptionsLength))
        this.setState({ options }, () => {
          if (this.props.onOptionsUpdate) {
            this.props.onOptionsUpdate(options)
          }
        })
      }, ADD_DELAY)
    }
  }

  onOptionClick = (value = "") => {
    if (this.state.controlled) {
      this.props.onChange && this.props.onChange(value)
    } else {
      this.setState({ value })
    }
    this.props.onSearch && this.props.onSearch(value)
  }

  get useValue() {
    return this.state.controlled ? this.props.value : this.state.value
  }

  render() {
    const { maxLength, placeholder, allowClear, width, zIndex, loading } = this.props
    const { options = [], visibleOptions } = this.state

    return (
      <div className={styles.container} style={{ zIndex, width }}>
        <Input
          ref={this.refInput}
          className={styles.input}
          type="text"
          maxLength={maxLength}
          placeholder={placeholder}
          allowClear={allowClear}
          value={this.useValue}
          onChange={this.onInputChange}
          onKeyUp={this.onHitEnter}
          onFocus={() => this.setState({ visibleOptions: true })}
          onBlur={() => this.setState({ visibleOptions: false })}
        />
        <Button
          icon={loading ? "loading" : "search"}
          type="primary"
          className={styles.search_btn}
          onClick={this.onSearchButtonClick}
        />

        <div className={loading ? styles.loading_active : styles.loading}></div>

        <ul className={options.length > 0 && visibleOptions ? styles.options_visible : styles.options}>
          {options.map((opt) => (
            <li key={opt} className={styles.option_item} onClick={() => this.onOptionClick(opt)} title={opt}>
              {opt}
            </li>
          ))}
        </ul>
      </div>
    )
  }
}
// SearchBox/styles.module.less

@item-height: 40px;

.container {
  position: relative;
}

.input {
  width: 200px;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.search_btn {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.options {
  position: absolute;
  z-index: inherit;
  top: 100%;
  left: 0;
  list-style: none;
  padding-left: 0;
  padding-top: 10px;
  margin: 0;
  max-height: 5 * @item-height + 10px;
  background-color: #fff;
  box-shadow: 0 4px 4px #dfdfdf;
  overflow-y: auto;

  transition: all 0.25s ease-in-out 0.2s;
  visibility: hidden;
  opacity: 0;
}

.options_visible {
  &:extend(.options);
  visibility: visible;
  opacity: 1;
}

.option_item {
  padding: 10px;
  height: @item-height;
  box-sizing: border-box;
  cursor: pointer;
  color: #333;

  transition: all 0.2s;

  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;

  &:hover {
    background-color: #eee;
  }
}

Tags:

最近发表
标签列表