src/material/PersonPicker.js
import React from 'react';
import PropTypes from 'prop-types';
import AutoComplete from 'material-ui/AutoComplete';
import MenuItem from 'material-ui/MenuItem';
import Chip from 'material-ui/Chip';
import Avatar from 'material-ui/Avatar';
import TextFieldLabel from 'material-ui/TextField/TextFieldLabel';
const getStyles = (props, context, state) => {
const {
baseTheme,
textField: {
floatingLabelColor,
disabledTextColor,
backgroundColor,
},
} = context.muiTheme;
const styles = {
root: {
fontSize: 16,
lineHeight: '24px',
width: props.fullWidth ? '100%' : 256,
height: props.floatingLabelText ? 72 : 48,
display: 'inline-block',
position: 'relative',
backgroundColor: backgroundColor,
fontFamily: baseTheme.fontFamily,
cursor: props.disabled ? 'not-allowed' : 'auto',
},
floatingLabel: {
color: props.disabled ? disabledTextColor : floatingLabelColor,
pointerEvents: 'none',
top: 38,
},
floatingLabelFocusStyle: {
transform: 'scale(0.75) translate(0, -36px)',
},
};
return styles;
};
/**
* @desc
* [AutoComplete](http://www.material-ui.com/#/components/auto-complete) search for person.
* Display selected as a [Chip](http://www.material-ui.com/#/components/chip).
*
* @example
* <PersonPicker
* floatingLabelText="Contact"
* floatingLabelFiexed={true}
* value={{key: 'billyga@ntnu.no', text: 'Billy Gates', avatar: 'https://some.url/to/picture'}}
* onChange={this.handleChange}
* onSearch={this.handleSearch}
* />
*/
class PersonPicker extends React.Component {
/**
* @type {Object}
* @property {boolean} fullWidth
* @property {boolean} disabled
* @property {Object} wrapStyle
* @property {string} floatingLabelText
* @property {boolean} floatingLabelFixed
* @property {{key: string, text: string, avatar: ?string}} value Set a person
* @property {function(selected: {key: string, text: string, avatar: ?string})} onChange Callback when a person is selected.
* @property {function(searchText: string): Promise<Object[]>} onSearch List of {key: string, text: string, avatar: ?string} objects.
*/
static propTypes = {
fullWidth: PropTypes.bool,
disabled: PropTypes.bool,
wrapStyle: PropTypes.object,
floatingLabelText: PropTypes.string.isRequired,
floatingLabelFixed: PropTypes.bool,
value: PropTypes.object,
onChange: PropTypes.func,
onSearch: PropTypes.func.isRequired,
};
/**
* @type {Object}
*/
static contextTypes = {
muiTheme: PropTypes.object.isRequired,
};
/**
* @type {Object}
*/
state = {
searchText: '',
selected: this.props.value && this.props.value.text ? this.props.value : null,
dataSource: [],
};
componentWillReceiveProps(nextProps) {
const old = this.props.value && this.props.value.key;
const next = nextProps.value && nextProps.value.key;
if (old !== next) {
this.setState({
selected: nextProps.value,
});
}
}
/**
* @type {function}
*/
onChangeCallback = (item) => {
if (this.props.onChange) {
if (item) {
this.props.onChange({key: item.key, text: item.text, avatar: item.avatar});
}
else {
this.props.onChange(undefined);
}
}
}
/**
* @type {function}
*/
handleNewRequest = (search, index) => {
if (index !== -1) {
this.setState({
selected: search,
dataSource: [],
searchText: '',
});
this.onChangeCallback(search);
}
}
/**
* @type {function}
*/
handleUpdateInput = (text) => {
const state = { searchText: text };
if (text.length > 2) {
this.props.onSearch(text).then((result) => {
this.setState({ dataSource: result.map((item) => {
return {
key: item.key,
text: item.text,
avatar: item.avatar,
value: (<MenuItem key={item.key} value={item.key} primaryText={item.text} leftIcon={item.avatar && <Avatar src={item.avatar}/>}/>),
};
})});
});
}
else {
state.dataSource = [];
}
this.setState(state);
}
/**
* @type {function}
*/
handleDelete = () => {
this.setState({selected: null});
this.onChangeCallback(undefined);
}
/**
* @return {Node}
*/
render() {
const {
fullWidth,
wrapStyle,
floatingLabelText,
floatingLabelFixed,
disabled,
} = this.props;
const styles = getStyles(this.props, this.context, this.state);
const chip = this.state.selected &&
(
<div style={Object.assign(styles.root, wrapStyle)}>
<TextFieldLabel
muiTheme={this.context.muiTheme}
style={styles.floatingLabel}
shrinkStyle={styles.floatingLabelFocusStyle}
shrink={true}
disabled={disabled}
>
{floatingLabelText}
</TextFieldLabel>
<Chip disabled={disabled} onRequestDelete={this.handleDelete} style={{marginTop: 30}}>
{this.state.selected.avatar && <Avatar src={this.state.selected.avatar} />}
{this.state.selected.text}
</Chip>
</div>
);
const autocomplete = !this.state.selected &&
(
<div style={Object.assign({width: fullWidth ? '100%' : 256, display: 'inline-block'}, wrapStyle)}>
<AutoComplete
floatingLabelText={floatingLabelText}
floatingLabelFixed={floatingLabelFixed}
fullWidth={fullWidth}
searchText={this.state.searchText}
onUpdateInput={this.handleUpdateInput}
onNewRequest={this.handleNewRequest}
dataSource={this.state.dataSource}
filter={() => true}
disabled={disabled}
/>
</div>
);
return (
chip ? chip : autocomplete
);
}
}
export default PersonPicker;