2022-01-28 13:44:58 +00:00
|
|
|
import React, { useRef, useState, useEffect, useCallback } from "react";
|
|
|
|
import PropTypes from "prop-types";
|
|
|
|
import Column from "./Column";
|
|
|
|
import Footer from "./Footer";
|
|
|
|
import Header from "./Header";
|
|
|
|
import Body from "./Body";
|
|
|
|
import { FixedSizeList as List } from "react-window";
|
|
|
|
import InfiniteLoader from "react-window-infinite-loader";
|
|
|
|
import AutoSizer from "react-virtualized-auto-sizer";
|
|
|
|
import ReactTooltip from "react-tooltip";
|
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
import Avatar from "@appserver/components/avatar";
|
2022-01-28 13:44:58 +00:00
|
|
|
import Checkbox from "@appserver/components/checkbox";
|
|
|
|
import SearchInput from "@appserver/components/search-input";
|
|
|
|
import Loader from "@appserver/components/loader";
|
|
|
|
import Text from "@appserver/components/text";
|
|
|
|
import Tooltip from "@appserver/components/tooltip";
|
2022-02-14 10:49:27 +00:00
|
|
|
import Heading from "@appserver/components/heading";
|
|
|
|
import IconButton from "@appserver/components/icon-button";
|
2022-01-28 13:44:58 +00:00
|
|
|
import CustomScrollbarsVirtualList from "@appserver/components/scrollbar/custom-scrollbars-virtual-list";
|
|
|
|
|
|
|
|
import StyledSelector from "./StyledSelector";
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2020-10-16 12:38:04 +00:00
|
|
|
const convertGroups = (items) => {
|
2019-12-14 13:16:01 +00:00
|
|
|
if (!items) return [];
|
|
|
|
|
|
|
|
const wrappedGroups = items.map(convertGroup);
|
|
|
|
|
|
|
|
return wrappedGroups;
|
|
|
|
};
|
|
|
|
|
2020-10-16 12:38:04 +00:00
|
|
|
const convertGroup = (group) => {
|
2019-12-14 13:16:01 +00:00
|
|
|
return {
|
|
|
|
key: group.key,
|
|
|
|
label: `${group.label} (${group.total})`,
|
|
|
|
total: group.total,
|
2020-10-16 12:38:04 +00:00
|
|
|
selected: 0,
|
2019-12-14 13:16:01 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2020-10-16 12:38:04 +00:00
|
|
|
const getCurrentGroup = (items) => {
|
2020-09-09 15:06:09 +00:00
|
|
|
const currentGroup = items.length > 0 ? items[0] : {};
|
2019-12-14 13:16:01 +00:00
|
|
|
return currentGroup;
|
|
|
|
};
|
|
|
|
|
2020-10-16 12:38:04 +00:00
|
|
|
const Selector = (props) => {
|
2019-12-14 13:16:01 +00:00
|
|
|
const {
|
|
|
|
groups,
|
|
|
|
selectButtonLabel,
|
|
|
|
isDisabled,
|
|
|
|
isMultiSelect,
|
|
|
|
hasNextPage,
|
|
|
|
options,
|
|
|
|
isNextPageLoading,
|
|
|
|
loadNextPage,
|
|
|
|
selectedOptions,
|
|
|
|
selectedGroups,
|
|
|
|
groupsHeaderLabel,
|
|
|
|
searchPlaceHolderLabel,
|
|
|
|
emptySearchOptionsLabel,
|
|
|
|
emptyOptionsLabel,
|
|
|
|
loadingLabel,
|
|
|
|
selectAllLabel,
|
|
|
|
onSelect,
|
|
|
|
getOptionTooltipContent,
|
|
|
|
onSearchChanged,
|
|
|
|
onGroupChanged,
|
2019-12-19 07:00:58 +00:00
|
|
|
size,
|
2020-03-28 10:10:32 +00:00
|
|
|
allowGroupSelection,
|
2020-10-16 12:38:04 +00:00
|
|
|
embeddedComponent,
|
2020-10-29 07:44:17 +00:00
|
|
|
showCounter,
|
2022-02-14 10:49:27 +00:00
|
|
|
onArrowClick,
|
|
|
|
headerLabel,
|
2019-12-14 13:16:01 +00:00
|
|
|
} = props;
|
|
|
|
|
|
|
|
const listOptionsRef = useRef(null);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2020-10-16 12:38:04 +00:00
|
|
|
Object.keys(currentGroup).length === 0 &&
|
|
|
|
setCurrentGroup(getCurrentGroup(convertGroups(groups)));
|
2019-12-14 13:16:01 +00:00
|
|
|
resetCache();
|
|
|
|
}, [searchValue, currentGroup, hasNextPage]);
|
|
|
|
|
2022-01-28 13:44:58 +00:00
|
|
|
const [selectedOptionList, setSelectedOptionList] = useState(
|
|
|
|
selectedOptions || []
|
|
|
|
);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-01-28 13:44:58 +00:00
|
|
|
const [selectedGroupList, setSelectedGroupList] = useState(
|
|
|
|
selectedGroups || []
|
|
|
|
);
|
|
|
|
const [searchValue, setSearchValue] = useState("");
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-01-28 13:44:58 +00:00
|
|
|
const [currentGroup, setCurrentGroup] = useState(
|
|
|
|
getCurrentGroup(convertGroups(groups))
|
|
|
|
);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const [groupHeader, setGroupHeader] = useState(null);
|
|
|
|
|
2019-12-14 13:16:01 +00:00
|
|
|
// Every row is loaded except for our loading indicator row.
|
2019-12-26 12:59:23 +00:00
|
|
|
const isItemLoaded = useCallback(
|
2020-10-16 12:38:04 +00:00
|
|
|
(index) => {
|
2019-12-26 12:59:23 +00:00
|
|
|
return !hasNextPage || index < options.length;
|
|
|
|
},
|
2022-01-28 13:44:58 +00:00
|
|
|
[hasNextPage, options]
|
2019-12-26 12:59:23 +00:00
|
|
|
);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
|
|
|
const onOptionChange = useCallback(
|
2022-02-14 10:49:27 +00:00
|
|
|
(index, isChecked) => {
|
|
|
|
const option = options[index];
|
|
|
|
const newSelected = !isChecked
|
2019-12-14 13:16:01 +00:00
|
|
|
? [option, ...selectedOptionList]
|
2020-10-16 12:38:04 +00:00
|
|
|
: selectedOptionList.filter((el) => el.key !== option.key);
|
2019-12-14 13:16:01 +00:00
|
|
|
setSelectedOptionList(newSelected);
|
|
|
|
|
|
|
|
if (!option.groups) return;
|
|
|
|
|
|
|
|
const newSelectedGroups = [];
|
|
|
|
const removedSelectedGroups = [];
|
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
if (isChecked) {
|
2020-10-16 12:38:04 +00:00
|
|
|
option.groups.forEach((g) => {
|
|
|
|
let index = selectedGroupList.findIndex((sg) => sg.key === g);
|
2019-12-14 13:16:01 +00:00
|
|
|
if (index > -1) {
|
|
|
|
// exists
|
|
|
|
const selectedGroup = selectedGroupList[index];
|
|
|
|
const newSelected = selectedGroup.selected + 1;
|
|
|
|
newSelectedGroups.push(
|
|
|
|
Object.assign({}, selectedGroup, {
|
2020-10-16 12:38:04 +00:00
|
|
|
selected: newSelected,
|
2022-01-28 13:44:58 +00:00
|
|
|
})
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
} else {
|
2020-10-16 12:38:04 +00:00
|
|
|
index = groups.findIndex((sg) => sg.key === g);
|
2019-12-14 13:16:01 +00:00
|
|
|
if (index < 0) return;
|
|
|
|
const notSelectedGroup = convertGroup(groups[index]);
|
|
|
|
newSelectedGroups.push(
|
|
|
|
Object.assign({}, notSelectedGroup, {
|
2020-10-16 12:38:04 +00:00
|
|
|
selected: 1,
|
2022-01-28 13:44:58 +00:00
|
|
|
})
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2020-10-16 12:38:04 +00:00
|
|
|
option.groups.forEach((g) => {
|
|
|
|
let index = selectedGroupList.findIndex((sg) => sg.key === g);
|
2019-12-14 13:16:01 +00:00
|
|
|
if (index > -1) {
|
|
|
|
// exists
|
|
|
|
const selectedGroup = selectedGroupList[index];
|
|
|
|
const newSelected = selectedGroup.selected - 1;
|
|
|
|
if (newSelected > 0) {
|
|
|
|
newSelectedGroups.push(
|
|
|
|
Object.assign({}, selectedGroup, {
|
2020-10-16 12:38:04 +00:00
|
|
|
selected: newSelected,
|
2022-01-28 13:44:58 +00:00
|
|
|
})
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
removedSelectedGroups.push(
|
|
|
|
Object.assign({}, selectedGroup, {
|
2020-10-16 12:38:04 +00:00
|
|
|
selected: newSelected,
|
2022-01-28 13:44:58 +00:00
|
|
|
})
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-16 12:38:04 +00:00
|
|
|
selectedGroupList.forEach((g) => {
|
|
|
|
const indexNew = newSelectedGroups.findIndex((sg) => sg.key === g.key);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
|
|
|
if (indexNew === -1) {
|
2022-01-28 13:44:58 +00:00
|
|
|
const indexRemoved = removedSelectedGroups.findIndex(
|
|
|
|
(sg) => sg.key === g.key
|
|
|
|
);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
|
|
|
if (indexRemoved === -1) {
|
|
|
|
newSelectedGroups.push(g);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
setSelectedGroupList(newSelectedGroups);
|
|
|
|
},
|
2022-01-28 13:44:58 +00:00
|
|
|
[options, selectedOptionList, groups, selectedGroupList]
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
const resetCache = useCallback(() => {
|
|
|
|
if (listOptionsRef && listOptionsRef.current) {
|
|
|
|
listOptionsRef.current.resetloadMoreItemsCache(true);
|
|
|
|
}
|
|
|
|
}, [listOptionsRef]);
|
|
|
|
|
2020-10-16 12:38:04 +00:00
|
|
|
const onSearchChange = useCallback((value) => {
|
2019-12-14 13:16:01 +00:00
|
|
|
setSearchValue(value);
|
|
|
|
onSearchChanged && onSearchChanged(value);
|
|
|
|
});
|
|
|
|
|
|
|
|
const onSearchReset = useCallback(() => {
|
2022-01-28 13:44:58 +00:00
|
|
|
onSearchChanged && onSearchChange("");
|
2019-12-14 13:16:01 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const isOptionChecked = useCallback(
|
2020-10-16 12:38:04 +00:00
|
|
|
(option) => {
|
2019-12-14 13:16:01 +00:00
|
|
|
const checked =
|
2020-10-16 12:38:04 +00:00
|
|
|
selectedOptionList.findIndex((el) => el.key === option.key) > -1 ||
|
2019-12-14 13:16:01 +00:00
|
|
|
(option.groups &&
|
2020-10-16 12:38:04 +00:00
|
|
|
option.groups.filter((gKey) => {
|
2022-01-28 13:44:58 +00:00
|
|
|
const selectedGroup = selectedGroupList.find(
|
|
|
|
(sg) => sg.key === gKey
|
|
|
|
);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
|
|
|
if (!selectedGroup) return false;
|
|
|
|
|
|
|
|
return selectedGroup.total === selectedGroup.selected;
|
|
|
|
}).length > 0);
|
|
|
|
|
|
|
|
return checked;
|
|
|
|
},
|
2022-01-28 13:44:58 +00:00
|
|
|
[selectedOptionList, selectedGroupList]
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
2022-02-14 10:49:27 +00:00
|
|
|
const onSelectOptions = (items) => {
|
|
|
|
onSelect && onSelect(items);
|
|
|
|
};
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const onAddClick = useCallback(() => {
|
|
|
|
onSelectOptions(selectedOptionList);
|
|
|
|
}, [selectedOptionList]);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const onLinkClick = useCallback(
|
|
|
|
(index) => {
|
2019-12-14 13:16:01 +00:00
|
|
|
const option = options[index];
|
|
|
|
|
|
|
|
if (!option) return;
|
|
|
|
|
|
|
|
onSelectOptions([option]);
|
|
|
|
},
|
2022-01-28 13:44:58 +00:00
|
|
|
[options]
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
|
2020-02-02 14:33:44 +00:00
|
|
|
const renderOptionItem = useCallback(
|
|
|
|
(index, style, option, isChecked, tooltipProps) => {
|
2020-02-03 12:39:28 +00:00
|
|
|
return isMultiSelect ? (
|
2022-02-14 10:49:27 +00:00
|
|
|
<div
|
|
|
|
style={style}
|
|
|
|
className="row-option"
|
|
|
|
value={`${index}`}
|
|
|
|
onClick={() => onOptionChange(index, isChecked)}
|
|
|
|
{...tooltipProps}
|
|
|
|
>
|
|
|
|
<div className="option-info">
|
|
|
|
<Avatar
|
|
|
|
className="option-avatar"
|
|
|
|
role="user"
|
|
|
|
size="min"
|
|
|
|
source={option.avatarUrl}
|
|
|
|
userName={option.label}
|
|
|
|
/>
|
|
|
|
<Text
|
|
|
|
className="option-text"
|
|
|
|
truncate={true}
|
|
|
|
noSelect={true}
|
|
|
|
fontSize="14px"
|
|
|
|
>
|
|
|
|
{option.label}
|
|
|
|
</Text>
|
|
|
|
</div>
|
2020-09-09 15:06:09 +00:00
|
|
|
<Checkbox
|
|
|
|
id={option.key}
|
|
|
|
value={`${index}`}
|
|
|
|
isChecked={isChecked}
|
2022-02-14 10:49:27 +00:00
|
|
|
className="option-checkbox"
|
2020-09-09 15:06:09 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : (
|
2022-02-14 10:49:27 +00:00
|
|
|
<div
|
2020-10-16 12:38:04 +00:00
|
|
|
key={option.key}
|
|
|
|
style={style}
|
|
|
|
className="row-option"
|
2022-02-14 10:49:27 +00:00
|
|
|
data-index={index}
|
|
|
|
onClick={() => onLinkClick(index)}
|
2020-10-16 12:38:04 +00:00
|
|
|
{...tooltipProps}
|
2022-01-28 13:44:58 +00:00
|
|
|
>
|
2022-02-14 10:49:27 +00:00
|
|
|
<div className="option-info">
|
|
|
|
{" "}
|
|
|
|
<Avatar
|
|
|
|
className="option-avatar"
|
|
|
|
role="user"
|
|
|
|
size="min"
|
|
|
|
source={option.avatarUrl}
|
|
|
|
userName={option.label}
|
2020-10-16 12:38:04 +00:00
|
|
|
/>
|
2022-02-14 10:49:27 +00:00
|
|
|
<Text
|
|
|
|
className="option-text"
|
|
|
|
truncate={true}
|
|
|
|
noSelect={true}
|
|
|
|
fontSize="14px"
|
|
|
|
>
|
|
|
|
{option.label}
|
|
|
|
</Text>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-10-16 12:38:04 +00:00
|
|
|
);
|
2020-02-02 14:33:44 +00:00
|
|
|
},
|
2022-02-14 10:49:27 +00:00
|
|
|
[isMultiSelect, onOptionChange, onLinkClick]
|
2020-02-02 14:33:44 +00:00
|
|
|
);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2020-02-02 14:33:44 +00:00
|
|
|
const renderOptionLoader = useCallback(
|
2020-10-16 12:38:04 +00:00
|
|
|
(style) => {
|
2019-12-14 13:16:01 +00:00
|
|
|
return (
|
2020-02-02 14:33:44 +00:00
|
|
|
<div style={style} className="row-option">
|
|
|
|
<div key="loader">
|
|
|
|
<Loader
|
|
|
|
type="oval"
|
|
|
|
size="16px"
|
|
|
|
style={{
|
2022-01-28 13:44:58 +00:00
|
|
|
display: "inline",
|
|
|
|
marginRight: "10px",
|
2020-02-02 14:33:44 +00:00
|
|
|
}}
|
|
|
|
/>
|
2022-02-14 10:49:27 +00:00
|
|
|
<Text as="span" noSelect={true}>
|
|
|
|
{loadingLabel}
|
|
|
|
</Text>
|
2020-02-02 14:33:44 +00:00
|
|
|
</div>
|
2019-12-14 13:16:01 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
},
|
2022-01-28 13:44:58 +00:00
|
|
|
[loadingLabel]
|
2020-02-02 14:33:44 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Render an item or a loading indicator.
|
|
|
|
// eslint-disable-next-line react/prop-types
|
|
|
|
const renderOption = useCallback(
|
|
|
|
({ index, style }) => {
|
|
|
|
const isLoaded = isItemLoaded(index);
|
|
|
|
|
|
|
|
if (!isLoaded) {
|
|
|
|
return renderOptionLoader(style);
|
|
|
|
}
|
|
|
|
|
|
|
|
const option = options[index];
|
|
|
|
const isChecked = isOptionChecked(option);
|
|
|
|
let tooltipProps = {};
|
|
|
|
|
|
|
|
ReactTooltip.rebuild();
|
|
|
|
|
|
|
|
return renderOptionItem(index, style, option, isChecked, tooltipProps);
|
|
|
|
},
|
2019-12-16 09:12:27 +00:00
|
|
|
[
|
|
|
|
isItemLoaded,
|
2020-02-02 14:33:44 +00:00
|
|
|
renderOptionLoader,
|
|
|
|
renderOptionItem,
|
2019-12-16 09:12:27 +00:00
|
|
|
loadingLabel,
|
|
|
|
options,
|
|
|
|
isOptionChecked,
|
|
|
|
isMultiSelect,
|
2020-02-02 14:33:44 +00:00
|
|
|
onOptionChange,
|
2019-12-16 09:12:27 +00:00
|
|
|
onLinkClick,
|
2020-10-16 12:38:04 +00:00
|
|
|
getOptionTooltipContent,
|
2022-01-28 13:44:58 +00:00
|
|
|
]
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const hasSelected = useCallback(() => {
|
|
|
|
return selectedOptionList.length > 0 || selectedGroupList.length > 0;
|
|
|
|
}, [selectedOptionList, selectedGroupList]);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
// If there are more items to be loaded then add an extra row to hold a loading indicator.
|
|
|
|
const itemCount = hasNextPage ? options.length + 1 : options.length;
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
// Only load 1 page of items at a time.
|
|
|
|
// Pass an empty callback to InfiniteLoader in case it asks us to load more than once.
|
|
|
|
const loadMoreItems = useCallback(
|
|
|
|
(startIndex) => {
|
|
|
|
if (isNextPageLoading) return;
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
startIndex: startIndex || 0,
|
|
|
|
searchValue: searchValue,
|
|
|
|
currentGroup: currentGroup ? currentGroup.key : null,
|
|
|
|
};
|
|
|
|
|
|
|
|
loadNextPage && loadNextPage(options);
|
2019-12-14 13:16:01 +00:00
|
|
|
},
|
2022-02-14 10:49:27 +00:00
|
|
|
[isNextPageLoading, searchValue, currentGroup, options]
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const getGroupSelectedOptions = useCallback(
|
2020-10-16 12:38:04 +00:00
|
|
|
(group) => {
|
2022-02-14 10:49:27 +00:00
|
|
|
const selectedGroup = selectedOptionList.filter(
|
|
|
|
(o) => o.groups.indexOf(group) > -1
|
|
|
|
);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
if (group === "all") {
|
|
|
|
selectedGroup.push(...selectedOptionList);
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectedGroup;
|
2019-12-14 13:16:01 +00:00
|
|
|
},
|
2022-02-14 10:49:27 +00:00
|
|
|
[selectedOptionList]
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const onGroupClick = useCallback(
|
|
|
|
(index) => {
|
2019-12-14 13:16:01 +00:00
|
|
|
const group = groups[index];
|
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
setGroupHeader({ ...group });
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
onGroupChanged && onGroupChanged(group);
|
|
|
|
setCurrentGroup(group);
|
2019-12-14 13:16:01 +00:00
|
|
|
},
|
2022-02-14 10:49:27 +00:00
|
|
|
[groups, onGroupChanged]
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
const renderGroup = useCallback(
|
|
|
|
({ index, style }) => {
|
|
|
|
const group = groups[index];
|
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const selectedOption = getGroupSelectedOptions(group.id);
|
|
|
|
|
|
|
|
const isIndeterminate = selectedOption.length > 0;
|
|
|
|
|
|
|
|
let label = group.label;
|
|
|
|
|
|
|
|
if (isMultiSelect && selectedOption.length > 0) {
|
|
|
|
label = `${group.label} (${selectedOption.length})`;
|
|
|
|
}
|
2019-12-14 13:16:01 +00:00
|
|
|
|
|
|
|
return (
|
2022-02-14 10:49:27 +00:00
|
|
|
<div
|
2019-12-14 13:16:01 +00:00
|
|
|
style={style}
|
2022-02-14 10:49:27 +00:00
|
|
|
className="row-option"
|
|
|
|
onClick={() => onGroupClick(index)}
|
2022-01-28 13:44:58 +00:00
|
|
|
>
|
2022-02-14 10:49:27 +00:00
|
|
|
<div className="option-info">
|
|
|
|
<Avatar
|
|
|
|
className="option-avatar"
|
|
|
|
role="user"
|
|
|
|
size="min"
|
|
|
|
source={group.avatarUrl}
|
|
|
|
userName={group.label}
|
|
|
|
/>
|
|
|
|
<Text
|
|
|
|
className="option-text option-text__group"
|
|
|
|
truncate={true}
|
|
|
|
noSelect={true}
|
|
|
|
fontSize="14px"
|
|
|
|
>
|
|
|
|
{label}
|
|
|
|
</Text>
|
|
|
|
</div>
|
|
|
|
{isMultiSelect && (
|
2019-12-14 13:16:01 +00:00
|
|
|
<Checkbox
|
|
|
|
value={`${index}`}
|
|
|
|
isIndeterminate={isIndeterminate}
|
2022-02-14 10:49:27 +00:00
|
|
|
className="option-checkbox"
|
2019-12-14 13:16:01 +00:00
|
|
|
/>
|
|
|
|
)}
|
2022-02-14 10:49:27 +00:00
|
|
|
</div>
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
},
|
2022-01-28 13:44:58 +00:00
|
|
|
[
|
2022-02-14 10:49:27 +00:00
|
|
|
isMultiSelect,
|
2022-01-28 13:44:58 +00:00
|
|
|
groups,
|
|
|
|
currentGroup,
|
|
|
|
selectedGroupList,
|
2022-02-14 10:49:27 +00:00
|
|
|
selectedOptionList,
|
|
|
|
getGroupSelectedOptions,
|
2022-01-28 13:44:58 +00:00
|
|
|
]
|
2019-12-14 13:16:01 +00:00
|
|
|
);
|
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const renderGroupsList = useCallback(() => {
|
|
|
|
return (
|
|
|
|
<AutoSizer>
|
|
|
|
{({ width, height }) => (
|
|
|
|
<List
|
|
|
|
className="options_list"
|
|
|
|
height={height - 8}
|
|
|
|
width={width + 8}
|
|
|
|
itemCount={groups.length}
|
|
|
|
itemSize={48}
|
|
|
|
outerElementType={CustomScrollbarsVirtualList}
|
|
|
|
>
|
|
|
|
{renderGroup}
|
|
|
|
</List>
|
|
|
|
)}
|
|
|
|
</AutoSizer>
|
|
|
|
);
|
|
|
|
}, [isMultiSelect, groups, selectedOptionList, getGroupSelectedOptions]);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const renderGroupHeader = useCallback(() => {
|
|
|
|
const selectedOption = getGroupSelectedOptions(groupHeader.id);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
const isIndeterminate = selectedOption.length > 0;
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
let label = groupHeader.label;
|
2019-12-14 13:16:01 +00:00
|
|
|
|
2022-02-14 10:49:27 +00:00
|
|
|
if (isMultiSelect && selectedOption.length > 0) {
|
|
|
|
label = `${groupHeader.label} (${selectedOption.length})`;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className="row-option row-header">
|
|
|
|
<div className="option-info">
|
|
|
|
<Avatar
|
|
|
|
className="option-avatar"
|
|
|
|
role="user"
|
|
|
|
size="min"
|
|
|
|
source={groupHeader.avatarUrl}
|
|
|
|
userName={groupHeader.label}
|
|
|
|
/>
|
|
|
|
<Text
|
|
|
|
className="option-text option-text__header"
|
|
|
|
truncate={true}
|
|
|
|
noSelect={true}
|
|
|
|
fontSize="14px"
|
|
|
|
>
|
|
|
|
{label}
|
|
|
|
</Text>
|
|
|
|
</div>
|
|
|
|
{isMultiSelect && (
|
|
|
|
<Checkbox
|
|
|
|
isIndeterminate={isIndeterminate}
|
|
|
|
className="option-checkbox"
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div className="option-separator"></div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}, [isMultiSelect, groupHeader, selectedOptionList, getGroupSelectedOptions]);
|
|
|
|
|
|
|
|
const onArrowClickAction = useCallback(() => {
|
|
|
|
if (groupHeader) {
|
|
|
|
setGroupHeader(null);
|
|
|
|
|
|
|
|
onGroupChanged && onGroupChanged([]);
|
|
|
|
setCurrentGroup([]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
onArrowClick && onArrowClick();
|
|
|
|
}, [groupHeader, onArrowClick, onGroupChanged]);
|
2019-12-14 13:16:01 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<StyledSelector
|
|
|
|
options={options}
|
|
|
|
groups={groups}
|
|
|
|
isMultiSelect={isMultiSelect}
|
2019-12-19 07:00:58 +00:00
|
|
|
allowGroupSelection={allowGroupSelection}
|
2019-12-14 13:16:01 +00:00
|
|
|
hasSelected={hasSelected()}
|
2022-01-28 13:44:58 +00:00
|
|
|
className="selector-wrapper"
|
|
|
|
>
|
2022-02-14 10:49:27 +00:00
|
|
|
<div className="header">
|
|
|
|
<IconButton
|
|
|
|
iconName="/static/images/arrow.path.react.svg"
|
|
|
|
size="17"
|
|
|
|
isFill={true}
|
|
|
|
className="arrow-button"
|
|
|
|
onClick={onArrowClickAction}
|
|
|
|
/>
|
|
|
|
<Heading size="medium" truncate={true}>
|
|
|
|
{headerLabel}
|
|
|
|
</Heading>
|
|
|
|
</div>
|
|
|
|
<Column className="column-options" size={size}>
|
2022-01-27 13:54:58 +00:00
|
|
|
<Header className="header-options">
|
2019-12-14 13:16:01 +00:00
|
|
|
<SearchInput
|
|
|
|
className="options_searcher"
|
|
|
|
isDisabled={isDisabled}
|
|
|
|
size="base"
|
|
|
|
scale={true}
|
|
|
|
isNeedFilter={false}
|
|
|
|
placeholder={searchPlaceHolderLabel}
|
|
|
|
value={searchValue}
|
|
|
|
onChange={onSearchChange}
|
|
|
|
onClearSearch={onSearchReset}
|
|
|
|
/>
|
|
|
|
</Header>
|
2022-01-27 13:54:58 +00:00
|
|
|
<Body className="body-options">
|
2022-02-14 10:49:27 +00:00
|
|
|
{!groupHeader && !searchValue ? (
|
|
|
|
renderGroupsList()
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
{!searchValue && renderGroupHeader()}
|
|
|
|
{!hasNextPage && itemCount === 0 ? (
|
|
|
|
<div className="row-option">
|
|
|
|
<Text>
|
|
|
|
{!searchValue ? emptyOptionsLabel : emptySearchOptionsLabel}
|
|
|
|
</Text>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<AutoSizer>
|
|
|
|
{({ width, height }) => (
|
|
|
|
<InfiniteLoader
|
|
|
|
ref={listOptionsRef}
|
|
|
|
isItemLoaded={isItemLoaded}
|
|
|
|
itemCount={itemCount}
|
|
|
|
loadMoreItems={loadMoreItems}
|
|
|
|
>
|
|
|
|
{({ onItemsRendered, ref }) => (
|
|
|
|
<List
|
|
|
|
className="options_list"
|
|
|
|
height={height - 25}
|
|
|
|
itemCount={itemCount}
|
|
|
|
itemSize={48}
|
|
|
|
onItemsRendered={onItemsRendered}
|
|
|
|
ref={ref}
|
|
|
|
width={width + 8}
|
|
|
|
outerElementType={CustomScrollbarsVirtualList}
|
|
|
|
>
|
|
|
|
{renderOption}
|
|
|
|
</List>
|
|
|
|
)}
|
|
|
|
</InfiniteLoader>
|
|
|
|
)}
|
|
|
|
</AutoSizer>
|
|
|
|
)}
|
|
|
|
</>
|
2019-12-14 13:16:01 +00:00
|
|
|
)}
|
2022-02-14 10:49:27 +00:00
|
|
|
|
2019-12-14 13:16:01 +00:00
|
|
|
{getOptionTooltipContent && (
|
2022-01-28 13:44:58 +00:00
|
|
|
<Tooltip
|
|
|
|
id="user"
|
|
|
|
offsetRight={90}
|
|
|
|
getContent={getOptionTooltipContent}
|
|
|
|
/>
|
2019-12-14 13:16:01 +00:00
|
|
|
)}
|
|
|
|
</Body>
|
|
|
|
</Column>
|
|
|
|
<Footer
|
|
|
|
className="footer"
|
|
|
|
selectButtonLabel={selectButtonLabel}
|
2020-10-29 07:44:17 +00:00
|
|
|
showCounter={showCounter}
|
2019-12-14 13:16:01 +00:00
|
|
|
isDisabled={isDisabled}
|
|
|
|
isVisible={isMultiSelect && hasSelected()}
|
|
|
|
onClick={onAddClick}
|
2020-03-30 11:31:50 +00:00
|
|
|
embeddedComponent={embeddedComponent}
|
2020-10-29 07:44:17 +00:00
|
|
|
selectedLength={selectedOptionList.length}
|
2019-12-14 13:16:01 +00:00
|
|
|
/>
|
|
|
|
</StyledSelector>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
Selector.propTypes = {
|
|
|
|
options: PropTypes.array,
|
|
|
|
groups: PropTypes.array,
|
|
|
|
|
|
|
|
hasNextPage: PropTypes.bool,
|
|
|
|
isNextPageLoading: PropTypes.bool,
|
|
|
|
loadNextPage: PropTypes.func,
|
|
|
|
|
|
|
|
isDisabled: PropTypes.bool,
|
|
|
|
isMultiSelect: PropTypes.bool,
|
2019-12-19 07:00:58 +00:00
|
|
|
allowGroupSelection: PropTypes.bool,
|
2019-12-14 13:16:01 +00:00
|
|
|
|
|
|
|
selectButtonLabel: PropTypes.string,
|
|
|
|
selectAllLabel: PropTypes.string,
|
|
|
|
searchPlaceHolderLabel: PropTypes.string,
|
|
|
|
groupsHeaderLabel: PropTypes.string,
|
|
|
|
emptySearchOptionsLabel: PropTypes.string,
|
|
|
|
emptyOptionsLabel: PropTypes.string,
|
|
|
|
loadingLabel: PropTypes.string,
|
|
|
|
|
2022-01-28 13:44:58 +00:00
|
|
|
size: PropTypes.oneOf(["compact", "full"]),
|
2019-12-14 13:16:01 +00:00
|
|
|
|
|
|
|
selectedOptions: PropTypes.array,
|
|
|
|
selectedGroups: PropTypes.array,
|
|
|
|
|
|
|
|
onSelect: PropTypes.func,
|
|
|
|
onSearchChanged: PropTypes.func,
|
|
|
|
onGroupChanged: PropTypes.func,
|
2020-03-28 10:20:27 +00:00
|
|
|
getOptionTooltipContent: PropTypes.func,
|
|
|
|
|
2020-10-16 12:38:04 +00:00
|
|
|
embeddedComponent: PropTypes.any,
|
2019-12-14 13:16:01 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Selector.defaultProps = {
|
2022-01-28 13:44:58 +00:00
|
|
|
size: "full",
|
2019-12-14 13:16:01 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export default Selector;
|