mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-10 19:06:34 +03:00
216 lines
4.9 KiB
TypeScript
216 lines
4.9 KiB
TypeScript
/**
|
|
* Could not find ready-to-use component with required behaviour so
|
|
* I quickly hacked my own. Will refactor into separate npm package later
|
|
*/
|
|
|
|
import * as React from 'react';
|
|
import styled, { StyledFunction } from 'styled-components';
|
|
|
|
function withProps<T, U extends HTMLElement = HTMLElement>(
|
|
styledFunction: StyledFunction<React.HTMLProps<U>>,
|
|
): StyledFunction<T & React.HTMLProps<U>> {
|
|
return styledFunction;
|
|
}
|
|
|
|
const DropDownItem = withProps<{ active: boolean }>(styled.li)`
|
|
${props => ((props as any).active ? 'background-color: #eee' : '')};
|
|
padding: 13px 16px;
|
|
&:hover {
|
|
background-color: #eee;
|
|
}
|
|
cursor: pointer;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
`;
|
|
|
|
const DropDownList = styled.ul`
|
|
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
|
|
0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
|
background: #fff;
|
|
border-radius: 0 0 2px 2px;
|
|
top: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 200;
|
|
overflow: hidden;
|
|
position: absolute;
|
|
list-style: none;
|
|
margin: 4px 0 0 0;
|
|
padding: 5px 0;
|
|
font-family: 'Lato';
|
|
overflow: hidden;
|
|
`;
|
|
|
|
const ComboBoxWrap = styled.div`
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 500px;
|
|
display: flex;
|
|
`;
|
|
|
|
const Input = styled.input`
|
|
box-sizing: border-box;
|
|
width: 100%;
|
|
padding: 0 10px;
|
|
color: #555;
|
|
background-color: #fff;
|
|
border: 1px solid #ccc;
|
|
|
|
font-size: 16px;
|
|
height: 28px;
|
|
box-sizing: border-box;
|
|
vertical-align: middle;
|
|
line-height: 1;
|
|
outline: none;
|
|
|
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
|
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
|
|
|
&:focus {
|
|
border-color: #66afe9;
|
|
outline: 0;
|
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
|
|
}
|
|
`;
|
|
|
|
const Button = styled.button`
|
|
background-color: #fff;
|
|
color: #333;
|
|
padding: 2px 10px;
|
|
touch-action: manipulation;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
border: 1px solid #ccc;
|
|
border-left: 0;
|
|
font-size: 16px;
|
|
height: 28px;
|
|
box-sizing: border-box;
|
|
vertical-align: middle;
|
|
line-height: 1;
|
|
outline: none;
|
|
width: 80px;
|
|
`;
|
|
|
|
export interface ComboBoxProps {
|
|
onChange?: (val: string) => void;
|
|
options: { value: string; label: string }[];
|
|
placeholder?: string;
|
|
value?: string;
|
|
}
|
|
export interface ComboBoxState {
|
|
open: boolean;
|
|
value: string;
|
|
activeItemIdx: number;
|
|
}
|
|
|
|
export default class ComboBox extends React.Component<ComboBoxProps, ComboBoxState> {
|
|
state = {
|
|
open: false,
|
|
value: this.props.value || '',
|
|
activeItemIdx: -1,
|
|
};
|
|
|
|
open = () => {
|
|
this.setState({
|
|
open: true,
|
|
});
|
|
};
|
|
|
|
close = () => {
|
|
this.setState({
|
|
open: false,
|
|
activeItemIdx: -1,
|
|
});
|
|
};
|
|
|
|
handleChange = e => {
|
|
this.updateValue(e.currentTarget.value);
|
|
};
|
|
|
|
updateValue(value) {
|
|
this.setState({
|
|
value,
|
|
activeItemIdx: -1,
|
|
});
|
|
}
|
|
|
|
handleSelect(value: string) {
|
|
this.updateValue(value);
|
|
if (this.props.onChange) {
|
|
this.props.onChange(value);
|
|
}
|
|
this.close();
|
|
}
|
|
|
|
handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.keyCode === 13) {
|
|
this.handleSelect(e.currentTarget.value);
|
|
} else if (e.keyCode === 40) {
|
|
const activeItemIdx = Math.min(this.props.options.length - 1, ++this.state.activeItemIdx);
|
|
this.setState({
|
|
open: true,
|
|
activeItemIdx,
|
|
value: this.props.options[activeItemIdx].value,
|
|
});
|
|
e.preventDefault();
|
|
} else if (e.keyCode === 38) {
|
|
const activeItemIdx = Math.max(0, --this.state.activeItemIdx);
|
|
this.setState({
|
|
activeItemIdx,
|
|
value: this.props.options[activeItemIdx].value,
|
|
});
|
|
e.preventDefault();
|
|
} else if (e.keyCode === 27) {
|
|
this.close();
|
|
}
|
|
};
|
|
|
|
handleBlur = () => {
|
|
setTimeout(() => this.close(), 100);
|
|
};
|
|
|
|
handleItemClick = (val, idx) => {
|
|
this.handleSelect(val);
|
|
this.setState({
|
|
activeItemIdx: idx,
|
|
});
|
|
};
|
|
|
|
render() {
|
|
const { open, value, activeItemIdx } = this.state;
|
|
const { options, placeholder } = this.props;
|
|
return (
|
|
<ComboBoxWrap>
|
|
<Input
|
|
placeholder={placeholder}
|
|
onChange={this.handleChange}
|
|
value={value}
|
|
onFocus={this.open}
|
|
onBlur={this.handleBlur}
|
|
onKeyDown={this.handleKeyPress}
|
|
/>
|
|
<Button onClick={() => this.handleSelect(this.state.value)}> TRY IT </Button>
|
|
{open && (
|
|
<DropDownList>
|
|
{options.map((option, idx) => (
|
|
<DropDownItem
|
|
active={idx == activeItemIdx}
|
|
key={option.value}
|
|
onMouseDown={() => {
|
|
this.handleItemClick(option.value, idx);
|
|
}}
|
|
>
|
|
<small>
|
|
<strong>{option.label}</strong>
|
|
</small>
|
|
<br />
|
|
{option.value}
|
|
</DropDownItem>
|
|
))}
|
|
</DropDownList>
|
|
)}
|
|
</ComboBoxWrap>
|
|
);
|
|
}
|
|
}
|