mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-27 19:29:57 +03:00
235 lines
6.2 KiB
JavaScript
235 lines
6.2 KiB
JavaScript
import * as React from "react";
|
|
import * as Constants from "~/common/constants";
|
|
import * as SVG from "~/common/svg";
|
|
|
|
import { css } from "@emotion/core";
|
|
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
|
|
|
const STYLES_DROPDOWN_CONTAINER = css`
|
|
box-sizing: border-box;
|
|
z-index: ${Constants.zindex.modal};
|
|
`;
|
|
|
|
const STYLES_DROPDOWN = css`
|
|
box-sizing: border-box;
|
|
position: absolute;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background-color: ${Constants.system.white};
|
|
overflow: hidden;
|
|
width: 100%;
|
|
scrollbar-width: none;
|
|
padding-bottom: 24px;
|
|
height: calc(100% - 16px);
|
|
overflow-y: scroll;
|
|
|
|
::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
@media (max-width: ${Constants.sizes.mobile}px) {
|
|
height: calc(100% - 36px);
|
|
}
|
|
`;
|
|
|
|
const STYLES_DROPDOWN_ITEM = css`
|
|
box-sizing: border-box;
|
|
padding: 8px;
|
|
font-size: 0.8em;
|
|
border-radius: 4px;
|
|
border: 1px solid transparent;
|
|
cursor: pointer;
|
|
margin-bottom: -1px;
|
|
|
|
:hover {
|
|
border-color: ${Constants.system.lightBorder} !important;
|
|
}
|
|
`;
|
|
|
|
const STYLES_INPUT = css`
|
|
font-family: ${Constants.font.text};
|
|
-webkit-appearance: none;
|
|
width: 100%;
|
|
height: 40px;
|
|
background: ${Constants.system.foreground};
|
|
color: ${Constants.system.black};
|
|
display: flex;
|
|
font-size: 14px;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
outline: 0;
|
|
border: 0;
|
|
box-sizing: border-box;
|
|
transition: 200ms ease all;
|
|
padding: 0 24px 0 48px;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
border-radius: 4px;
|
|
margin-bottom: 16px;
|
|
::placeholder {
|
|
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
|
color: ${Constants.system.black};
|
|
opacity: 1; /* Firefox */
|
|
}
|
|
:-ms-input-placeholder {
|
|
/* Internet Explorer 10-11 */
|
|
color: ${Constants.system.black};
|
|
}
|
|
::-ms-input-placeholder {
|
|
/* Microsoft Edge */
|
|
color: ${Constants.system.black};
|
|
}
|
|
`;
|
|
|
|
const STYLES_LOADER = css`
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
`;
|
|
|
|
export class SearchDropdown extends React.Component {
|
|
_input;
|
|
_optionRoot;
|
|
|
|
static defaultProps = {
|
|
defaultResults: [],
|
|
};
|
|
|
|
state = {
|
|
selectedIndex: -1,
|
|
};
|
|
|
|
componentDidMount = () => {
|
|
window.addEventListener("keydown", this._handleDocumentKeydown);
|
|
this.debounceInstance = this.debounce(() => {
|
|
if (this.state.selectedIndex !== -1) {
|
|
this.setState({ selectedIndex: -1 });
|
|
}
|
|
this.props.onSearch();
|
|
}, 500);
|
|
};
|
|
|
|
componentWillUnmount = () => {
|
|
window.removeEventListener("keydown", this._handleDocumentKeydown);
|
|
};
|
|
|
|
componentDidUpdate = (prevProps) => {
|
|
if (prevProps.disabled && !this.props.disabled) {
|
|
this._input.focus();
|
|
}
|
|
};
|
|
|
|
debounce = (fn, time) => {
|
|
let timer;
|
|
|
|
return () => {
|
|
window.clearTimeout(timer);
|
|
timer = window.setTimeout(() => fn(), time);
|
|
};
|
|
};
|
|
|
|
_handleChange = (e) => {
|
|
this.props.onChange(e);
|
|
this.debounceInstance(e);
|
|
};
|
|
|
|
_handleSelect = (index) => {
|
|
this.props.onSelect(this.props.results[index].value);
|
|
};
|
|
|
|
_handleDocumentKeydown = (e) => {
|
|
if (e.keyCode === 27) {
|
|
this._handleDelete();
|
|
e.preventDefault();
|
|
} else if (e.keyCode === 9) {
|
|
this._handleDelete();
|
|
} else if (e.keyCode === 40) {
|
|
if (this.state.selectedIndex < this.props.results.length - 1) {
|
|
let listElem = this._optionRoot.children[this.state.selectedIndex + 1];
|
|
let elemRect = listElem.getBoundingClientRect();
|
|
let rootRect = this._optionRoot.getBoundingClientRect();
|
|
if (elemRect.bottom > rootRect.bottom) {
|
|
this._optionRoot.scrollTop =
|
|
listElem.offsetTop + listElem.offsetHeight - this._optionRoot.offsetHeight;
|
|
}
|
|
this.setState({ selectedIndex: this.state.selectedIndex + 1 });
|
|
}
|
|
e.preventDefault();
|
|
} else if (e.keyCode === 38) {
|
|
if (this.state.selectedIndex > 0) {
|
|
let listElem = this._optionRoot.children[this.state.selectedIndex - 1];
|
|
let elemRect = listElem.getBoundingClientRect();
|
|
let rootRect = this._optionRoot.getBoundingClientRect();
|
|
if (elemRect.top < rootRect.top) {
|
|
this._optionRoot.scrollTop = listElem.offsetTop;
|
|
}
|
|
this.setState({ selectedIndex: this.state.selectedIndex - 1 });
|
|
}
|
|
e.preventDefault();
|
|
} else if (e.keyCode === 13) {
|
|
if (this.props.results.length > this.state.selectedIndex && this.state.selectedIndex !== -1) {
|
|
this._handleSelect(this.state.selectedIndex);
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<div css={STYLES_DROPDOWN_CONTAINER} style={this.props.containerStyle}>
|
|
{this.props.disabled ? (
|
|
<div css={STYLES_LOADER}>
|
|
<LoaderSpinner />
|
|
</div>
|
|
) : (
|
|
<React.Fragment>
|
|
<div style={{ position: "relative" }}>
|
|
<input
|
|
disabled={this.props.disabled}
|
|
css={STYLES_INPUT}
|
|
value={this.props.inputValue}
|
|
placeholder={this.props.placeholder}
|
|
style={this.props.inputStyle}
|
|
onChange={this._handleChange}
|
|
ref={(c) => {
|
|
this._input = c;
|
|
}}
|
|
/>
|
|
<SVG.Search
|
|
height="20px"
|
|
style={{ position: "absolute", left: "12px", top: "10px" }}
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
data-menu
|
|
ref={(c) => {
|
|
this._optionRoot = c;
|
|
}}
|
|
css={STYLES_DROPDOWN}
|
|
style={this.props.style}
|
|
>
|
|
{this.props.results.map((each, i) => (
|
|
<div
|
|
key={each.value.data.id}
|
|
css={STYLES_DROPDOWN_ITEM}
|
|
style={{
|
|
borderColor:
|
|
this.state.selectedIndex === i ? Constants.system.lightBorder : "transparent",
|
|
...this.props.itemStyle,
|
|
}}
|
|
onClick={() => this.props.onSelect(each.value)}
|
|
>
|
|
{each.component}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</React.Fragment>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|