2020-03-31 13:47:59 +08:00

366 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { polyfill } from 'react-lifecycles-compat';
import Input from '../input';
import Select from '../select';
import Button from '../button';
import Icon from '../icon';
import { obj, func, KEYCODE } from '../util';
import zhCN from '../locale/zh-cn';
const Group = Input.Group;
const AutoComplete = Select.AutoComplete;
/**
* Search
* @description 输入框部分继承 Select.AutoComplete 的能力可以直接用AutoComplete 的 api
*/
class Search extends React.Component {
static propTypes = {
/**
* 样式前缀
*/
prefix: PropTypes.string,
/**
* 形状
*/
shape: PropTypes.oneOf(['normal', 'simple']),
/**
* 类型 shape=normal: primary/secondary; shape=simple: normal/dark;
*/
type: PropTypes.oneOf(['primary', 'secondary', 'normal', 'dark']),
/**
* 大小
* @enumdesc '大', '小'
*/
size: PropTypes.oneOf(['large', 'medium']),
/**
* 搜索框默认值
*/
defaultValue: PropTypes.string,
/**
* 搜索框数值
*/
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* 输入关键字时的回掉
* @param {Object} value 输入值
*/
onChange: PropTypes.func,
/**
* 点击搜索按钮触发的回调
* @param {String} value 输入值
* @param {String} filterValue 选项值
*/
onSearch: PropTypes.func,
/**
* 选择器默认值
*/
defaultFilterValue: PropTypes.string,
/**
* 选择器
*/
filter: PropTypes.array,
/**
* 选择器值
*/
filterValue: PropTypes.string,
/**
* 选择器发生变化时回调
* @param {Object} filter value
*/
onFilterChange: PropTypes.func,
/**
* 搜索框下拉联想列表
*/
dataSource: PropTypes.array,
/**
* 默认提示
*/
placeholder: PropTypes.string,
/**
* button 的内容
*/
searchText: PropTypes.node,
/**
* 自定义样式
*/
style: PropTypes.object,
/**
* 样式名称
*/
className: PropTypes.string,
/**
* 选择器的props
*/
filterProps: PropTypes.object,
/**
* 按钮的额外属性
*/
buttonProps: PropTypes.object,
/**
* 自定义渲染的的下拉框
*/
popupContent: PropTypes.node,
/**
* 是否跟随滚动
*/
followTrigger: PropTypes.bool,
/**
* 自定义渲染的的下拉框
*/
visible: PropTypes.bool,
/**
* 是否显示清除按钮
*/
hasClear: PropTypes.bool,
/**
* 是否显示搜索按钮
*/
hasIcon: PropTypes.bool,
/**
* 是否禁用
*/
disabled: PropTypes.bool,
locale: PropTypes.object,
rtl: PropTypes.bool,
};
static defaultProps = {
prefix: 'next-',
shape: 'normal',
type: 'normal',
size: 'medium',
hasIcon: true,
filter: [],
locale: zhCN.Search,
buttonProps: {},
onChange: func.noop,
onSearch: func.noop,
onFilterChange: func.noop,
hasClear: false,
disabled: false,
};
constructor(props) {
super(props);
const value = 'value' in props ? props.value : props.defaultValue;
const filterValue =
'filterValue' in props
? props.filterValue
: props.defaultFilterValue;
this.state = {
value: typeof value === 'undefined' ? '' : value,
filterValue,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
const nextState = {};
if ('value' in nextProps && nextProps.value !== prevState.value) {
const value = nextProps.value;
nextState.value =
value === undefined || value === null ? '' : nextProps.value;
}
if (
'filterValue' in nextProps &&
nextProps.filterValue !== prevState.filterValue
) {
const filterValue = nextProps.filterValue;
nextState.filterValue =
filterValue === undefined ? '' : filterValue;
}
if (Object.keys(nextState).length > 0) {
return nextState;
}
return null;
}
onChange = (value, ...argv) => {
if (!('value' in this.props)) {
this.setState({ value });
}
this.props.onChange(value, ...argv);
};
onSearch = () => {
if (this.props.disabled) {
return;
}
this.props.onSearch(this.state.value, this.state.filterValue);
};
onFilterChange = filterValue => {
if (!('filterValue' in this.props)) {
this.setState({ filterValue });
}
this.props.onFilterChange(filterValue);
};
onKeyDown = e => {
if (this.props.disabled) {
return;
}
if (e.keyCode !== KEYCODE.ENTER) {
return;
}
this.onSearch();
};
render() {
const {
shape,
filter,
hasIcon,
disabled,
placeholder,
type,
className,
style,
size,
prefix,
searchText,
dataSource,
filterProps,
buttonProps,
popupContent,
followTrigger,
hasClear,
visible,
locale,
rtl,
...others
} = this.props;
const cls = classNames({
[`${prefix}search`]: true,
[`${prefix}search-${shape}`]: true,
[`${prefix}${type}`]: type,
[`${prefix}${size}`]: size,
[className]: !!className,
});
let searchIcon = null,
filterSelect = null,
searchBtn = null;
if (shape === 'simple') {
const cls = classNames({
[`${prefix}search-icon`]: true,
[buttonProps.className]: !!buttonProps.className,
});
hasIcon &&
(searchIcon = (
<Icon
type="search"
tabIndex="0"
role="button"
aria-disabled={disabled}
aria-label={locale.buttonText}
{...buttonProps}
className={cls}
onClick={this.onSearch}
onKeyDown={this.onKeyDown}
/>
));
} else {
const cls = classNames({
[`${prefix}search-btn`]: true,
[buttonProps.className]: !!buttonProps.className,
});
searchBtn = (
<Button
tabIndex="0"
aria-disabled={disabled}
aria-label={locale.buttonText}
className={cls}
disabled={disabled}
{...buttonProps}
onClick={this.onSearch}
onKeyDown={this.onKeyDown}
>
{hasIcon ? <Icon type="search" /> : null}
{searchText ? (
<span className={`${prefix}search-btn-text`}>
{searchText}
</span>
) : null}
</Button>
);
}
if (filter.length > 0) {
filterSelect = (
<Select
{...filterProps}
followTrigger={followTrigger}
hasBorder={false}
dataSource={filter}
size={size}
disabled={disabled}
value={this.state.filterValue}
onChange={this.onFilterChange}
/>
);
}
const othersAttributes = obj.pickOthers(Search.propTypes, others);
if (visible !== undefined) {
// 受控属性 visible 不能直接写在组件上
othersAttributes.visible = Boolean(visible);
}
const dataAttr = obj.pickAttrsWith(others, 'data-');
const left = (
<Group
addonBefore={filterSelect}
className={`${prefix}search-left`}
addonBeforeClassName={`${prefix}search-left-addon`}
>
<AutoComplete
aria-label={locale.buttonText}
{...othersAttributes}
followTrigger={followTrigger}
role="searchbox"
hasClear={hasClear}
className={`${prefix}search-input`}
size={size}
placeholder={placeholder}
dataSource={dataSource}
innerAfter={searchIcon}
onPressEnter={this.onSearch}
value={this.state.value}
onChange={this.onChange}
popupContent={popupContent}
disabled={disabled}
/>
</Group>
);
return (
<span
className={cls}
style={style}
{...dataAttr}
dir={rtl ? 'rtl' : undefined}
>
{searchBtn ? (
<Group addonAfter={searchBtn}>{left}</Group>
) : (
left
)}
</span>
);
}
}
export default polyfill(Search);