web: Components: Attempt to re-write selector and fix check options
This commit is contained in:
parent
eae2a45b71
commit
b445e25701
@ -0,0 +1,220 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import {
|
||||
withKnobs
|
||||
} from "@storybook/addon-knobs/react";
|
||||
import withReadme from "storybook-readme/with-readme";
|
||||
import Readme from "./README.md";
|
||||
import { FixedSizeList as List } from "react-window";
|
||||
import InfiniteLoader from "react-window-infinite-loader";
|
||||
import faker, { name } from "faker";
|
||||
import uniqueId from "lodash/uniqueId";
|
||||
import Section from "../../../.storybook/decorators/section";
|
||||
//import ADSelectorRow from "./sub-components/row";
|
||||
import Checkbox from "../checkbox";
|
||||
import CustomScrollbarsVirtualList from "../scrollbar/custom-scrollbars-virtual-list";
|
||||
import DropDown from "../drop-down";
|
||||
|
||||
const ExampleWrapper = ({
|
||||
// Are there more items to load?
|
||||
// (This information comes from the most recent API request.)
|
||||
hasNextPage,
|
||||
|
||||
// Are we currently loading a page of items?
|
||||
// (This may be an in-flight flag in your Redux store for example.)
|
||||
isNextPageLoading,
|
||||
|
||||
// Array of items loaded so far.
|
||||
items,
|
||||
|
||||
// Callback function responsible for loading the next page of items.
|
||||
loadNextPage,
|
||||
|
||||
sortOrder
|
||||
}) => {
|
||||
// We create a reference for the InfiniteLoader
|
||||
const listRef = useRef(null);
|
||||
const hasMountedRef = useRef(false);
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
// Each time the sort prop changed we called the method resetloadMoreItemsCache to clear the cache
|
||||
useEffect(() => {
|
||||
if (listRef.current && hasMountedRef.current) {
|
||||
listRef.current.resetloadMoreItemsCache();
|
||||
}
|
||||
hasMountedRef.current = true;
|
||||
}, [sortOrder]);
|
||||
|
||||
// If there are more items to be loaded then add an extra row to hold a loading indicator.
|
||||
const itemCount = hasNextPage ? items.length + 1 : items.length;
|
||||
|
||||
// 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 = isNextPageLoading ? () => {} : loadNextPage;
|
||||
|
||||
// Every row is loaded except for our loading indicator row.
|
||||
const isItemLoaded = index => !hasNextPage || index < items.length;
|
||||
|
||||
const onChange = (e, item) => {
|
||||
const newSelected = e.target.checked
|
||||
? [item, ...selected]
|
||||
: selected.filter(el => el.name !== item.name);
|
||||
//console.log("OnChange", newSelected);
|
||||
setSelected(newSelected);
|
||||
};
|
||||
|
||||
const onSelect = (e, item) => {
|
||||
console.log("onSelect", item);
|
||||
};
|
||||
|
||||
// Render an item or a loading indicator.
|
||||
const Item = ({ index, style }) => {
|
||||
let content;
|
||||
if (!isItemLoaded(index)) {
|
||||
content = "Loading...";
|
||||
} else {
|
||||
const item = items[index];
|
||||
const checked = selected.findIndex(el => el.id === item.id) > -1;
|
||||
const newStyle = Object.assign({}, style, {
|
||||
padding: "0 0.5rem",
|
||||
lineHeight: "30px"
|
||||
});
|
||||
//console.log("Item render", item, checked, selected);
|
||||
content = (
|
||||
<Checkbox
|
||||
key={item.id}
|
||||
label={item.name}
|
||||
isChecked={checked}
|
||||
className="option_checkbox"
|
||||
onChange={e => onChange(e, item)}
|
||||
/>
|
||||
/*<>
|
||||
<input
|
||||
id={item.id}
|
||||
type="checkbox"
|
||||
onChange={e => onChange(e, item)}
|
||||
checked={checked}
|
||||
/>
|
||||
<label htmlFor={item.id}>{item.name}</label>
|
||||
</>*/
|
||||
/*<ADSelectorRow
|
||||
key={item.id}
|
||||
label={item.name}
|
||||
isChecked={checked}
|
||||
isMultiSelect={true}
|
||||
isSelected={false}
|
||||
className="ListItem"
|
||||
style={{ padding: "0 0.5rem", lineHeight: "30px" }}
|
||||
onChange={e => onChange(e, item)}
|
||||
onSelect={e => onSelect(e, item)}
|
||||
/>*/
|
||||
);
|
||||
}
|
||||
|
||||
return <div style={style}>{content}</div>;
|
||||
};
|
||||
|
||||
// We passed down the ref to the InfiniteLoader component
|
||||
return (
|
||||
<InfiniteLoader
|
||||
ref={listRef}
|
||||
isItemLoaded={isItemLoaded}
|
||||
itemCount={itemCount}
|
||||
loadMoreItems={loadMoreItems}
|
||||
>
|
||||
{({ onItemsRendered, ref }) => (
|
||||
<List
|
||||
className="List"
|
||||
style={{border: "1px solid #ddd", borderRadius: "0.25rem"}}
|
||||
height={300}
|
||||
itemCount={itemCount}
|
||||
itemSize={30}
|
||||
onItemsRendered={onItemsRendered}
|
||||
ref={ref}
|
||||
width={500}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{Item}
|
||||
</List>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
);
|
||||
}
|
||||
|
||||
class ADSelectorExample extends React.PureComponent {
|
||||
state = {
|
||||
hasNextPage: true,
|
||||
isNextPageLoading: false,
|
||||
sortOrder: "asc",
|
||||
items: []
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
faker.seed(123);
|
||||
this.persons = new Array(1000)
|
||||
.fill(true)
|
||||
.map(() => ({ name: name.findName(), id: uniqueId() }));
|
||||
this.persons.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
_loadNextPage = (...args) => {
|
||||
this.setState({ isNextPageLoading: true }, () => {
|
||||
setTimeout(() => {
|
||||
this.setState(state => ({
|
||||
hasNextPage: state.items.length < 100,
|
||||
isNextPageLoading: false,
|
||||
items: [...state.items].concat(
|
||||
this.persons.slice(args[0], args[0] + 10)
|
||||
)
|
||||
}));
|
||||
}, 2500);
|
||||
});
|
||||
};
|
||||
|
||||
_handleSortOrderChange = e => {
|
||||
this.persons.sort((a, b) => {
|
||||
if (e.target.value === "asc") {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
return b.name.localeCompare(a.name);
|
||||
});
|
||||
this.setState({
|
||||
sortOrder: e.target.value,
|
||||
items: []
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { hasNextPage, isNextPageLoading, items, sortOrder } = this.state;
|
||||
return (
|
||||
<>
|
||||
<div style={{position: "relative"}}>
|
||||
<select onChange={this._handleSortOrderChange}>
|
||||
<option value="asc">ASC</option>
|
||||
<option value="desc">DESC</option>
|
||||
</select>
|
||||
</div>
|
||||
<ExampleWrapper
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
items={items}
|
||||
sortOrder={sortOrder}
|
||||
loadNextPage={this._loadNextPage}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
storiesOf("Components|AdvancedSelector", module)
|
||||
.addDecorator(withKnobs)
|
||||
.addDecorator(withReadme(Readme))
|
||||
.add("only list", () => {
|
||||
return (
|
||||
<Section>
|
||||
<ADSelectorExample />
|
||||
</Section>
|
||||
);
|
||||
});
|
@ -1,19 +1,44 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import CustomScrollbarsVirtualList from "../../../scrollbar/custom-scrollbars-virtual-list";
|
||||
import { FixedSizeList } from "react-window";
|
||||
import { FixedSizeList as List } from "react-window";
|
||||
import InfiniteLoader from "react-window-infinite-loader";
|
||||
import ADSelectorRow from "../row";
|
||||
//import ADSelectorRow from "../row";
|
||||
import Checkbox from "../../../checkbox";
|
||||
import Loader from "../../../loader";
|
||||
import { Text } from "../../../text";
|
||||
import findIndex from "lodash/findIndex";
|
||||
//import isEqual from "lodash/isEqual";
|
||||
|
||||
class ADSelectorOptionsBody extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.listRef = React.createRef();
|
||||
//this.hasMountedRef = React.createRef(false);
|
||||
}
|
||||
|
||||
/*componentDidMount() {
|
||||
this.hasMountedRef.current = true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if(!isEqual(this.props.selectedOptions, prevProps.selectedOptions)) {
|
||||
if(this.listRef.current && this.hasMountedRef.current) {
|
||||
//this.listRef.current.resetloadMoreItemsCache(true);
|
||||
//console.log("this.listRef.current", this.listRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
renderRow = ({ index, style }) => {
|
||||
//console.log("renderRow", option, isChecked, this.state.selectedOptions);
|
||||
|
||||
let content;
|
||||
if (!this.isItemLoaded(index)) {
|
||||
const isLoaded = this.isItemLoaded(index);
|
||||
let key = "loader";
|
||||
if (!isLoaded) {
|
||||
content = (
|
||||
<div className="option" style={style} key="loader">
|
||||
<Loader
|
||||
@ -34,9 +59,17 @@ class ADSelectorOptionsBody extends React.Component {
|
||||
? selectedAll ||
|
||||
findIndex(selectedOptions, { key: option.key }) > -1
|
||||
: undefined;
|
||||
key = option.key;
|
||||
|
||||
content = (
|
||||
<ADSelectorRow
|
||||
<Checkbox
|
||||
key={option.key}
|
||||
label={option.label}
|
||||
isChecked={isChecked}
|
||||
className="option_checkbox"
|
||||
onChange={this.props.onRowChecked.bind(this, option)}
|
||||
/>
|
||||
/*<ADSelectorRow
|
||||
key={option.key}
|
||||
label={option.label}
|
||||
isChecked={isChecked}
|
||||
@ -44,11 +77,23 @@ class ADSelectorOptionsBody extends React.Component {
|
||||
style={style}
|
||||
onChange={this.props.onRowChecked.bind(this, option)}
|
||||
onSelect={this.props.onRowSelect.bind(this, option)}
|
||||
/>
|
||||
/>*/
|
||||
/*<div className="option" style={style}>
|
||||
<input
|
||||
id={option.key}
|
||||
type="checkbox"
|
||||
onChange={(e) => {
|
||||
console.log("checkbox click", e);
|
||||
this.props.onRowChecked(option, e);
|
||||
}}
|
||||
checked={isChecked}
|
||||
/>
|
||||
<label htmlFor={option.key}>{option.label}</label>
|
||||
</div>*/
|
||||
);
|
||||
}
|
||||
|
||||
return <>{content}</>;
|
||||
return <div className="option" style={style} key={key}>{content}</div>;
|
||||
};
|
||||
|
||||
isItemLoaded = index =>
|
||||
@ -71,12 +116,13 @@ class ADSelectorOptionsBody extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<InfiniteLoader
|
||||
ref={this.listRef}
|
||||
isItemLoaded={this.isItemLoaded}
|
||||
itemCount={itemCount}
|
||||
loadMoreItems={this.loadMoreItems}
|
||||
>
|
||||
{({ onItemsRendered, ref }) => (
|
||||
<FixedSizeList
|
||||
<List
|
||||
className="options_list"
|
||||
height={listHeight}
|
||||
width={listWidth}
|
||||
@ -87,7 +133,7 @@ class ADSelectorOptionsBody extends React.Component {
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{this.renderRow}
|
||||
</FixedSizeList>
|
||||
</List>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
</div>
|
||||
|
@ -15,10 +15,10 @@ class ADSelectorRow extends React.Component {
|
||||
isSelected
|
||||
} = this.props;
|
||||
|
||||
console.log("ADSelectorRow render", label, isChecked);
|
||||
//console.log("ADSelectorRow render", label, isChecked);
|
||||
|
||||
return (
|
||||
<div className={`option ${isSelected && "selected"}`} style={style}>
|
||||
<div className={`option${isSelected ? "selected" : ""}`} style={style}>
|
||||
{isMultiSelect ? (
|
||||
<Checkbox
|
||||
label={label}
|
||||
|
@ -1,21 +1,18 @@
|
||||
import React from "react";
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled, { css } from "styled-components";
|
||||
import { FixedSizeList as List } from "react-window";
|
||||
import InfiniteLoader from "react-window-infinite-loader";
|
||||
import Checkbox from "../../checkbox";
|
||||
import ComboBox from "../../combobox";
|
||||
|
||||
import filter from "lodash/filter";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
|
||||
import Loader from "../../loader";
|
||||
import { Text } from "../../text";
|
||||
import CustomScrollbarsVirtualList from "../../scrollbar/custom-scrollbars-virtual-list";
|
||||
import ADSelectorOptionsHeader from "./options/header";
|
||||
import ADSelectorOptionsBody from "./options/body";
|
||||
import ADSelectorGroupsHeader from "./groups/header";
|
||||
import ADSelectorGroupsBody from "./groups/body";
|
||||
import ADSelectorFooter from "./footer";
|
||||
|
||||
import { handleAnyClick } from "../../../utils/event";
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable react/prop-types */
|
||||
const Container = ({
|
||||
@ -53,253 +50,182 @@ const Container = ({
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
const StyledContainer = styled(Container)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
${props => (props.containerWidth ? `width: ${props.containerWidth};` : "")}
|
||||
${props => (props.containerHeight ? `height: ${props.containerHeight};` : "")}
|
||||
|
||||
.data_container {
|
||||
margin: 16px 16px -5px 16px;
|
||||
|
||||
.head_container {
|
||||
display: flex;
|
||||
margin-bottom: ${props => (props.displayType === "dropdown" ? 8 : 16)}px;
|
||||
|
||||
.options_searcher {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
${props => (props.containerWidth ? `width: ${props.containerWidth};` : "")}
|
||||
${props =>
|
||||
props.containerHeight ? `height: ${props.containerHeight};` : ""}
|
||||
|
||||
.data_container {
|
||||
margin: 16px 16px -5px 16px;
|
||||
|
||||
.head_container {
|
||||
display: flex;
|
||||
margin-bottom: ${props =>
|
||||
props.displayType === "dropdown" ? 8 : 16}px;
|
||||
|
||||
.options_searcher {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
${props =>
|
||||
props.displayType === "dropdown" &&
|
||||
props.size === "full" &&
|
||||
css`
|
||||
margin-right: ${props => (props.allowCreation ? 8 : 16)}px;
|
||||
`}
|
||||
/*${props =>
|
||||
props.allowCreation
|
||||
? css`
|
||||
width: 272px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
: css`
|
||||
width: ${props => (props.isDropDown ? "313px" : "100%")};
|
||||
`}*/
|
||||
}
|
||||
|
||||
.add_new_btn {
|
||||
${props =>
|
||||
props.allowCreation &&
|
||||
css`
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 32px;
|
||||
width: 36px;
|
||||
margin-right: 16px;
|
||||
line-height: 18px;
|
||||
`}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.options_group_selector {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.data_column_one {
|
||||
${props =>
|
||||
props.size === "full" &&
|
||||
props.displayType === "dropdown" &&
|
||||
props.groups &&
|
||||
props.groups.length > 0
|
||||
? css`
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
`
|
||||
: ""}
|
||||
|
||||
.options_list {
|
||||
margin-top: 4px;
|
||||
margin-left: -8px;
|
||||
.option {
|
||||
line-height: 32px;
|
||||
padding-left: ${props => (props.isMultiSelect ? 8 : 0)}px;
|
||||
cursor: pointer;
|
||||
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
|
||||
.option_link {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f9f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data_column_two {
|
||||
${props =>
|
||||
props.displayType === "dropdown" &&
|
||||
props.size === "full" &&
|
||||
css`
|
||||
margin-right: ${props => (props.allowCreation ? 8 : 16)}px;
|
||||
`}
|
||||
/*${props =>
|
||||
props.allowCreation
|
||||
props.groups &&
|
||||
props.groups.length > 0
|
||||
? css`
|
||||
width: 272px;
|
||||
margin-right: 8px;
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
border-left: 1px solid #eceef1;
|
||||
`
|
||||
: css`
|
||||
width: ${props => (props.isDropDown ? "313px" : "100%")};
|
||||
`}*/
|
||||
}
|
||||
|
||||
.add_new_btn {
|
||||
${props =>
|
||||
props.allowCreation &&
|
||||
css`
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 32px;
|
||||
width: 36px;
|
||||
margin-right: 16px;
|
||||
line-height: 18px;
|
||||
`}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.options_group_selector {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.data_column_one {
|
||||
${props =>
|
||||
props.size === "full" &&
|
||||
props.displayType === "dropdown" &&
|
||||
props.groups &&
|
||||
props.groups.length > 0
|
||||
? css`
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
`
|
||||
: ""}
|
||||
|
||||
.options_list {
|
||||
margin-top: 4px;
|
||||
margin-left: -8px;
|
||||
.option {
|
||||
line-height: 32px;
|
||||
padding-left: ${props => (props.isMultiSelect ? 8 : 0)}px;
|
||||
cursor: pointer;
|
||||
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
|
||||
.option_link {
|
||||
: ""}
|
||||
|
||||
.group_header {
|
||||
font-weight: 600;
|
||||
padding-left: 16px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.group_list {
|
||||
margin-left: 8px;
|
||||
|
||||
.option {
|
||||
line-height: 32px;
|
||||
padding-left: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
|
||||
.option_link {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f9f9;
|
||||
|
||||
.option.selected {
|
||||
background-color: #ECEEF1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
.data_column_two {
|
||||
${props =>
|
||||
props.displayType === "dropdown" &&
|
||||
props.groups &&
|
||||
props.groups.length > 0
|
||||
? css`
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
border-left: 1px solid #eceef1;
|
||||
`
|
||||
: ""}
|
||||
const ADSelector = props => {
|
||||
const {
|
||||
options,
|
||||
groups,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
loadNextPage,
|
||||
value,
|
||||
placeholder,
|
||||
isDisabled,
|
||||
onSearchChanged,
|
||||
isMultiSelect,
|
||||
buttonLabel,
|
||||
selectAllLabel,
|
||||
size,
|
||||
displayType,
|
||||
onAddNewClick,
|
||||
allowCreation,
|
||||
selectedOptions,
|
||||
selectedGroups,
|
||||
onSelect
|
||||
} = props;
|
||||
|
||||
.group_header {
|
||||
font-weight: 600;
|
||||
padding-left: 16px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
// We create a reference for the InfiniteLoader
|
||||
const listRef = useRef(null);
|
||||
//const hasMountedRef = useRef(false);
|
||||
const [selected, setSelected] = useState(selectedOptions || []);
|
||||
const [selectedGrps, setSelectedGroups] = useState(selectedGroups || []);
|
||||
const [selectedAll, setSelectedAll] = useState(false);
|
||||
|
||||
.group_list {
|
||||
margin-left: 8px;
|
||||
const convertGroups = items => {
|
||||
if (!items) return [];
|
||||
|
||||
.option {
|
||||
line-height: 32px;
|
||||
padding-left: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
|
||||
.option_link {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
.option.selected {
|
||||
background-color: #ECEEF1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class ADSelector extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.ref = React.createRef();
|
||||
|
||||
const { groups, selectedOptions, selectedGroups, selectedAll, isOpen } = props;
|
||||
|
||||
const convertedGroups = this.convertGroups(groups);
|
||||
const currentGroup = this.getCurrentGroup(convertedGroups);
|
||||
|
||||
this.state = {
|
||||
selectedOptions: selectedOptions || [],
|
||||
selectedAll: selectedAll || false,
|
||||
groups: convertedGroups,
|
||||
selectedGroups: selectedGroups || [],
|
||||
currentGroup: currentGroup
|
||||
};
|
||||
|
||||
if (isOpen) handleAnyClick(true, this.handleClick);
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const { onCancel, allowAnyClickClose, isOpen } = this.props;
|
||||
|
||||
if (
|
||||
isOpen &&
|
||||
allowAnyClickClose &&
|
||||
this.ref &&
|
||||
this.ref.current &&
|
||||
!this.ref.current.contains(e.target) &&
|
||||
e &&
|
||||
e.target &&
|
||||
e.target.className &&
|
||||
typeof e.target.className.indexOf === "function" &&
|
||||
e.target.className.indexOf("option_checkbox") === -1
|
||||
) {
|
||||
onCancel && onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
handleAnyClick(false, this.handleClick);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
groups,
|
||||
selectedAll,
|
||||
isMultiSelect,
|
||||
selectedOptions,
|
||||
selectedGroups,
|
||||
allowAnyClickClose,
|
||||
isOpen
|
||||
} = this.props;
|
||||
|
||||
if (isOpen !== prevProps.isOpen) {
|
||||
handleAnyClick(isOpen, this.handleClick);
|
||||
}
|
||||
|
||||
if (allowAnyClickClose !== prevProps.allowAnyClickClose) {
|
||||
handleAnyClick(allowAnyClickClose, this.handleClick);
|
||||
}
|
||||
|
||||
let newState = {};
|
||||
|
||||
if (!isEqual(selectedOptions, prevProps.selectedOptions)) {
|
||||
newState = { selectedOptions };
|
||||
}
|
||||
|
||||
if (!isEqual(selectedGroups, prevProps.selectedGroups)) {
|
||||
newState = Object.assign({}, newState, { selectedGroups });
|
||||
}
|
||||
if (isMultiSelect !== prevProps.isMultiSelect) {
|
||||
newState = Object.assign({}, newState, {
|
||||
selectedOptions: []
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedAll !== prevProps.selectedAll) {
|
||||
newState = Object.assign({}, newState, {
|
||||
selectedAll
|
||||
});
|
||||
}
|
||||
|
||||
if (!isEqual(groups, prevProps.groups)) {
|
||||
const newGroups = this.convertGroups(groups);
|
||||
const currentGroup = this.getCurrentGroup(newGroups);
|
||||
newState = Object.assign({}, newState, {
|
||||
groups: newGroups,
|
||||
currentGroup
|
||||
});
|
||||
}
|
||||
|
||||
if (!isEmpty(newState)) {
|
||||
this.setState({ ...this.state, ...newState });
|
||||
}
|
||||
}
|
||||
|
||||
convertGroups = groups => {
|
||||
if (!groups) return [];
|
||||
|
||||
const wrappedGroups = groups.map(this.convertGroup);
|
||||
const wrappedGroups = items.map(convertGroup);
|
||||
|
||||
return wrappedGroups;
|
||||
};
|
||||
|
||||
convertGroup = group => {
|
||||
const convertGroup = group => {
|
||||
return {
|
||||
key: group.key,
|
||||
label: `${group.label} (${group.total})`,
|
||||
@ -307,231 +233,259 @@ class ADSelector extends React.Component {
|
||||
};
|
||||
};
|
||||
|
||||
getCurrentGroup = groups => {
|
||||
const currentGroup = groups.length > 0 ? groups[0] : "No groups";
|
||||
const getCurrentGroup = items => {
|
||||
const currentGroup = items.length > 0 ? items[0] : "No groups";
|
||||
return currentGroup;
|
||||
};
|
||||
|
||||
onButtonClick = () => {
|
||||
this.props.onSelect &&
|
||||
this.props.onSelect(
|
||||
this.state.selectedAll ? this.props.options : this.state.selectedOptions
|
||||
);
|
||||
const convertedGroups = convertGroups(groups);
|
||||
const curGroup = getCurrentGroup(convertedGroups);
|
||||
|
||||
const [currentGroup, setCurrentGroup] = useState(curGroup);
|
||||
|
||||
// Each time the sort prop changed we called the method resetloadMoreItemsCache to clear the cache
|
||||
/*useEffect(() => {
|
||||
if (listRef.current && hasMountedRef.current) {
|
||||
listRef.current.resetloadMoreItemsCache();
|
||||
}
|
||||
hasMountedRef.current = true;
|
||||
}, [sortOrder]);*/
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 = isNextPageLoading ? () => {} : loadNextPage;
|
||||
|
||||
// Every row is loaded except for our loading indicator row.
|
||||
const isItemLoaded = index => !hasNextPage || index < options.length;
|
||||
|
||||
const onChange = (e, item) => {
|
||||
const newSelected = e.target.checked
|
||||
? [item, ...selected]
|
||||
: selected.filter(el => el.name !== item.name);
|
||||
//console.log("OnChange", newSelected);
|
||||
setSelected(newSelected);
|
||||
};
|
||||
|
||||
onSelectedAllChange = e => {
|
||||
this.setState({
|
||||
selectedAll: e.target.checked,
|
||||
selectedOptions: e.target.checked ? this.props.options : []
|
||||
});
|
||||
};
|
||||
|
||||
onOptionSelect = option => {
|
||||
this.props.onSelect && this.props.onSelect(option);
|
||||
};
|
||||
|
||||
onOptionChange = (option, e) => {
|
||||
const { selectedOptions } = this.state;
|
||||
const newSelectedOptions = e.target.checked
|
||||
? [...selectedOptions, option]
|
||||
: filter(selectedOptions, obj => obj.key !== option.key);
|
||||
|
||||
//console.log("onChange", option, e.target.checked, newSelectedOptions);
|
||||
|
||||
this.setState({
|
||||
selectedOptions: newSelectedOptions,
|
||||
selectedAll: newSelectedOptions.length === this.props.options.length
|
||||
});
|
||||
};
|
||||
|
||||
onGroupChange = (group, e) => {
|
||||
/*this.setState({
|
||||
currentGroup: group
|
||||
});
|
||||
|
||||
this.props.onGroupChange && this.props.onGroupChange(group);*/
|
||||
|
||||
const { selectedGroups } = this.state;
|
||||
const onGroupChange = (e, item) => {
|
||||
const newSelectedGroups = e.target.checked
|
||||
? [...selectedGroups, group]
|
||||
: filter(selectedGroups, obj => obj.key !== group.key);
|
||||
|
||||
//console.log("onChange", option, e.target.checked, newSelectedOptions);
|
||||
|
||||
this.setState({
|
||||
selectedGroups: newSelectedGroups,
|
||||
currentGroup: group
|
||||
});
|
||||
|
||||
this.props.onGroupSelect && this.props.onGroupSelect(group);
|
||||
? [item, ...selectedGrps]
|
||||
: selectedGrps.filter(el => el.name !== item.name);
|
||||
//console.log("onGroupChange", item);
|
||||
setSelectedGroups(newSelectedGroups);
|
||||
};
|
||||
const onCurrentGroupChange = (e, item) => {
|
||||
//console.log("onCurrentGroupChange", item);
|
||||
setCurrentGroup(item);
|
||||
};
|
||||
const onSelectedAllChange = (e, item) => {
|
||||
//console.log("onSelectedAllChange", item);
|
||||
setSelectedAll(item);
|
||||
};
|
||||
|
||||
render() {
|
||||
//console.log("ADSelector render");
|
||||
const {
|
||||
options,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
loadNextPage,
|
||||
value,
|
||||
placeholder,
|
||||
isDisabled,
|
||||
onSearchChanged,
|
||||
isMultiSelect,
|
||||
buttonLabel,
|
||||
selectAllLabel,
|
||||
size,
|
||||
displayType,
|
||||
onAddNewClick,
|
||||
allowCreation
|
||||
} = this.props;
|
||||
const onButtonClick = () => {
|
||||
onSelect && onSelect(selectedAll ? options : selectedOptions);
|
||||
};
|
||||
|
||||
const { selectedOptions, selectedAll, currentGroup, groups, selectedGroups } = this.state;
|
||||
|
||||
let containerHeight;
|
||||
let containerWidth;
|
||||
let listHeight;
|
||||
let listWidth;
|
||||
const itemHeight = 32;
|
||||
const hasGroups = groups && groups.length > 0;
|
||||
|
||||
switch (size) {
|
||||
case "compact":
|
||||
containerHeight = hasGroups ? "326px" : "100%";
|
||||
containerWidth = "379px";
|
||||
listWidth = displayType === "dropdown" ? 356 : 356;
|
||||
listHeight = hasGroups ? 488 : isMultiSelect ? 176 : 226;
|
||||
break;
|
||||
case "full":
|
||||
default:
|
||||
containerHeight = "100%";
|
||||
containerWidth = displayType === "dropdown" ? "690px" : "326px";
|
||||
listWidth = displayType === "dropdown" ? 320 : 300;
|
||||
listHeight = 488;
|
||||
break;
|
||||
// Render an item or a loading indicator.
|
||||
const Item = ({ index, style }) => {
|
||||
let content;
|
||||
if (!isItemLoaded(index)) {
|
||||
content = <div className="option" style={style} key="loader">
|
||||
<Loader
|
||||
type="oval"
|
||||
size={16}
|
||||
style={{
|
||||
display: "inline",
|
||||
marginRight: "10px"
|
||||
}}
|
||||
/>
|
||||
<Text.Body as="span">Loading... Please wait...</Text.Body>
|
||||
</div>;
|
||||
} else {
|
||||
const option = options[index];
|
||||
const checked = selected.findIndex(el => el.key === option.key) > -1;
|
||||
//console.log("Item render", item, checked, selected);
|
||||
content = (
|
||||
<Checkbox
|
||||
key={option.key}
|
||||
label={option.label}
|
||||
isChecked={checked}
|
||||
className="option_checkbox"
|
||||
onChange={e => onChange(e, option)}
|
||||
/>
|
||||
/*<>
|
||||
<input
|
||||
id={item.id}
|
||||
type="checkbox"
|
||||
onChange={e => onChange(e, item)}
|
||||
checked={checked}
|
||||
/>
|
||||
<label htmlFor={item.id}>{item.name}</label>
|
||||
</>*/
|
||||
/*<ADSelectorRow
|
||||
key={item.id}
|
||||
label={item.name}
|
||||
isChecked={checked}
|
||||
isMultiSelect={true}
|
||||
isSelected={false}
|
||||
className="ListItem"
|
||||
style={{ padding: "0 0.5rem", lineHeight: "30px" }}
|
||||
onChange={e => onChange(e, item)}
|
||||
onSelect={e => onSelect(e, item)}
|
||||
/>*/
|
||||
);
|
||||
}
|
||||
|
||||
// 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;
|
||||
return <div style={style}>{content}</div>;
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
let containerHeight;
|
||||
let containerWidth;
|
||||
let listHeight;
|
||||
let listWidth;
|
||||
const itemHeight = 32;
|
||||
const hasGroups = convertedGroups && convertedGroups.length > 0;
|
||||
|
||||
switch (size) {
|
||||
case "compact":
|
||||
containerHeight = hasGroups ? "326px" : "100%";
|
||||
containerWidth = "379px";
|
||||
listWidth = displayType === "dropdown" ? 356 : 356;
|
||||
listHeight = hasGroups ? 488 : isMultiSelect ? 176 : 226;
|
||||
break;
|
||||
case "full":
|
||||
default:
|
||||
containerHeight = "100%";
|
||||
containerWidth = displayType === "dropdown" ? "690px" : "326px";
|
||||
listWidth = displayType === "dropdown" ? 320 : 300;
|
||||
listHeight = 488;
|
||||
break;
|
||||
}
|
||||
|
||||
// We passed down the ref to the InfiniteLoader component
|
||||
return (
|
||||
<StyledContainer
|
||||
containerHeight={containerHeight}
|
||||
containerWidth={containerWidth}
|
||||
{...this.props}
|
||||
>
|
||||
<div ref={this.ref}>
|
||||
<div className="data_container">
|
||||
<div className="data_column_one">
|
||||
<ADSelectorOptionsHeader
|
||||
value={value}
|
||||
searchPlaceHolder={placeholder}
|
||||
isDisabled={isDisabled}
|
||||
allowCreation={allowCreation}
|
||||
onAddNewClick={onAddNewClick}
|
||||
onChange={onSearchChanged}
|
||||
onClearSearch={onSearchChanged.bind(this, "")}
|
||||
/>
|
||||
{displayType === "aside" && groups && groups.length > 0 && (
|
||||
<ComboBox
|
||||
className="options_group_selector"
|
||||
isDisabled={isDisabled}
|
||||
options={groups}
|
||||
selectedOption={currentGroup}
|
||||
dropDownMaxHeight={200}
|
||||
scaled={true}
|
||||
scaledOptions={true}
|
||||
size="content"
|
||||
onSelect={this.onCurrentGroupChange}
|
||||
/>
|
||||
)}
|
||||
{isMultiSelect && !groups && !groups.length && (
|
||||
<Checkbox
|
||||
label={selectAllLabel}
|
||||
isChecked={
|
||||
selectedAll || selectedOptions.length === options.length
|
||||
}
|
||||
isIndeterminate={!selectedAll && selectedOptions.length > 0}
|
||||
className="option_select_all_checkbox"
|
||||
onChange={this.onSelectedAllChange}
|
||||
/>
|
||||
)}
|
||||
<ADSelectorOptionsBody
|
||||
options={options}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={loadNextPage}
|
||||
{...props}
|
||||
>
|
||||
<div className="data_container">
|
||||
<div className="data_column_one">
|
||||
<ADSelectorOptionsHeader
|
||||
value={value}
|
||||
searchPlaceHolder={placeholder}
|
||||
isDisabled={isDisabled}
|
||||
allowCreation={allowCreation}
|
||||
onAddNewClick={onAddNewClick}
|
||||
onChange={onSearchChanged}
|
||||
onClearSearch={onSearchChanged.bind(this, "")}
|
||||
/>
|
||||
{displayType === "aside" && convertedGroups && convertedGroups.length > 0 && (
|
||||
<ComboBox
|
||||
className="options_group_selector"
|
||||
isDisabled={isDisabled}
|
||||
options={convertedGroups}
|
||||
selectedOption={currentGroup}
|
||||
dropDownMaxHeight={200}
|
||||
scaled={true}
|
||||
scaledOptions={true}
|
||||
size="content"
|
||||
onSelect={onCurrentGroupChange}
|
||||
/>
|
||||
)}
|
||||
{isMultiSelect && !convertedGroups && !convertedGroups.length && (
|
||||
<Checkbox
|
||||
label={selectAllLabel}
|
||||
isChecked={
|
||||
selectedAll || selectedOptions.length === options.length
|
||||
}
|
||||
isIndeterminate={!selectedAll && selectedOptions.length > 0}
|
||||
className="option_select_all_checkbox"
|
||||
onChange={onSelectedAllChange}
|
||||
/>
|
||||
)}
|
||||
<InfiniteLoader
|
||||
ref={listRef}
|
||||
isItemLoaded={isItemLoaded}
|
||||
itemCount={itemCount}
|
||||
loadMoreItems={loadMoreItems}
|
||||
>
|
||||
{({ onItemsRendered, ref }) => (
|
||||
<List
|
||||
className="options_list"
|
||||
//style={{ border: "1px solid #ddd", borderRadius: "0.25rem" }}
|
||||
height={listHeight}
|
||||
itemCount={itemCount}
|
||||
itemSize={itemHeight}
|
||||
onItemsRendered={onItemsRendered}
|
||||
ref={ref}
|
||||
width={listWidth}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{Item}
|
||||
</List>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
</div>
|
||||
{displayType === "dropdown" &&
|
||||
size === "full" &&
|
||||
convertedGroups &&
|
||||
convertedGroups.length > 0 && (
|
||||
<div className="data_column_two">
|
||||
<ADSelectorGroupsHeader headerLabel="Groups" />
|
||||
<ADSelectorGroupsBody
|
||||
options={convertedGroups}
|
||||
selectedOptions={selectedGrps}
|
||||
isMultiSelect={isMultiSelect}
|
||||
currentGroup={currentGroup}
|
||||
listHeight={listHeight}
|
||||
listWidth={listWidth}
|
||||
itemHeight={itemHeight}
|
||||
onRowChecked={this.onOptionChange}
|
||||
onRowSelect={this.onOptionSelect}
|
||||
selectedOptions={selectedOptions}
|
||||
selectedAll={selectedAll}
|
||||
onRowChecked={onGroupChange}
|
||||
/>
|
||||
</div>
|
||||
{displayType === "dropdown" &&
|
||||
size === "full" &&
|
||||
groups &&
|
||||
groups.length > 0 && (
|
||||
<div className="data_column_two">
|
||||
<ADSelectorGroupsHeader headerLabel="Groups" />
|
||||
<ADSelectorGroupsBody
|
||||
options={groups}
|
||||
selectedOptions={selectedGroups}
|
||||
isMultiSelect={isMultiSelect}
|
||||
currentGroup={currentGroup}
|
||||
listHeight={listHeight}
|
||||
itemHeight={itemHeight}
|
||||
onRowChecked={this.onGroupChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ADSelectorFooter
|
||||
buttonLabel={buttonLabel}
|
||||
isDisabled={
|
||||
!this.state.selectedOptions ||
|
||||
!this.state.selectedOptions.length
|
||||
}
|
||||
isMultiSelect={isMultiSelect}
|
||||
onClick={this.onButtonClick}
|
||||
selectedOptions={selectedOptions}
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<ADSelectorFooter
|
||||
buttonLabel={buttonLabel}
|
||||
isDisabled={!selectedOptions || !selectedOptions.length}
|
||||
isMultiSelect={isMultiSelect}
|
||||
onClick={onButtonClick}
|
||||
selectedOptions={selectedOptions}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
ADSelector.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
options: PropTypes.array,
|
||||
groups: PropTypes.array,
|
||||
hasNextPage: PropTypes.bool,
|
||||
isNextPageLoading: PropTypes.bool,
|
||||
loadNextPage: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
isDisabled: PropTypes.bool,
|
||||
onSearchChanged: PropTypes.func,
|
||||
isMultiSelect: PropTypes.bool,
|
||||
buttonLabel: PropTypes.string,
|
||||
selectAllLabel: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
displayType: PropTypes.oneOf(["dropdown", "aside"]),
|
||||
onAddNewClick: PropTypes.func,
|
||||
allowCreation: PropTypes.bool,
|
||||
onSelect: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onGroupSelect: PropTypes.func,
|
||||
onGroupChange: PropTypes.func,
|
||||
selectedOptions: PropTypes.array,
|
||||
selectedGroups: PropTypes.array,
|
||||
selectedAll: PropTypes.bool,
|
||||
onCancel: PropTypes.func,
|
||||
allowAnyClickClose: PropTypes.bool,
|
||||
isOpen: PropTypes.bool,
|
||||
options: PropTypes.array,
|
||||
groups: PropTypes.array,
|
||||
hasNextPage: PropTypes.bool,
|
||||
isNextPageLoading: PropTypes.bool,
|
||||
loadNextPage: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
isDisabled: PropTypes.bool,
|
||||
onSearchChanged: PropTypes.func,
|
||||
isMultiSelect: PropTypes.bool,
|
||||
buttonLabel: PropTypes.string,
|
||||
selectAllLabel: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
displayType: PropTypes.oneOf(["dropdown", "aside"]),
|
||||
onAddNewClick: PropTypes.func,
|
||||
allowCreation: PropTypes.bool,
|
||||
onSelect: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onGroupSelect: PropTypes.func,
|
||||
onGroupChange: PropTypes.func,
|
||||
selectedOptions: PropTypes.array,
|
||||
selectedGroups: PropTypes.array,
|
||||
selectedAll: PropTypes.bool,
|
||||
onCancel: PropTypes.func,
|
||||
allowAnyClickClose: PropTypes.bool
|
||||
};
|
||||
|
||||
export default ADSelector;
|
||||
|
@ -0,0 +1,537 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled, { css } from "styled-components";
|
||||
import Checkbox from "../../checkbox";
|
||||
import ComboBox from "../../combobox";
|
||||
|
||||
import filter from "lodash/filter";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
|
||||
import ADSelectorOptionsHeader from "./options/header";
|
||||
import ADSelectorOptionsBody from "./options/body";
|
||||
import ADSelectorGroupsHeader from "./groups/header";
|
||||
import ADSelectorGroupsBody from "./groups/body";
|
||||
import ADSelectorFooter from "./footer";
|
||||
|
||||
import { handleAnyClick } from "../../../utils/event";
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable react/prop-types */
|
||||
const Container = ({
|
||||
value,
|
||||
placeholder,
|
||||
isMultiSelect,
|
||||
size,
|
||||
width,
|
||||
maxHeight,
|
||||
isDisabled,
|
||||
onSelect,
|
||||
onSearchChanged,
|
||||
options,
|
||||
selectedOptions,
|
||||
buttonLabel,
|
||||
selectAllLabel,
|
||||
groups,
|
||||
selectedGroups,
|
||||
onGroupSelect,
|
||||
onGroupChange,
|
||||
isOpen,
|
||||
displayType,
|
||||
containerWidth,
|
||||
containerHeight,
|
||||
allowCreation,
|
||||
onAddNewClick,
|
||||
allowAnyClickClose,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
loadNextPage,
|
||||
isSelected,
|
||||
...props
|
||||
}) => <div {...props} />;
|
||||
/* eslint-enable react/prop-types */
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
const StyledContainer = styled(Container)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
${props => (props.containerWidth ? `width: ${props.containerWidth};` : "")}
|
||||
${props => (props.containerHeight ? `height: ${props.containerHeight};` : "")}
|
||||
|
||||
.data_container {
|
||||
margin: 16px 16px -5px 16px;
|
||||
|
||||
.head_container {
|
||||
display: flex;
|
||||
margin-bottom: ${props => (props.displayType === "dropdown" ? 8 : 16)}px;
|
||||
|
||||
.options_searcher {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
${props =>
|
||||
props.displayType === "dropdown" &&
|
||||
props.size === "full" &&
|
||||
css`
|
||||
margin-right: ${props => (props.allowCreation ? 8 : 16)}px;
|
||||
`}
|
||||
/*${props =>
|
||||
props.allowCreation
|
||||
? css`
|
||||
width: 272px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
: css`
|
||||
width: ${props => (props.isDropDown ? "313px" : "100%")};
|
||||
`}*/
|
||||
}
|
||||
|
||||
.add_new_btn {
|
||||
${props =>
|
||||
props.allowCreation &&
|
||||
css`
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 32px;
|
||||
width: 36px;
|
||||
margin-right: 16px;
|
||||
line-height: 18px;
|
||||
`}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.options_group_selector {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.data_column_one {
|
||||
${props =>
|
||||
props.size === "full" &&
|
||||
props.displayType === "dropdown" &&
|
||||
props.groups &&
|
||||
props.groups.length > 0
|
||||
? css`
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
`
|
||||
: ""}
|
||||
|
||||
.options_list {
|
||||
margin-top: 4px;
|
||||
margin-left: -8px;
|
||||
.option {
|
||||
line-height: 32px;
|
||||
padding-left: ${props => (props.isMultiSelect ? 8 : 0)}px;
|
||||
cursor: pointer;
|
||||
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
|
||||
.option_link {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f9f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data_column_two {
|
||||
${props =>
|
||||
props.displayType === "dropdown" &&
|
||||
props.groups &&
|
||||
props.groups.length > 0
|
||||
? css`
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
border-left: 1px solid #eceef1;
|
||||
`
|
||||
: ""}
|
||||
|
||||
.group_header {
|
||||
font-weight: 600;
|
||||
padding-left: 16px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.group_list {
|
||||
margin-left: 8px;
|
||||
|
||||
.option {
|
||||
line-height: 32px;
|
||||
padding-left: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
|
||||
.option_link {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
.option.selected {
|
||||
background-color: #ECEEF1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class ADSelector extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.ref = React.createRef();
|
||||
|
||||
const { groups, selectedOptions, selectedGroups, selectedAll, isOpen } = props;
|
||||
|
||||
const convertedGroups = this.convertGroups(groups);
|
||||
const currentGroup = this.getCurrentGroup(convertedGroups);
|
||||
|
||||
this.state = {
|
||||
selectedOptions: selectedOptions || [],
|
||||
selectedAll: selectedAll || false,
|
||||
groups: convertedGroups,
|
||||
selectedGroups: selectedGroups || [],
|
||||
currentGroup: currentGroup
|
||||
};
|
||||
|
||||
if (isOpen) handleAnyClick(true, this.handleClick);
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const { onCancel, allowAnyClickClose, isOpen } = this.props;
|
||||
|
||||
if (
|
||||
isOpen &&
|
||||
allowAnyClickClose &&
|
||||
this.ref &&
|
||||
this.ref.current &&
|
||||
!this.ref.current.contains(e.target) &&
|
||||
e &&
|
||||
e.target &&
|
||||
e.target.className &&
|
||||
typeof e.target.className.indexOf === "function" &&
|
||||
e.target.className.indexOf("option_checkbox") === -1
|
||||
) {
|
||||
onCancel && onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
handleAnyClick(false, this.handleClick);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
groups,
|
||||
selectedAll,
|
||||
isMultiSelect,
|
||||
selectedOptions,
|
||||
selectedGroups,
|
||||
allowAnyClickClose,
|
||||
isOpen
|
||||
} = this.props;
|
||||
|
||||
if (isOpen !== prevProps.isOpen) {
|
||||
handleAnyClick(isOpen, this.handleClick);
|
||||
}
|
||||
|
||||
if (allowAnyClickClose !== prevProps.allowAnyClickClose) {
|
||||
handleAnyClick(allowAnyClickClose, this.handleClick);
|
||||
}
|
||||
|
||||
let newState = {};
|
||||
|
||||
if (!isEqual(selectedOptions, prevProps.selectedOptions)) {
|
||||
newState = { selectedOptions };
|
||||
}
|
||||
|
||||
if (!isEqual(selectedGroups, prevProps.selectedGroups)) {
|
||||
newState = Object.assign({}, newState, { selectedGroups });
|
||||
}
|
||||
if (isMultiSelect !== prevProps.isMultiSelect) {
|
||||
newState = Object.assign({}, newState, {
|
||||
selectedOptions: []
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedAll !== prevProps.selectedAll) {
|
||||
newState = Object.assign({}, newState, {
|
||||
selectedAll
|
||||
});
|
||||
}
|
||||
|
||||
if (!isEqual(groups, prevProps.groups)) {
|
||||
const newGroups = this.convertGroups(groups);
|
||||
const currentGroup = this.getCurrentGroup(newGroups);
|
||||
newState = Object.assign({}, newState, {
|
||||
groups: newGroups,
|
||||
currentGroup
|
||||
});
|
||||
}
|
||||
|
||||
if (!isEmpty(newState)) {
|
||||
this.setState({ ...this.state, ...newState });
|
||||
}
|
||||
}
|
||||
|
||||
convertGroups = groups => {
|
||||
if (!groups) return [];
|
||||
|
||||
const wrappedGroups = groups.map(this.convertGroup);
|
||||
|
||||
return wrappedGroups;
|
||||
};
|
||||
|
||||
convertGroup = group => {
|
||||
return {
|
||||
key: group.key,
|
||||
label: `${group.label} (${group.total})`,
|
||||
total: group.total
|
||||
};
|
||||
};
|
||||
|
||||
getCurrentGroup = groups => {
|
||||
const currentGroup = groups.length > 0 ? groups[0] : "No groups";
|
||||
return currentGroup;
|
||||
};
|
||||
|
||||
onButtonClick = () => {
|
||||
this.props.onSelect &&
|
||||
this.props.onSelect(
|
||||
this.state.selectedAll ? this.props.options : this.state.selectedOptions
|
||||
);
|
||||
};
|
||||
|
||||
onSelectedAllChange = e => {
|
||||
this.setState({
|
||||
selectedAll: e.target.checked,
|
||||
selectedOptions: e.target.checked ? this.props.options : []
|
||||
});
|
||||
};
|
||||
|
||||
onOptionSelect = option => {
|
||||
this.props.onSelect && this.props.onSelect(option);
|
||||
};
|
||||
|
||||
onOptionChange = (option, e) => {
|
||||
const { selectedOptions } = this.state;
|
||||
const newSelectedOptions = e.target.checked
|
||||
? [...selectedOptions, option]
|
||||
: filter(selectedOptions, obj => obj.key !== option.key);
|
||||
|
||||
//console.log("onChange", option, e.target.checked, newSelectedOptions);
|
||||
|
||||
this.setState({
|
||||
selectedOptions: newSelectedOptions,
|
||||
selectedAll: newSelectedOptions.length === this.props.options.length
|
||||
});
|
||||
};
|
||||
|
||||
onGroupChange = (group, e) => {
|
||||
/*this.setState({
|
||||
currentGroup: group
|
||||
});
|
||||
|
||||
this.props.onGroupChange && this.props.onGroupChange(group);*/
|
||||
|
||||
const { selectedGroups } = this.state;
|
||||
const newSelectedGroups = e.target.checked
|
||||
? [...selectedGroups, group]
|
||||
: filter(selectedGroups, obj => obj.key !== group.key);
|
||||
|
||||
//console.log("onChange", option, e.target.checked, newSelectedOptions);
|
||||
|
||||
this.setState({
|
||||
selectedGroups: newSelectedGroups,
|
||||
currentGroup: group
|
||||
});
|
||||
|
||||
this.props.onGroupSelect && this.props.onGroupSelect(group);
|
||||
};
|
||||
|
||||
render() {
|
||||
//console.log("ADSelector render");
|
||||
const {
|
||||
options,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
loadNextPage,
|
||||
value,
|
||||
placeholder,
|
||||
isDisabled,
|
||||
onSearchChanged,
|
||||
isMultiSelect,
|
||||
buttonLabel,
|
||||
selectAllLabel,
|
||||
size,
|
||||
displayType,
|
||||
onAddNewClick,
|
||||
allowCreation
|
||||
} = this.props;
|
||||
|
||||
const { selectedOptions, selectedAll, currentGroup, groups, selectedGroups } = this.state;
|
||||
|
||||
let containerHeight;
|
||||
let containerWidth;
|
||||
let listHeight;
|
||||
let listWidth;
|
||||
const itemHeight = 32;
|
||||
const hasGroups = groups && groups.length > 0;
|
||||
|
||||
switch (size) {
|
||||
case "compact":
|
||||
containerHeight = hasGroups ? "326px" : "100%";
|
||||
containerWidth = "379px";
|
||||
listWidth = displayType === "dropdown" ? 356 : 356;
|
||||
listHeight = hasGroups ? 488 : isMultiSelect ? 176 : 226;
|
||||
break;
|
||||
case "full":
|
||||
default:
|
||||
containerHeight = "100%";
|
||||
containerWidth = displayType === "dropdown" ? "690px" : "326px";
|
||||
listWidth = displayType === "dropdown" ? 320 : 300;
|
||||
listHeight = 488;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
containerHeight={containerHeight}
|
||||
containerWidth={containerWidth}
|
||||
{...this.props}
|
||||
>
|
||||
<div ref={this.ref}>
|
||||
<div className="data_container">
|
||||
<div className="data_column_one">
|
||||
<ADSelectorOptionsHeader
|
||||
value={value}
|
||||
searchPlaceHolder={placeholder}
|
||||
isDisabled={isDisabled}
|
||||
allowCreation={allowCreation}
|
||||
onAddNewClick={onAddNewClick}
|
||||
onChange={onSearchChanged}
|
||||
onClearSearch={onSearchChanged.bind(this, "")}
|
||||
/>
|
||||
{displayType === "aside" && groups && groups.length > 0 && (
|
||||
<ComboBox
|
||||
className="options_group_selector"
|
||||
isDisabled={isDisabled}
|
||||
options={groups}
|
||||
selectedOption={currentGroup}
|
||||
dropDownMaxHeight={200}
|
||||
scaled={true}
|
||||
scaledOptions={true}
|
||||
size="content"
|
||||
onSelect={this.onCurrentGroupChange}
|
||||
/>
|
||||
)}
|
||||
{isMultiSelect && !groups && !groups.length && (
|
||||
<Checkbox
|
||||
label={selectAllLabel}
|
||||
isChecked={
|
||||
selectedAll || selectedOptions.length === options.length
|
||||
}
|
||||
isIndeterminate={!selectedAll && selectedOptions.length > 0}
|
||||
className="option_select_all_checkbox"
|
||||
onChange={this.onSelectedAllChange}
|
||||
/>
|
||||
)}
|
||||
<ADSelectorOptionsBody
|
||||
options={options}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={loadNextPage}
|
||||
isMultiSelect={isMultiSelect}
|
||||
listHeight={listHeight}
|
||||
listWidth={listWidth}
|
||||
itemHeight={itemHeight}
|
||||
onRowChecked={this.onOptionChange}
|
||||
onRowSelect={this.onOptionSelect}
|
||||
selectedOptions={selectedOptions}
|
||||
selectedAll={selectedAll}
|
||||
/>
|
||||
</div>
|
||||
{displayType === "dropdown" &&
|
||||
size === "full" &&
|
||||
groups &&
|
||||
groups.length > 0 && (
|
||||
<div className="data_column_two">
|
||||
<ADSelectorGroupsHeader headerLabel="Groups" />
|
||||
<ADSelectorGroupsBody
|
||||
options={groups}
|
||||
selectedOptions={selectedGroups}
|
||||
isMultiSelect={isMultiSelect}
|
||||
currentGroup={currentGroup}
|
||||
listHeight={listHeight}
|
||||
itemHeight={itemHeight}
|
||||
onRowChecked={this.onGroupChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ADSelectorFooter
|
||||
buttonLabel={buttonLabel}
|
||||
isDisabled={
|
||||
!this.state.selectedOptions ||
|
||||
!this.state.selectedOptions.length
|
||||
}
|
||||
isMultiSelect={isMultiSelect}
|
||||
onClick={this.onButtonClick}
|
||||
selectedOptions={selectedOptions}
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ADSelector.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
options: PropTypes.array,
|
||||
groups: PropTypes.array,
|
||||
hasNextPage: PropTypes.bool,
|
||||
isNextPageLoading: PropTypes.bool,
|
||||
loadNextPage: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
isDisabled: PropTypes.bool,
|
||||
onSearchChanged: PropTypes.func,
|
||||
isMultiSelect: PropTypes.bool,
|
||||
buttonLabel: PropTypes.string,
|
||||
selectAllLabel: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
displayType: PropTypes.oneOf(["dropdown", "aside"]),
|
||||
onAddNewClick: PropTypes.func,
|
||||
allowCreation: PropTypes.bool,
|
||||
onSelect: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onGroupSelect: PropTypes.func,
|
||||
onGroupChange: PropTypes.func,
|
||||
selectedOptions: PropTypes.array,
|
||||
selectedGroups: PropTypes.array,
|
||||
selectedAll: PropTypes.bool,
|
||||
onCancel: PropTypes.func,
|
||||
allowAnyClickClose: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default ADSelector;
|
Loading…
Reference in New Issue
Block a user