Files
superset2/superset-frontend/src/explore/components/controls/SelectControl.jsx
Geido 3f0756f637 chore: Select component refactoring - SelectControl - Iteration 5 (#16510)
* Refactor Select DatasourceEditor

* Fire onChange with allowNewOptions

* Clean up

* Refactor Select in AnnotationLayer

* Handle on clear

* Update tests

* Refactor Select in SpatialControl

* Show search

* Refactor Select in FilterBox

* Remove search where unnecessary

* Update SelectControl - WIP

* Refactor Controls

* Update SelectControl tests

* Clean up

* Test allowNewOptions false

* Use SelectControl AnnotationLayer

* Use SelectControl SpatialControl

* Clean up

* Render custom label

* Show search

* Implement filterOption

* Improve filterOption

* Update Cypress

* Update Cypress table test

* Use value for defaultValue

* Merge with latest changes

* Reconcile with latest Select changes

* Update superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Update superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Revert changes to test

* Call onPopoverClear when v value is undefined

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
2021-10-04 18:24:41 +03:00

253 lines
6.4 KiB
JavaScript

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { css, t } from '@superset-ui/core';
import { Select } from 'src/components';
import ControlHeader from 'src/explore/components/ControlHeader';
const propTypes = {
ariaLabel: PropTypes.string,
autoFocus: PropTypes.bool,
choices: PropTypes.array,
clearable: PropTypes.bool,
description: PropTypes.string,
disabled: PropTypes.bool,
freeForm: PropTypes.bool,
isLoading: PropTypes.bool,
multi: PropTypes.bool,
isMulti: PropTypes.bool,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
onFocus: PropTypes.func,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.array,
]),
default: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.array,
]),
showHeader: PropTypes.bool,
optionRenderer: PropTypes.func,
valueKey: PropTypes.string,
options: PropTypes.array,
placeholder: PropTypes.string,
filterOption: PropTypes.func,
// ControlHeader props
label: PropTypes.string,
renderTrigger: PropTypes.bool,
validationErrors: PropTypes.array,
rightNode: PropTypes.node,
leftNode: PropTypes.node,
onClick: PropTypes.func,
hovered: PropTypes.bool,
tooltipOnClick: PropTypes.func,
warning: PropTypes.string,
danger: PropTypes.string,
};
const defaultProps = {
autoFocus: false,
choices: [],
clearable: true,
description: null,
disabled: false,
freeForm: false,
isLoading: false,
label: null,
multi: false,
onChange: () => {},
onFocus: () => {},
showHeader: true,
valueKey: 'value',
};
export default class SelectControl extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
options: this.getOptions(props),
};
this.onChange = this.onChange.bind(this);
this.handleFilterOptions = this.handleFilterOptions.bind(this);
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (
nextProps.choices !== this.props.choices ||
nextProps.options !== this.props.options
) {
const options = this.getOptions(nextProps);
this.setState({ options });
}
}
// Beware: This is acting like an on-click instead of an on-change
// (firing every time user chooses vs firing only if a new option is chosen).
onChange(val) {
// will eventually call `exploreReducer`: SET_FIELD_VALUE
const { valueKey } = this.props;
let onChangeVal = val;
if (Array.isArray(val)) {
const values = val.map(v => v?.[valueKey] || v);
onChangeVal = values;
}
if (typeof val === 'object' && val?.[valueKey]) {
onChangeVal = val[valueKey];
}
this.props.onChange(onChangeVal, []);
}
getOptions(props) {
const { choices, optionRenderer, valueKey } = props;
let options = [];
if (props.options) {
options = props.options.map(o => ({
...o,
value: o[valueKey],
label: o.label || o[valueKey],
customLabel: optionRenderer ? optionRenderer(o) : undefined,
}));
} else if (choices) {
// Accepts different formats of input
options = choices.map(c => {
if (Array.isArray(c)) {
const [value, label] = c.length > 1 ? c : [c[0], c[0]];
return {
value,
label,
};
}
if (Object.is(c)) {
return {
...c,
value: c[valueKey],
label: c.label || c[valueKey],
};
}
return { value: c, label: c };
});
}
return options;
}
handleFilterOptions(text, option) {
const { filterOption } = this.props;
return filterOption({ data: option }, text);
}
render() {
const {
ariaLabel,
autoFocus,
clearable,
disabled,
filterOption,
freeForm,
isLoading,
isMulti,
label,
multi,
name,
placeholder,
onFocus,
optionRenderer,
showHeader,
value,
// ControlHeader props
description,
renderTrigger,
rightNode,
leftNode,
validationErrors,
onClick,
hovered,
tooltipOnClick,
warning,
danger,
} = this.props;
const headerProps = {
name,
label,
description,
renderTrigger,
rightNode,
leftNode,
validationErrors,
onClick,
hovered,
tooltipOnClick,
warning,
danger,
};
const selectProps = {
allowNewOptions: freeForm,
autoFocus,
ariaLabel:
ariaLabel || (typeof label === 'string' ? label : t('Select ...')),
allowClear: clearable,
disabled,
filterOption:
filterOption && typeof filterOption === 'function'
? this.handleFilterOptions
: true,
header: showHeader && <ControlHeader {...headerProps} />,
loading: isLoading,
mode: isMulti || multi ? 'multiple' : 'single',
name: `select-${name}`,
onChange: this.onChange,
onFocus,
optionRenderer,
options: this.state.options,
placeholder,
value:
value ||
(this.props.default !== undefined ? this.props.default : undefined),
};
return (
<div
css={theme => css`
.type-label {
margin-right: ${theme.gridUnit * 2}px;
}
.Select__multi-value__label > span,
.Select__option > span,
.Select__single-value > span {
display: flex;
align-items: center;
}
`}
>
<Select {...selectProps} />
</div>
);
}
}
SelectControl.propTypes = propTypes;
SelectControl.defaultProps = defaultProps;