packages/web/src/components/shared/Dropdown.js
import React, { Component } from 'react';
import Downshift from 'downshift';
import { withTheme } from 'emotion-theming';
import types from '@appbaseio/reactivecore/lib/utils/types';
import { getClassName } from '@appbaseio/reactivecore/lib/utils/helper';
import Input, { suggestionsContainer, suggestions } from '../../styles/Input';
import Select, { Tick } from '../../styles/Select';
import Chevron from '../../styles/Chevron';
class Dropdown extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
searchTerm: '',
};
}
toggle = () => {
this.setState({
isOpen: !this.state.isOpen,
});
};
close = () => {
this.setState({
isOpen: false,
});
};
onChange = (item) => {
if (this.props.returnsObject) {
this.props.onChange(item);
} else {
this.props.onChange(item[this.props.keyField]);
}
if (!this.props.multi) {
this.setState({
isOpen: false,
});
}
};
handleStateChange = (changes) => {
const { isOpen, type } = changes;
if (type === Downshift.stateChangeTypes.mouseUp) {
this.setState({
isOpen,
});
}
};
getBackgroundColor = (highlighted, selected) => {
const isDark = this.props.themePreset === 'dark';
if (highlighted) {
return isDark ? '#555' : '#eee';
} else if (selected) {
return isDark ? '#686868' : '#fafafa';
}
return isDark ? '#424242' : '#fff';
};
handleInputChange = (e) => {
const { value } = e.target;
this.setState({
searchTerm: value,
});
};
renderToString = (value) => {
if (Array.isArray(value) && value.length) {
const arrayToRender = value.map(item => this.renderToString(item));
return arrayToRender.join(', ');
} else if (value && typeof value === 'object') {
if (value[this.props.labelField]) {
return value[this.props.labelField];
} else if (Object.keys(value).length) {
return this.renderToString(Object.keys(value));
}
return this.props.placeholder;
}
return value;
};
render() {
const {
items,
selectedItem,
placeholder,
labelField,
keyField,
themePreset,
theme,
renderListItem,
transformData,
footer,
} = this.props;
let itemsToRender = items;
if (transformData) {
itemsToRender = transformData(itemsToRender);
}
return (
<Downshift
selectedItem={selectedItem}
onChange={this.onChange}
onOuterClick={this.close}
onStateChange={this.handleStateChange}
isOpen={this.state.isOpen}
itemToString={i => i && i[this.props.labelField]}
render={({
getButtonProps, getItemProps, isOpen, highlightedIndex,
}) => (
<div className={suggestionsContainer}>
<Select
{...getButtonProps()}
className={getClassName(this.props.innerClass, 'select') || null}
onClick={this.toggle}
title={selectedItem ? this.renderToString(selectedItem) : placeholder}
small={this.props.small}
themePreset={this.props.themePreset}
>
<div>
{selectedItem ? this.renderToString(selectedItem) : placeholder}
</div>
<Chevron open={isOpen} />
</Select>
{isOpen && itemsToRender.length ? (
<ul
className={`${suggestions(themePreset, theme)} ${
this.props.small ? 'small' : ''
} ${getClassName(this.props.innerClass, 'list')}`}
>
{this.props.showSearch ? (
<Input
id={`${this.props.componentId}-input`}
style={{
border: 0,
borderBottom: '1px solid #ddd',
}}
showIcon={false}
className={getClassName(this.props.innerClass, 'input')}
placeholder="Type here to search..."
value={this.state.searchTerm}
onChange={this.handleInputChange}
themePreset={themePreset}
/>
) : null}
{itemsToRender
.filter((item) => {
if (String(item[labelField]).length) {
if (this.props.showSearch && this.state.searchTerm) {
return String(item[labelField])
.toLowerCase()
.includes(this.state.searchTerm.toLowerCase());
}
return true;
}
return false;
})
.map((item, index) => {
let selected
= this.props.multi
// MultiDropdownList
&& ((selectedItem && !!selectedItem[item[keyField]])
// MultiDropdownRange
|| (Array.isArray(selectedItem)
&& selectedItem.find(value =>
value[labelField] === item[labelField])));
if (!this.props.multi) selected = item.key === selectedItem;
return (
<li
{...getItemProps({ item })}
key={item[keyField]}
className={`${selected ? 'active' : ''}`}
style={{
backgroundColor: this.getBackgroundColor(
highlightedIndex === index,
selected,
),
}}
>
{renderListItem ? (
renderListItem(item[labelField], item.doc_count)
) : (
<div>
{typeof item[labelField] === 'string' ? (
<span
dangerouslySetInnerHTML={{
__html: item[labelField],
}}
/>
) : (
item[labelField]
)}
{this.props.showCount
&& item.doc_count && (
<span
className={
getClassName(
this.props.innerClass,
'count',
) || null
}
>
({item.doc_count})
</span>
)}
</div>
)}
{selected && this.props.multi ? (
<Tick
className={
getClassName(
this.props.innerClass,
'icon',
) || null
}
/>
) : null}
</li>
);
})}
{footer}
</ul>
) : null}
</div>
)}
/>
);
}
}
Dropdown.defaultProps = {
keyField: 'key',
labelField: 'label',
small: false,
};
Dropdown.propTypes = {
innerClass: types.style,
items: types.data,
keyField: types.string,
labelField: types.string,
multi: types.bool,
onChange: types.func,
placeholder: types.string,
returnsObject: types.bool,
renderListItem: types.func,
transformData: types.func,
selectedItem: types.selectedValue,
showCount: types.bool,
single: types.bool,
small: types.bool,
theme: types.style,
themePreset: types.themePreset,
showSearch: types.bool,
};
export default withTheme(Dropdown);