aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/components/common/MultiSelect.tsx')
-rw-r--r--server/sonar-web/src/main/js/components/common/MultiSelect.tsx67
1 files changed, 50 insertions, 17 deletions
diff --git a/server/sonar-web/src/main/js/components/common/MultiSelect.tsx b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
index 91a68f25fe8..892cab66813 100644
--- a/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
+++ b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
@@ -18,17 +18,24 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import * as classNames from 'classnames';
import { difference } from 'lodash';
import MultiSelectOption from './MultiSelectOption';
import SearchBox from '../controls/SearchBox';
+import { translateWithParameters } from '../../helpers/l10n';
interface Props {
+ allowNewElements?: boolean;
+ allowSelection?: boolean;
elements: string[];
+ filterSelected?: (query: string, selectedElements: string[]) => string[];
+ footerNode?: React.ReactNode;
listSize?: number;
onSearch: (query: string) => Promise<void>;
onSelect: (item: string) => void;
onUnselect: (item: string) => void;
placeholder: string;
+ renderLabel?: (element: string) => React.ReactNode;
selectedElements: string[];
validateSearchInput?: (value: string) => string;
}
@@ -42,7 +49,9 @@ interface State {
}
interface DefaultProps {
+ filterSelected: (query: string, selectedElements: string[]) => string[];
listSize: number;
+ renderLabel: (element: string) => React.ReactNode;
validateSearchInput: (value: string) => string;
}
@@ -54,7 +63,10 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
mounted = false;
static defaultProps: DefaultProps = {
- listSize: 10,
+ filterSelected: (query: string, selectedElements: string[]) =>
+ selectedElements.filter(elem => elem.includes(query)),
+ listSize: 0,
+ renderLabel: (element: string) => element,
validateSearchInput: (value: string) => value
};
@@ -72,7 +84,7 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
componentDidMount() {
this.mounted = true;
this.onSearchQuery('');
- this.updateSelectedElements(this.props);
+ this.updateSelectedElements(this.props as PropsWithDefault);
this.updateUnselectedElements(this.props as PropsWithDefault);
if (this.container) {
this.container.addEventListener('keydown', this.handleKeyboard, true);
@@ -164,13 +176,13 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
onUnselectItem = (item: string) => this.props.onUnselect(item);
isNewElement = (elem: string, { selectedElements, elements }: Props) =>
- elem && selectedElements.indexOf(elem) === -1 && elements.indexOf(elem) === -1;
+ elem.length > 0 && selectedElements.indexOf(elem) === -1 && elements.indexOf(elem) === -1;
- updateSelectedElements = (props: Props) => {
+ updateSelectedElements = (props: PropsWithDefault) => {
this.setState((state: State) => {
if (state.query) {
return {
- selectedElements: [...props.selectedElements.filter(elem => elem.includes(state.query))]
+ selectedElements: props.filterSelected(state.query, props.selectedElements)
};
} else {
return { selectedElements: [...props.selectedElements] };
@@ -180,7 +192,9 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
updateUnselectedElements = (props: PropsWithDefault) => {
this.setState((state: State) => {
- if (props.listSize < state.selectedElements.length) {
+ if (props.listSize === 0) {
+ return { unselectedElements: difference(props.elements, props.selectedElements) };
+ } else if (props.listSize < state.selectedElements.length) {
return { unselectedElements: [] };
} else {
return {
@@ -239,8 +253,17 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
};
render() {
+ const { allowSelection = true, allowNewElements = true, footerNode = '' } = this.props;
+ const { renderLabel } = this.props as PropsWithDefault;
const { query, activeIdx, selectedElements, unselectedElements } = this.state;
const activeElement = this.getAllElements(this.props, this.state)[activeIdx];
+ const infiniteList = this.props.listSize === 0;
+ const listClasses = classNames('menu', {
+ 'menu-vertically-limited': infiniteList,
+ 'spacer-top': infiniteList,
+ 'with-top-separator': infiniteList,
+ 'with-bottom-separator': Boolean(footerNode)
+ });
return (
<div className="multi-select" ref={div => (this.container = div)}>
@@ -254,7 +277,11 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
value={query}
/>
</div>
- <ul className="menu">
+ <ul className={listClasses}>
+ {selectedElements.length < 1 &&
+ unselectedElements.length < 1 && (
+ <li className="spacer-left">{translateWithParameters('no_results_for_x', query)}</li>
+ )}
{selectedElements.length > 0 &&
selectedElements.map(element => (
<MultiSelectOption
@@ -263,6 +290,7 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
key={element}
onHover={this.handleElementHover}
onSelectChange={this.handleSelectChange}
+ renderLabel={renderLabel}
selected={true}
/>
))}
@@ -270,23 +298,28 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
unselectedElements.map(element => (
<MultiSelectOption
active={activeElement === element}
+ disabled={!allowSelection}
element={element}
key={element}
onHover={this.handleElementHover}
onSelectChange={this.handleSelectChange}
+ renderLabel={renderLabel}
/>
))}
- {this.isNewElement(query, this.props) && (
- <MultiSelectOption
- active={activeElement === query}
- custom={true}
- element={query}
- key={query}
- onHover={this.handleElementHover}
- onSelectChange={this.handleSelectChange}
- />
- )}
+ {allowNewElements &&
+ this.isNewElement(query, this.props) && (
+ <MultiSelectOption
+ active={activeElement === query}
+ custom={true}
+ element={query}
+ key={query}
+ onHover={this.handleElementHover}
+ onSelectChange={this.handleSelectChange}
+ renderLabel={renderLabel}
+ />
+ )}
</ul>
+ {footerNode}
</div>
);
}