web: Components: Reworked ComboBox component. Fixed readme and story.
This commit is contained in:
parent
8dda4ddeab
commit
810d8c23d8
@ -26,15 +26,20 @@ const StyledComboBox = styled.div`
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
border: 1px solid #D0D5DA;
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
${props => props.isDisabled && `
|
${props => !props.noBorder && `
|
||||||
|
border: 1px solid #D0D5DA;
|
||||||
|
border-radius: 3px;
|
||||||
|
`}
|
||||||
|
|
||||||
|
${props => props.isDisabled && !props.noBorder && `
|
||||||
border-color: #ECEEF1;
|
border-color: #ECEEF1;
|
||||||
background: #F8F9F9;
|
background: #F8F9F9;
|
||||||
`}
|
`}
|
||||||
|
|
||||||
height: 32px;
|
${props => !props.noBorder && `
|
||||||
|
height: 32px;
|
||||||
|
`}
|
||||||
|
|
||||||
:hover{
|
:hover{
|
||||||
border-color: ${state => state.isOpen ? '#2DA7DB' : '#A3A9AE'};
|
border-color: ${state => state.isOpen ? '#2DA7DB' : '#A3A9AE'};
|
||||||
@ -49,7 +54,9 @@ const StyledComboButton = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 30px;
|
|
||||||
|
height: ${props => props.noBorder ? `18px` : `30px`};
|
||||||
|
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -72,16 +79,26 @@ font-size: 13px;
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|
||||||
|
${props => props.noBorder && `
|
||||||
|
line-height: none;
|
||||||
|
|
||||||
|
:hover{
|
||||||
|
border-bottom: 1px dashed;
|
||||||
|
}
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledArrowIcon = styled.div`
|
const StyledArrowIcon = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-self: start;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
margin-top: ${props => props.noBorder ? `5px` : `12px`};
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|
||||||
${state => state.isOpen && `
|
${props => props.isOpen && `
|
||||||
transform: scale(1, -1);
|
transform: scale(1, -1);
|
||||||
margin-top: 8px;
|
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -91,26 +108,19 @@ class ComboBox extends React.PureComponent {
|
|||||||
|
|
||||||
this.ref = React.createRef();
|
this.ref = React.createRef();
|
||||||
|
|
||||||
const selectedItem = this.getSelected();
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpen: props.opened,
|
isOpen: props.opened,
|
||||||
boxLabel: selectedItem && selectedItem.label,
|
selectedOption: props.selectedOption
|
||||||
boxIcon: selectedItem && selectedItem.icon,
|
|
||||||
options: props.options
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleClick = this.handleClick.bind(this);
|
|
||||||
this.stopAction = this.stopAction.bind(this);
|
|
||||||
this.toggle = this.toggle.bind(this);
|
|
||||||
this.comboBoxClick = this.comboBoxClick.bind(this);
|
|
||||||
this.optionClick = this.optionClick.bind(this);
|
|
||||||
|
|
||||||
if (props.opened)
|
if (props.opened)
|
||||||
handleAnyClick(true, this.handleClick);
|
handleAnyClick(true, this.handleClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = (e) => this.state.isOpen && !this.ref.current.contains(e.target) && this.toggle(false);
|
handleClick = (e) =>
|
||||||
|
this.state.isOpen
|
||||||
|
&& !this.ref.current.contains(e.target)
|
||||||
|
&& this.toggle(false);
|
||||||
|
|
||||||
stopAction = (e) => e.preventDefault();
|
stopAction = (e) => e.preventDefault();
|
||||||
|
|
||||||
@ -118,69 +128,43 @@ class ComboBox extends React.PureComponent {
|
|||||||
|
|
||||||
comboBoxClick = (e) => {
|
comboBoxClick = (e) => {
|
||||||
if (this.props.isDisabled || e.target.closest('.optionalBlock')) return;
|
if (this.props.isDisabled || e.target.closest('.optionalBlock')) return;
|
||||||
|
this.toggle(!this.state.isOpen);
|
||||||
this.setState({
|
|
||||||
option: this.props.option,
|
|
||||||
isOpen: !this.state.isOpen
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
optionClick = (option) => {
|
optionClick = (option) => {
|
||||||
this.setState({
|
this.toggle(!this.state.isOpen);
|
||||||
boxLabel: option.label,
|
this.setState({
|
||||||
boxIcon: option.icon,
|
isOpen: !this.state.isOpen,
|
||||||
isOpen: !this.state.isOpen
|
selectedOption: option
|
||||||
});
|
});
|
||||||
this.props.onSelect && this.props.onSelect(option);
|
this.props.onSelect && this.props.onSelect(option);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
handleAnyClick(false, this.handleClick);
|
handleAnyClick(false, this.handleClick);
|
||||||
}
|
};
|
||||||
|
|
||||||
getSelected = () => {
|
|
||||||
const selectedItem = this.props.options.find(x => x.key === this.props.selectedOption)
|
|
||||||
|| this.props.options[0];
|
|
||||||
|
|
||||||
return selectedItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSelectedLabel = () => {
|
|
||||||
const selectedItem = this.getSelected();
|
|
||||||
|
|
||||||
return selectedItem ? selectedItem.label : this.props.emptyOptionsPlaceholder;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (this.props.opened !== prevProps.opened) {
|
if (this.props.opened !== prevProps.opened) {
|
||||||
this.toggle(this.props.opened);
|
handleAnyClick(this.props.opened, this.handleClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.isOpen !== prevState.isOpen) {
|
if (this.state.isOpen !== prevState.isOpen) {
|
||||||
handleAnyClick(this.state.isOpen, this.handleClick);
|
handleAnyClick(this.state.isOpen, this.handleClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.options.length !== prevProps.options.length) { //TODO: Move options from state
|
|
||||||
const label = this.getSelectedLabel();
|
|
||||||
this.setState({
|
|
||||||
options: this.props.options,
|
|
||||||
boxLabel: label
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.selectedOption !== prevProps.selectedOption) {
|
if (this.props.selectedOption !== prevProps.selectedOption) {
|
||||||
const label = this.getSelectedLabel();
|
this.setState({selectedOption: this.props.selectedOption});
|
||||||
this.setState({ boxLabel: label });
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
//console.log("ComboBox render");
|
console.log("ComboBox render");
|
||||||
|
|
||||||
const { dropDownMaxHeight, isDisabled, directionX, directionY, scaled, children } = this.props;
|
const { dropDownMaxHeight, isDisabled, directionX, directionY, scaled, children, options, noBorder } = this.props;
|
||||||
const { boxLabel, boxIcon, isOpen, options } = this.state;
|
const { isOpen, selectedOption } = this.state;
|
||||||
|
|
||||||
const dropDownMaxHeightProp = dropDownMaxHeight ? { maxHeight: dropDownMaxHeight} : {};
|
const dropDownMaxHeightProp = dropDownMaxHeight ? { maxHeight: dropDownMaxHeight } : {};
|
||||||
const dropDownManualWidthProp = scaled ? { manualWidth: '100%' } : {};
|
const dropDownManualWidthProp = scaled ? { manualWidth: '100%' } : {};
|
||||||
const boxIconColor = isDisabled ? '#D0D5DA' : '#333333';
|
const boxIconColor = isDisabled ? '#D0D5DA' : '#333333';
|
||||||
const arrowIconColor = isDisabled ? '#D0D5DA' : '#A3A9AE';
|
const arrowIconColor = isDisabled ? '#D0D5DA' : '#A3A9AE';
|
||||||
@ -189,17 +173,17 @@ class ComboBox extends React.PureComponent {
|
|||||||
<StyledComboBox ref={this.ref}
|
<StyledComboBox ref={this.ref}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
{...this.state}
|
{...this.state}
|
||||||
data={boxLabel}
|
data={selectedOption}
|
||||||
onClick={this.comboBoxClick}
|
onClick={this.comboBoxClick}
|
||||||
onSelect={this.stopAction}
|
onSelect={this.stopAction}
|
||||||
>
|
>
|
||||||
<StyledComboButton>
|
<StyledComboButton noBorder={noBorder}>
|
||||||
<StyledOptionalItem className='optionalBlock'>
|
<StyledOptionalItem className='optionalBlock'>
|
||||||
{children}
|
{children}
|
||||||
</StyledOptionalItem>
|
</StyledOptionalItem>
|
||||||
{boxIcon &&
|
{selectedOption && selectedOption.icon &&
|
||||||
<StyledIcon>
|
<StyledIcon>
|
||||||
{React.createElement(Icons[boxIcon],
|
{React.createElement(Icons[selectedOption.icon],
|
||||||
{
|
{
|
||||||
size: 'scale',
|
size: 'scale',
|
||||||
color: boxIconColor,
|
color: boxIconColor,
|
||||||
@ -208,10 +192,10 @@ class ComboBox extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
</StyledIcon>
|
</StyledIcon>
|
||||||
}
|
}
|
||||||
<StyledLabel>
|
<StyledLabel noBorder={noBorder}>
|
||||||
{boxLabel}
|
{selectedOption.label}
|
||||||
</StyledLabel>
|
</StyledLabel>
|
||||||
<StyledArrowIcon {...this.state}>
|
<StyledArrowIcon noBorder={noBorder} isOpen={this.state.isOpen}>
|
||||||
{React.createElement(Icons['ExpanderDownIcon'],
|
{React.createElement(Icons['ExpanderDownIcon'],
|
||||||
{
|
{
|
||||||
size: 'scale',
|
size: 'scale',
|
||||||
@ -231,7 +215,7 @@ class ComboBox extends React.PureComponent {
|
|||||||
>
|
>
|
||||||
{options.map((option) =>
|
{options.map((option) =>
|
||||||
<DropDownItem {...option}
|
<DropDownItem {...option}
|
||||||
disabled={option.label === boxLabel}
|
disabled={option.label === selectedOption.label}
|
||||||
onClick={this.optionClick.bind(this, option)}
|
onClick={this.optionClick.bind(this, option)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -242,25 +226,20 @@ class ComboBox extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ComboBox.propTypes = {
|
ComboBox.propTypes = {
|
||||||
|
noBorder: PropTypes.bool,
|
||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool,
|
||||||
withBorder: PropTypes.bool,
|
selectedOption: PropTypes.object,
|
||||||
selectedOption: PropTypes.oneOfType([
|
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.number
|
|
||||||
]),
|
|
||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
onSelect: PropTypes.func,
|
onSelect: PropTypes.func,
|
||||||
dropDownMaxHeight: PropTypes.number,
|
dropDownMaxHeight: PropTypes.number,
|
||||||
emptyOptionsPlaceholder: PropTypes.string,
|
|
||||||
|
|
||||||
size: PropTypes.oneOf(['base', 'middle', 'big', 'huge', 'content']),
|
size: PropTypes.oneOf(['base', 'middle', 'big', 'huge', 'content']),
|
||||||
scaled: PropTypes.bool,
|
scaled: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboBox.defaultProps = {
|
ComboBox.defaultProps = {
|
||||||
|
noBorder: false,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
withBorder: true,
|
|
||||||
emptyOptionsPlaceholder: 'Select',
|
|
||||||
size: 'base',
|
size: 'base',
|
||||||
scaled: true
|
scaled: true
|
||||||
}
|
}
|
||||||
|
@ -37,13 +37,25 @@ const options = [
|
|||||||
key: 5,
|
key: 5,
|
||||||
icon: 'CopyIcon',
|
icon: 'CopyIcon',
|
||||||
label: 'Option 5'
|
label: 'Option 5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 6,
|
||||||
|
label: 'Option 6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 7,
|
||||||
|
label: 'Option 7'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
<ComboBox options={options}
|
<ComboBox options={options}
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
selectedOption={25}
|
selectedOption={{
|
||||||
|
key: 0,
|
||||||
|
label: 'Select'
|
||||||
|
}}
|
||||||
dropDownMaxHeight='200px'
|
dropDownMaxHeight='200px'
|
||||||
|
noBorder={false}
|
||||||
scale={true}
|
scale={true}
|
||||||
size='content'
|
size='content'
|
||||||
onSelect={option => console.log('selected', option)}
|
onSelect={option => console.log('selected', option)}
|
||||||
@ -56,9 +68,9 @@ const options = [
|
|||||||
| ---------------------- | ----------------- | :------: | ---------------------------- | ------- | -------------------------------------------- |
|
| ---------------------- | ----------------- | :------: | ---------------------------- | ------- | -------------------------------------------- |
|
||||||
| `options` | `array` | ✅ | - | - | Combo box options |
|
| `options` | `array` | ✅ | - | - | Combo box options |
|
||||||
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
|
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
|
||||||
| `selectedOption` | `string`,`number` | - | - | `0` | Index of option selected by default |
|
| `noBorder` | `bool` | - | - | `false` | Indicates that component is displayed without borders |
|
||||||
|
| `selectedOption` | `object` | - | - | - | Selected option |
|
||||||
| `onSelect` | `func` | - | - | - | Will be triggered whenever an ComboBox is selected option |
|
| `onSelect` | `func` | - | - | - | Will be triggered whenever an ComboBox is selected option |
|
||||||
| `dropDownMaxHeight` | `string` | - | - | - | Height of Dropdown |
|
| `dropDownMaxHeight` | `string` | - | - | - | Height of Dropdown |
|
||||||
| `scaled` | `bool` | - | - | `true` | Indicates that component is scaled by parent |
|
| `scaled` | `bool` | - | - | `true` | Indicates that component is scaled by parent |
|
||||||
| `size` | `oneOf` | - | `base`, `middle`, `big`, `huge`, `content` | `base` | Select component width, one of default |
|
| `size` | `oneOf` | - | `base`, `middle`, `big`, `huge`, `content` | `base` | Select component width, one of default |
|
||||||
| `emptyOptionsPlaceholder`| `string` | - | - | `Select`| Label displayed in absence of options |
|
|
@ -11,17 +11,6 @@ import Section from '../../../.storybook/decorators/section';
|
|||||||
const iconNames = Object.keys(Icons);
|
const iconNames = Object.keys(Icons);
|
||||||
const sizeOptions = ['base', 'middle', 'big', 'huge', 'content'];
|
const sizeOptions = ['base', 'middle', 'big', 'huge', 'content'];
|
||||||
|
|
||||||
const appendOptions = (comboOptions, optionsCount) => {
|
|
||||||
let newOptions = comboOptions;
|
|
||||||
for (let i = 0; i <= optionsCount; i++) {
|
|
||||||
newOptions.push({
|
|
||||||
key: (i + 6),
|
|
||||||
label: 'Option ' + (i + 6)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return newOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
iconNames.push("NONE");
|
iconNames.push("NONE");
|
||||||
|
|
||||||
storiesOf('Components|Input', module)
|
storiesOf('Components|Input', module)
|
||||||
@ -52,26 +41,25 @@ storiesOf('Components|Input', module)
|
|||||||
key: 5,
|
key: 5,
|
||||||
icon: 'CopyIcon',
|
icon: 'CopyIcon',
|
||||||
label: 'Option 5'
|
label: 'Option 5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 6,
|
||||||
|
label: 'Option 6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 7,
|
||||||
|
label: 'Option 7'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const optionsCount = number('Add options', 1,
|
|
||||||
{
|
|
||||||
range: true,
|
|
||||||
min: 1,
|
|
||||||
max: 100,
|
|
||||||
step: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const needScrollDropDown = boolean('Need scroll dropdown', false);
|
const needScrollDropDown = boolean('Need scroll dropdown', false);
|
||||||
const dropDownMaxHeight = needScrollDropDown && number('dropDownMaxHeight', 200);
|
const dropDownMaxHeight = needScrollDropDown && number('dropDownMaxHeight', 200);
|
||||||
const optionsMultiSelect = options('Children',
|
const optionsMultiSelect = options('children',
|
||||||
{
|
{
|
||||||
button: 'button',
|
button: 'button',
|
||||||
icon: 'icon'
|
icon: 'icon'
|
||||||
},
|
},
|
||||||
['icon'],
|
[],
|
||||||
{
|
{
|
||||||
display: 'multi-select',
|
display: 'multi-select',
|
||||||
});
|
});
|
||||||
@ -80,10 +68,10 @@ storiesOf('Components|Input', module)
|
|||||||
optionsMultiSelect.forEach(function (item, i) {
|
optionsMultiSelect.forEach(function (item, i) {
|
||||||
switch (item) {
|
switch (item) {
|
||||||
case "button":
|
case "button":
|
||||||
children.push(<Button label="OK" key={i} />);
|
children.push(<Button label="button" key={i} />);
|
||||||
break;
|
break;
|
||||||
case "icon":
|
case "icon":
|
||||||
children.push(<Icons.SettingsIcon size="medium" key={i} />);
|
children.push(<Icons.NavLogoIcon size="medium" key={i} />);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -93,9 +81,14 @@ storiesOf('Components|Input', module)
|
|||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
options={appendOptions(comboOptions, optionsCount)}
|
options={comboOptions}
|
||||||
onSelect={option => action("Selected option")(option)}
|
onSelect={option => action("Selected option")(option)}
|
||||||
|
selectedOption={{
|
||||||
|
key: 0,
|
||||||
|
label: 'Select'
|
||||||
|
}}
|
||||||
isDisabled={boolean('isDisabled', false)}
|
isDisabled={boolean('isDisabled', false)}
|
||||||
|
noBorder={boolean('noBorder', false)}
|
||||||
dropDownMaxHeight={dropDownMaxHeight}
|
dropDownMaxHeight={dropDownMaxHeight}
|
||||||
scaled={boolean('scaled', false)}
|
scaled={boolean('scaled', false)}
|
||||||
size={select('size', sizeOptions, 'content')}
|
size={select('size', sizeOptions, 'content')}
|
||||||
|
Loading…
Reference in New Issue
Block a user