196 lines
5.2 KiB
JavaScript
196 lines
5.2 KiB
JavaScript
|
|
import React, { Component } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import clazz from 'classname';
|
|
|
|
import classes from './style.css';
|
|
|
|
export default class UserList extends Component {
|
|
static propTypes = {
|
|
max: PropTypes.number.isRequired,
|
|
searching: PropTypes.string.isRequired,
|
|
search: PropTypes.func.isRequired,
|
|
getList: PropTypes.func.isRequired,
|
|
onChange: PropTypes.func.isRequired,
|
|
};
|
|
|
|
static defaultProps = {
|
|
max: 20,
|
|
};
|
|
|
|
state = {
|
|
selected: [],
|
|
active: '',
|
|
};
|
|
|
|
highlight(offset) {
|
|
var scroller = this.refs.list;
|
|
var users = Array.from(scroller.querySelectorAll('li[data-userid]'));
|
|
var index = users.findIndex(e => e.classList.contains(classes.active));
|
|
|
|
if (index > -1) {
|
|
users[index].classList.remove(classes.active);
|
|
}
|
|
|
|
index += offset;
|
|
|
|
if (index < 0) {
|
|
// Fallback to the last element
|
|
index = users.length - 1;
|
|
} else if (index > users.length - 1) {
|
|
// Fallback to the 1th element
|
|
index = 0;
|
|
}
|
|
|
|
var active = users[index];
|
|
|
|
if (active) {
|
|
// Keep active item always in the viewport
|
|
active.classList.add(classes.active);
|
|
scroller.scrollTop = active.offsetTop + active.offsetHeight - scroller.offsetHeight;
|
|
}
|
|
}
|
|
|
|
navigation(e) {
|
|
var keyCode = e.keyCode;
|
|
var offset = {
|
|
// Up
|
|
'38': -1,
|
|
'40': 1,
|
|
}[keyCode];
|
|
|
|
if (offset) {
|
|
this.highlight(offset);
|
|
}
|
|
|
|
if (keyCode !== 13) {
|
|
return;
|
|
}
|
|
|
|
var active = this.refs.list.querySelector(`.${classes.active}`);
|
|
|
|
if (active) {
|
|
let userid = active.dataset.userid;
|
|
|
|
if (!this.state.selected.includes(userid)) {
|
|
// Add
|
|
this.addSelected(userid, userid);
|
|
} else {
|
|
// Remove
|
|
this.removeSelected(userid, userid);
|
|
}
|
|
setTimeout(() => this.props.onChange(this.state.selected));
|
|
}
|
|
}
|
|
|
|
timer;
|
|
|
|
search(text) {
|
|
clearTimeout(this.timer);
|
|
|
|
this.timer = setTimeout(() => {
|
|
this.props.search(text);
|
|
}, 300);
|
|
}
|
|
|
|
addSelected(userid, active = this.state.active) {
|
|
var selected = [
|
|
userid,
|
|
...this.state.selected,
|
|
];
|
|
var max = this.props.max;
|
|
|
|
if (max > 0) {
|
|
selected = selected.slice(0, this.props.max);
|
|
}
|
|
|
|
this.setState({
|
|
active,
|
|
selected,
|
|
});
|
|
setTimeout(() => this.props.onChange(this.state.selected));
|
|
}
|
|
|
|
removeSelected(userid, active = this.state.active) {
|
|
var selected = this.state.selected;
|
|
var index = selected.indexOf(userid);
|
|
|
|
this.setState({
|
|
active,
|
|
selected: [
|
|
...selected.slice(0, index),
|
|
...selected.slice(index + 1, selected.length)
|
|
]
|
|
});
|
|
setTimeout(() => this.props.onChange(this.state.selected));
|
|
}
|
|
|
|
toggleSelected(userid) {
|
|
if (!this.state.selected.includes(userid)) {
|
|
// Add
|
|
this.addSelected(userid);
|
|
} else {
|
|
// Remove
|
|
this.removeSelected(userid);
|
|
}
|
|
|
|
setTimeout(() => this.refs.input.focus());
|
|
}
|
|
|
|
renderList() {
|
|
var { searching, getList } = this.props;
|
|
var list = getList();
|
|
|
|
if (searching && list.length === 0) {
|
|
return (
|
|
<li className={classes.notfound}>
|
|
<img src="assets/images/crash.png" />
|
|
<h3>没有搜索到 '{searching}'</h3>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
return list.map((e, index) => {
|
|
return (
|
|
<li
|
|
className={clazz({
|
|
[classes.selected]: this.state.selected.includes(e.UserName),
|
|
[classes.active]: this.state.active === e.UserName,
|
|
})}
|
|
data-userid={e.UserName}
|
|
key={index}
|
|
onClick={ev => this.toggleSelected(e.UserName)}>
|
|
<img
|
|
className={classes.avatar}
|
|
src={e.HeadImgUrl} />
|
|
<span
|
|
className={classes.username}
|
|
dangerouslySetInnerHTML={{__html: e.RemarkName || e.NickName}} />
|
|
|
|
<i className="icon-ion-android-done-all" />
|
|
</li>
|
|
);
|
|
});
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className={classes.container}>
|
|
<input
|
|
autoFocus={true}
|
|
onKeyUp={e => this.navigation(e)}
|
|
onInput={e => this.search(e.target.value)}
|
|
placeholder="输入需要搜索的对象"
|
|
ref="input"
|
|
type="text" />
|
|
|
|
<ul
|
|
className={classes.list}
|
|
ref="list">
|
|
{this.renderList()}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
}
|