@zohodesk/components
Version:
In this Package, we Provide Some Basic Components to Build Web App
583 lines (552 loc) • 19.2 kB
JavaScript
/**** Libraries ****/
import React from 'react';
import { AdvancedMultiSelect_propTypes } from "./props/propTypes";
import { AdvancedMultiSelect_defaultProps } from "./props/defaultProps";
/**** Components ****/
import { MultiSelectComponent } from "./MultiSelect";
import SelectedOptions from "./SelectedOptions";
import Suggestions from "./Suggestions";
import EmptyState from "./EmptyState";
import MultiSelectHeader from "./MultiSelectHeader";
import Popup from "../Popup/Popup";
import TextBoxIcon from "../TextBoxIcon/TextBoxIcon";
import { Container, Box } from "../Layout";
import Card, { CardContent, CardHeader, CardFooter } from "../Card/Card";
import { Icon } from '@zohodesk/icons';
import { getUniqueId } from "../../Provider/IdProvider";
import ResponsiveDropBox from "../ResponsiveDropBox/ResponsiveDropBox";
import { ResponsiveReceiver } from "../../Responsive/CustomResponsive";
import Loader from '@zohodesk/svg/lib/Loader/Loader';
/**** CSS ****/
import style from "../../MultiSelect/MultiSelect.module.css";
/**** Methods ****/
import { getIsEmptyValue, getSearchString } from "../../utils/Common.js";
import { filterSelectedOptions, makeGetIsShowClearIcon } from "../../utils/dropDownUtils";
/* eslint-disable react/sort-prop-types */
/* eslint-disable react/no-unused-prop-types */
/* eslint-disable react/forbid-component-props */
const dummyArray = [];
export class AdvancedMultiSelectComponent extends MultiSelectComponent {
constructor(props) {
super(props);
const {
state: oldState
} = this;
const {
selectedOptionsLimit,
selectedOptions
} = this.props;
const selectedOptionsLen = selectedOptions.length;
this.state = Object.assign({}, oldState, {
showedSelectedOptionsCount: !getIsEmptyValue(selectedOptionsLimit) && selectedOptionsLen > selectedOptionsLimit ? selectedOptionsLimit : selectedOptionsLen
});
this.getIsShowClearIcon = makeGetIsShowClearIcon();
this.handleFilterSuggestions = this.handleFilterSuggestions.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSelectOption = this.handleSelectOption.bind(this);
this.handleGetSelectedOptions = this.handleGetSelectedOptions.bind(this);
this.handleFormatSelectedOptions = this.handleFormatSelectedOptions.bind(this);
this.handleComponentDidUpdate = this.handleComponentDidUpdate.bind(this);
this.handleMoreClick = this.handleMoreClick.bind(this);
this.getNextAriaId = getUniqueId(this);
}
handleFilterSuggestions() {
const {
options = dummyArray,
searchStr
} = this.state;
const {
needLocalSearch
} = this.props;
const {
suggestions,
suggestionIds
} = this.getFilterSuggestions({
options,
selectedOptions: dummyArray,
searchStr: getSearchString(searchStr),
needSearch: needLocalSearch
});
this.suggestionsOrder = suggestionIds;
return suggestions;
}
handleChange() {
let selectedOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
let e = arguments.length > 1 ? arguments[1] : undefined;
const {
optionsNormalize,
searchStr
} = this.state;
const {
onChange,
needToCloseOnSelect = false,
togglePopup,
isSearchClearOnSelect,
selectedOptions: propSelectedOptions,
disabledOptions
} = this.props;
const {
newSelectedOptions
} = filterSelectedOptions({
selectedOptions,
propSelectedOptions,
disabledOptions
});
const selectedOptionsLen = newSelectedOptions.length;
const allSelectedOptionsDetails = [];
for (let i = 0; i < selectedOptionsLen; i++) {
const id = newSelectedOptions[i];
allSelectedOptionsDetails.push(optionsNormalize[id]);
}
onChange && onChange(newSelectedOptions, allSelectedOptionsDetails);
if (searchStr.trim() !== '' && isSearchClearOnSelect) {
this.handleSearch('');
}
this.searchInput && this.searchInput.focus({
preventScroll: true
});
if (needToCloseOnSelect) {
e && togglePopup(e);
}
}
handleSelectOption(option, value, index, e) {
const {
selectedOptions
} = this.props;
if (selectedOptions.indexOf(option) >= 0) {
//toggle select option
this.handleRemoveOption(option);
} else {
this.handleChange([...selectedOptions, option], e);
}
}
handleFormatOptions(props) {
const {
options,
valueField,
textField,
imageField,
iconName,
iconSize,
prefixText,
optionType,
disabledOptions
} = props;
return this.formatOptions({
options,
valueField,
textField,
imageField,
iconName,
prefixText,
optionType,
iconSize,
disabledOptions
});
}
handleGetSelectedOptions(selectedOptions, normalizedFormatOptions, props) {
//handle selectedoptions length
let {
showedSelectedOptionsCount
} = this.state || {};
const {
selectedOptionsLimit,
selectedOptionDetails
} = props;
showedSelectedOptionsCount = getIsEmptyValue(showedSelectedOptionsCount) ? selectedOptionsLimit : showedSelectedOptionsCount;
const {
normalizedFormatOptions: normalizedSelectedOptions
} = this.handleFormatSelectedOptions(selectedOptionDetails, props);
const formatOptions = this.objectConcat({
obj1: normalizedFormatOptions,
obj2: normalizedSelectedOptions
});
return this.getSelectedOptions({
selectedOptions,
normalizedFormatOptions: formatOptions,
selectedOptionsLength: showedSelectedOptionsCount
});
}
handleFormatSelectedOptions(selectedOptionDetails, props) {
const {
valueField,
textField,
imageField,
optionType,
iconName,
iconSize,
disabledOptions
} = props;
return this.formatSelectedOptions({
options: selectedOptionDetails,
valueField,
textField,
imageField,
optionType,
iconName,
iconSize,
disabledOptions
});
}
handleComponentDidUpdate(prevProps, prevState) {
const {
allSelectedOptionsDetails: oldAllSelectedOptionsDetails
} = this;
const {
selectedOptions,
selectedOptionsLimit,
id,
selectedOptionDetails
} = this.props;
const {
selectedOptions: oldSelectedOptions,
selectedOptionsLimit: oldSelectedOptionsLimit
} = prevProps;
const {
showedSelectedOptionsCount: oldCount
} = this.state;
const selectedOptionsLen = selectedOptions.length;
const oldSelectedOptionsLen = oldSelectedOptions.length;
if (id !== prevProps.id || selectedOptionsLimit !== oldSelectedOptionsLimit) {
//handle component update case (New set of options or new selectedOptionsLimit update)
this.setState({
showedSelectedOptionsCount: !getIsEmptyValue(selectedOptionsLimit) && selectedOptionsLen > selectedOptionsLimit ? selectedOptionsLimit : selectedOptionsLen
});
return;
}
if (oldCount !== prevState.showedSelectedOptionsCount || selectedOptionDetails !== prevProps.selectedOptionDetails) {
//When showedSelectedOptionsCount update, setState the updated selected option details
const {
normalizedFormatOptions
} = this.handleFormatOptions(this.props);
const optionsDetails = Object.assign({}, oldAllSelectedOptionsDetails, normalizedFormatOptions);
const {
formatSelectedOptions,
normalizedSelectedOptions,
selectedOptionIds
} = this.handleGetSelectedOptions(selectedOptions, optionsDetails, this.props);
this.allSelectedOptionsDetails = Object.assign(oldAllSelectedOptionsDetails, normalizedSelectedOptions);
this.setState({
selectedOptions: formatSelectedOptions,
selectedOptionIds
});
}
if (getIsEmptyValue(oldCount) || oldSelectedOptionsLen <= oldCount) {
//handle not more options case
if (selectedOptionsLen > oldCount) {
this.setState({
showedSelectedOptionsCount: selectedOptionsLen
});
}
} else if (oldSelectedOptionsLen > selectedOptionsLen) {
//handle more case and option remove case
const showedSelectedOptions = oldSelectedOptions.slice(0, oldCount);
let removeSelectedOption = oldCount;
selectedOptions.forEach(option => {
if (showedSelectedOptions.indexOf(option) >= 0) {
removeSelectedOption -= 1;
}
});
const newCount = oldCount - removeSelectedOption;
this.setState({
showedSelectedOptionsCount: newCount
}, () => {
const {
showedSelectedOptionsCount
} = this.state;
if (showedSelectedOptionsCount === 0) {
this.handleMoreClick();
}
});
}
}
handleMoreClick(e) {
const {
showedSelectedOptionsCount
} = this.state;
const {
selectedOptionsLimit,
selectedOptions,
getSelectedOptionDetails,
removeClose
} = this.props;
removeClose(e);
const selectedOptionsLen = selectedOptions.length;
if (showedSelectedOptionsCount < selectedOptionsLen) {
const newCount = showedSelectedOptionsCount + selectedOptionsLimit;
const newSelectedOptionIds = selectedOptions.slice(showedSelectedOptionsCount, newCount);
try {
getSelectedOptionDetails(newSelectedOptionIds).then(() => {
this.setState({
showedSelectedOptionsCount: showedSelectedOptionsCount + newSelectedOptionIds.length
});
});
} catch (e) {//console.error('getSelectedOptionDetails is not a promise function.');
}
}
this.searchInput && this.searchInput.focus({
preventScroll: true
});
}
responsiveFunc(_ref) {
let {
mediaQueryOR
} = _ref;
return {
tabletMode: mediaQueryOR([{
maxWidth: 700
}])
};
}
render() {
let {
isReadOnly,
needSelectAll,
searchEmptyMessage,
emptyMessage,
noMoreOptionsMessage,
dropBoxSize,
placeHolder,
isPopupOpen,
isPopupReady,
position,
defaultDropBoxPosition,
selectAllText,
getContainerRef,
removeClose,
isAnimate,
animationStyle,
textBoxSize,
variant,
size,
isDisabled,
title,
dataIdMultiSelectComp,
dataIdClearIcon,
dataIdSelectAllEle,
dataIdLoading,
selectedOptions: allselectedOptionIds = [],
listItemSize,
needBorder,
i18nKeys,
htmlId,
a11y,
borderColor,
isBoxPaddingNeed,
getFooter,
customProps,
needEffect,
disabledOptions,
isLoading,
dataSelectorId,
customClass,
isAbsolutePositioningNeeded,
positionsOffset,
targetOffset,
isRestrictScroll
} = this.props;
let {
SuggestionsProps = {},
DropBoxProps = {}
} = customProps;
let {
containerClass = ''
} = customClass;
const {
clearText = 'Clear all'
} = i18nKeys;
const {
clearLabel = 'Clear all'
} = a11y;
i18nKeys = Object.assign({}, i18nKeys, {
emptyText: i18nKeys.emptyText || emptyMessage,
noMoreText: i18nKeys.noMoreText || noMoreOptionsMessage,
searchEmptyText: i18nKeys.searchEmptyText || searchEmptyMessage
});
const {
selectedOptions,
searchStr,
hoverOption,
highLightedSelectOptions,
isFetchingOptions,
isActive,
showedSelectedOptionsCount,
options
} = this.state;
const suggestions = this.handleFilterSuggestions();
const allselectedOptionIdsLen = allselectedOptionIds.length;
const moreCount = allselectedOptionIdsLen - showedSelectedOptionsCount;
const setAriaId = this.getNextAriaId();
const ariaErrorId = this.getNextAriaId();
let {
isShowClearIcon: isShowClear
} = this.getIsShowClearIcon({
selectedOptions,
disabledOptions
});
let isShowClearIcon = !isReadOnly && !isDisabled && isShowClear;
return /*#__PURE__*/React.createElement("div", {
className: `${style.wrapper} ${isDisabled ? style.disabled : ''} ${borderColor === 'transparent' ? style.transparentContainer : ''} ${needEffect && !(isDisabled || isReadOnly) ? style.effect : ''} ${containerClass}`,
"data-id": dataIdMultiSelectComp,
"data-test-id": dataIdMultiSelectComp,
"data-title": isDisabled ? title : null,
onClick: this.handleInputFocus,
"data-selector-id": dataSelectorId
}, /*#__PURE__*/React.createElement(Container, {
align: "vertical",
alignBox: "row",
className: `${style.container} ${style[size]} ${isActive && needBorder ? style.active : ''} ${needBorder ? style.hasBorder : ''} ${needBorder ? !isDisabled ? style[`borderColor_${borderColor}`] : style.borderColor_transparent : ''}`,
eleRef: this.selectedOptionContainerRef,
wrap: "wrap"
}, /*#__PURE__*/React.createElement(SelectedOptions, {
selectedOptions: selectedOptions,
highLightedSelectOptions: highLightedSelectOptions,
isReadOnly: isReadOnly,
getRef: this.selectedOptionRef,
onRemove: this.handleRemoveOption,
onSelect: this.handleClickSelectedOption,
dataId: `${dataIdMultiSelectComp}_selectedOptions`
}), moreCount > 0 ? /*#__PURE__*/React.createElement(Box, {
className: style.more,
onClick: this.handleMoreClick,
tagName: "button"
}, `+ ${moreCount} More`) : null, /*#__PURE__*/React.createElement(Box, {
flexible: true,
className: style.wrapper,
adjust: true,
shrink: true,
dataId: `${dataIdMultiSelectComp}_textBox`
}, /*#__PURE__*/React.createElement("span", {
className: ` ${style.custmSpan} ${textBoxSize === 'xmedium' ? style.custmSpanXmedium : style.custmSpanMedium} ${isShowClearIcon ? style.clearIconSpace : ''}
`
}, searchStr), /*#__PURE__*/React.createElement(TextBoxIcon, {
isDisabled: isDisabled,
inputRef: this.searchInputRef,
needBorder: false,
onBlur: this.handleInactive,
onChange: this.handleSearch,
onClick: this.handleInputCick,
onFocus: this.handleActive,
onKeyDown: this.handleKeyDown,
placeHolder: selectedOptions.length >= 1 ? '' : placeHolder,
size: textBoxSize,
value: searchStr,
variant: variant,
isReadOnly: isReadOnly,
tabindex: isDisabled && '-1',
customClass: {
customTBoxWrap: style.custmInputWrapper
},
htmlId: htmlId,
a11y: {
role: 'combobox',
ariaOwns: setAriaId,
ariaControls: setAriaId,
ariaExpanded: !isReadOnly && !isDisabled && isPopupOpen ? true : false,
ariaHaspopup: true,
ariaDescribedby: ariaErrorId
},
autoComplete: false
}, isShowClearIcon ? /*#__PURE__*/React.createElement(Container, {
alignBox: "row",
align: "vertical"
}, /*#__PURE__*/React.createElement(Box, {
className: `${style.delete}`,
dataId: dataIdClearIcon,
"data-title": clearText,
onClick: this.handleDeselectAll,
tagName: "button",
"aria-label": clearLabel
}, /*#__PURE__*/React.createElement(Icon, {
name: "ZD-delete",
size: "15"
}))) : null))), !isReadOnly && !isDisabled && isPopupOpen ? /*#__PURE__*/React.createElement(ResponsiveReceiver, {
query: this.responsiveFunc,
responsiveId: "Helmet"
}, _ref2 => {
let {
tabletMode
} = _ref2;
return /*#__PURE__*/React.createElement(ResponsiveDropBox, {
animationStyle: animationStyle,
boxPosition: position || `${defaultDropBoxPosition}Center`,
getRef: getContainerRef,
isActive: isPopupReady,
isAnimate: isAnimate,
isArrow: false,
onClick: removeClose,
isPadding: false,
isBoxPaddingNeed: isBoxPaddingNeed,
htmlId: setAriaId,
a11y: {
role: 'listbox',
ariaMultiselectable: true
},
dataId: `${dataIdMultiSelectComp}_dropbox`,
isAbsolutePositioningNeeded: isAbsolutePositioningNeeded,
positionsOffset: positionsOffset,
targetOffset: targetOffset,
isRestrictScroll: isRestrictScroll,
...DropBoxProps,
alignBox: "row",
isResponsivePadding: getFooter ? false : true
}, isLoading ? /*#__PURE__*/React.createElement(Container, {
align: "both",
className: style.loader
}, /*#__PURE__*/React.createElement(Loader, null)) : /*#__PURE__*/React.createElement(Box, {
flexible: true
}, /*#__PURE__*/React.createElement(Card, {
customClass: style.box,
onScroll: this.handleScroll
}, needSelectAll ? /*#__PURE__*/React.createElement(CardHeader, null, /*#__PURE__*/React.createElement(MultiSelectHeader, {
onSelect: this.handleSelectAll,
selectAllText: selectAllText,
suggestions: suggestions,
dataId: dataIdSelectAllEle
})) : null, /*#__PURE__*/React.createElement(CardContent, {
shrink: true,
customClass: !tabletMode && dropBoxSize ? style[dropBoxSize] : '',
eleRef: this.suggestionContainerRef
}, suggestions.length ? /*#__PURE__*/React.createElement(Suggestions, {
suggestions: suggestions,
selectedOptions: allselectedOptionIds,
getRef: this.suggestionItemRef,
hoverOption: hoverOption,
onClick: this.handleSelectOption,
onMouseEnter: this.handleMouseEnter,
needTick: true,
listItemSize: listItemSize,
a11y: {
role: 'option'
},
dataId: `${dataIdMultiSelectComp}_Options`,
...SuggestionsProps
}) : /*#__PURE__*/React.createElement(EmptyState, {
isLoading: isFetchingOptions,
options: options,
searchString: searchStr,
suggestions: suggestions,
dataId: dataIdLoading,
i18nKeys: i18nKeys,
htmlId: ariaErrorId
}), isFetchingOptions && /*#__PURE__*/React.createElement(Container, {
isCover: false,
align: "both"
}, /*#__PURE__*/React.createElement(Loader, null))), getFooter ? /*#__PURE__*/React.createElement(CardFooter, null, getFooter()) : null)));
}) : null);
}
}
AdvancedMultiSelectComponent.propTypes = AdvancedMultiSelect_propTypes;
AdvancedMultiSelectComponent.defaultProps = AdvancedMultiSelect_defaultProps;
AdvancedMultiSelectComponent.displayName = 'AdvancedMultiSelect';
const AdvancedMultiSelect = Popup(AdvancedMultiSelectComponent);
AdvancedMultiSelect.defaultProps = AdvancedMultiSelectComponent.defaultProps;
AdvancedMultiSelect.propTypes = AdvancedMultiSelectComponent.propTypes; // if (__DOCS__) {
// AdvancedMultiSelectComponent.docs = {
// componentGroup: 'Form Elements',
// folderName: 'Style Guide'
// };
// // eslint-disable-next-line react/forbid-foreign-prop-types
// AdvancedMultiSelect.propTypes = AdvancedMultiSelectComponent.propTypes;
// }
export default AdvancedMultiSelect;