2020-10-16 13:16:01 +00:00
|
|
|
import React, { memo } from "react";
|
|
|
|
import PropTypes from "prop-types";
|
|
|
|
import { VariableSizeList } from "react-window";
|
2020-01-09 13:27:49 +00:00
|
|
|
import onClickOutside from "react-onclickoutside";
|
2021-01-27 16:28:34 +00:00
|
|
|
import { isMobile } from "react-device-detect";
|
2019-06-10 12:28:54 +00:00
|
|
|
|
2021-02-10 10:45:26 +00:00
|
|
|
import CustomScrollbarsVirtualList from "../scrollbar/custom-scrollbars-virtual-list";
|
|
|
|
import DropDownItem from "../drop-down-item";
|
|
|
|
import Backdrop from "../backdrop";
|
|
|
|
import StyledDropdown from "./styled-drop-down";
|
2019-06-10 12:28:54 +00:00
|
|
|
|
2020-01-17 10:58:44 +00:00
|
|
|
// eslint-disable-next-line react/display-name, react/prop-types
|
2019-08-13 11:21:38 +00:00
|
|
|
const Row = memo(({ data, index, style }) => {
|
|
|
|
const option = data[index];
|
2020-09-01 13:55:28 +00:00
|
|
|
// eslint-disable-next-line react/prop-types
|
2020-10-16 13:16:01 +00:00
|
|
|
const separator = option.props.isSeparator
|
|
|
|
? { width: `calc(100% - 32px)`, height: `1px` }
|
|
|
|
: {};
|
|
|
|
const newStyle = { ...style, ...separator };
|
2019-08-13 11:21:38 +00:00
|
|
|
|
2019-07-31 11:45:39 +00:00
|
|
|
return (
|
2019-08-13 11:21:38 +00:00
|
|
|
<DropDownItem
|
2020-01-17 10:58:44 +00:00
|
|
|
// eslint-disable-next-line react/prop-types
|
2019-08-13 11:21:38 +00:00
|
|
|
{...option.props}
|
2020-10-16 13:16:01 +00:00
|
|
|
style={newStyle}
|
|
|
|
/>
|
2019-07-31 11:45:39 +00:00
|
|
|
);
|
2019-07-24 17:43:18 +00:00
|
|
|
});
|
2019-06-24 13:18:47 +00:00
|
|
|
|
2019-08-13 11:21:38 +00:00
|
|
|
class DropDown extends React.PureComponent {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
2019-12-23 13:14:46 +00:00
|
|
|
width: this.dropDownRef ? this.dropDownRef.current.offsetWidth : 240,
|
2019-11-19 10:35:42 +00:00
|
|
|
directionX: props.directionX,
|
2020-10-16 13:16:01 +00:00
|
|
|
directionY: props.directionY,
|
2019-08-13 11:21:38 +00:00
|
|
|
};
|
2019-12-17 14:27:06 +00:00
|
|
|
|
2019-11-19 10:35:42 +00:00
|
|
|
this.dropDownRef = React.createRef();
|
2019-08-13 11:21:38 +00:00
|
|
|
}
|
|
|
|
|
2019-12-17 14:27:06 +00:00
|
|
|
componentDidMount() {
|
2020-01-17 10:58:44 +00:00
|
|
|
if (this.props.open) {
|
2020-01-09 13:27:49 +00:00
|
|
|
this.props.enableOnClickOutside();
|
|
|
|
this.checkPosition();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.props.disableOnClickOutside();
|
2019-09-09 13:45:07 +00:00
|
|
|
}
|
2019-08-13 11:21:38 +00:00
|
|
|
|
2019-12-23 13:14:46 +00:00
|
|
|
componentDidUpdate(prevProps) {
|
2019-12-17 14:27:06 +00:00
|
|
|
if (this.props.open !== prevProps.open) {
|
2020-01-17 10:58:44 +00:00
|
|
|
if (this.props.open) {
|
2020-01-09 13:27:49 +00:00
|
|
|
this.props.enableOnClickOutside();
|
|
|
|
this.checkPosition();
|
2020-10-16 13:16:01 +00:00
|
|
|
} else {
|
2020-01-09 13:27:49 +00:00
|
|
|
this.props.disableOnClickOutside();
|
|
|
|
}
|
2019-11-19 10:35:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-16 13:16:01 +00:00
|
|
|
handleClickOutside = (e) => {
|
2021-01-27 16:28:34 +00:00
|
|
|
e.preventDefault();
|
2020-01-09 13:27:49 +00:00
|
|
|
this.toggleDropDown(e);
|
|
|
|
};
|
|
|
|
|
|
|
|
toggleDropDown = (e) => {
|
2020-10-16 13:16:01 +00:00
|
|
|
this.props.clickOutsideAction &&
|
|
|
|
this.props.clickOutsideAction(e, !this.props.open);
|
|
|
|
};
|
2019-12-17 14:27:06 +00:00
|
|
|
|
2019-11-19 10:35:42 +00:00
|
|
|
checkPosition = () => {
|
2019-12-17 14:27:06 +00:00
|
|
|
if (!this.dropDownRef.current) return;
|
|
|
|
|
|
|
|
const rects = this.dropDownRef.current.getBoundingClientRect();
|
|
|
|
const container = { width: window.innerWidth, height: window.innerHeight };
|
2020-02-11 08:23:30 +00:00
|
|
|
const left = rects.left < 0 && rects.width < container.width;
|
2020-10-16 13:16:01 +00:00
|
|
|
const right =
|
|
|
|
rects.width &&
|
2021-10-21 09:06:51 +00:00
|
|
|
rects.left < (rects.width || 250) &&
|
2020-10-16 13:16:01 +00:00
|
|
|
rects.left > rects.width &&
|
|
|
|
rects.width < container.width;
|
2020-02-17 13:37:42 +00:00
|
|
|
const top = rects.bottom > container.height && rects.top > rects.height;
|
|
|
|
const bottom = rects.top < 0;
|
2020-10-16 13:16:01 +00:00
|
|
|
const x = left ? "left" : right ? "right" : this.state.directionX;
|
|
|
|
const y = bottom ? "bottom" : top ? "top" : this.state.directionY;
|
2019-12-17 14:27:06 +00:00
|
|
|
|
|
|
|
this.setState({
|
2020-02-11 08:23:30 +00:00
|
|
|
directionX: x,
|
2020-02-14 09:21:25 +00:00
|
|
|
directionY: y,
|
2020-10-16 13:16:01 +00:00
|
|
|
width: rects.width,
|
2019-12-17 14:27:06 +00:00
|
|
|
});
|
2020-10-16 13:16:01 +00:00
|
|
|
};
|
2019-08-13 11:21:38 +00:00
|
|
|
|
2020-04-13 12:56:47 +00:00
|
|
|
getItemHeight = (item) => {
|
|
|
|
const isTablet = window.innerWidth < 1024; //TODO: Make some better
|
|
|
|
|
2020-10-16 13:16:01 +00:00
|
|
|
if (item && item.props.isSeparator) return isTablet ? 16 : 12;
|
2020-04-13 12:56:47 +00:00
|
|
|
|
|
|
|
return isTablet ? 36 : 32;
|
2020-10-16 13:16:01 +00:00
|
|
|
};
|
2020-12-09 09:38:58 +00:00
|
|
|
hideDisabledItems = () => {
|
2020-12-10 16:41:42 +00:00
|
|
|
if (React.Children.count(this.props.children) > 0) {
|
|
|
|
const { children } = this.props;
|
|
|
|
const enabledChildren = React.Children.map(children, (child) => {
|
|
|
|
if (child && !child.props.disabled) return child;
|
|
|
|
});
|
2020-12-09 09:38:58 +00:00
|
|
|
|
2020-12-10 16:41:42 +00:00
|
|
|
const sizeEnabledChildren = enabledChildren.length;
|
2020-12-09 09:38:58 +00:00
|
|
|
|
2020-12-10 16:41:42 +00:00
|
|
|
const cleanChildren = React.Children.map(
|
|
|
|
enabledChildren,
|
|
|
|
(child, index) => {
|
|
|
|
if (!child.props.isSeparator) return child;
|
|
|
|
if (index !== 0 && index !== sizeEnabledChildren - 1) return child;
|
|
|
|
}
|
|
|
|
);
|
2020-12-09 09:38:58 +00:00
|
|
|
|
2020-12-10 16:41:42 +00:00
|
|
|
return cleanChildren;
|
|
|
|
}
|
2020-12-09 09:38:58 +00:00
|
|
|
};
|
2020-04-13 12:56:47 +00:00
|
|
|
|
2019-08-13 11:21:38 +00:00
|
|
|
render() {
|
2020-12-10 16:41:42 +00:00
|
|
|
const { maxHeight, children, showDisabledItems } = this.props;
|
2019-12-23 13:14:46 +00:00
|
|
|
const { directionX, directionY, width } = this.state;
|
2020-12-09 09:38:58 +00:00
|
|
|
let cleanChildren;
|
|
|
|
|
2020-10-16 13:16:01 +00:00
|
|
|
const rowHeights = React.Children.map(children, (child) =>
|
|
|
|
this.getItemHeight(child)
|
|
|
|
);
|
|
|
|
const getItemSize = (index) => rowHeights[index];
|
2020-04-13 12:56:47 +00:00
|
|
|
const fullHeight = children && rowHeights.reduce((a, b) => a + b, 0);
|
2020-10-16 13:16:01 +00:00
|
|
|
const calculatedHeight =
|
|
|
|
fullHeight > 0 && fullHeight < maxHeight ? fullHeight : maxHeight;
|
|
|
|
const dropDownMaxHeightProp = maxHeight
|
|
|
|
? { height: calculatedHeight + "px" }
|
|
|
|
: {};
|
2020-02-17 13:37:42 +00:00
|
|
|
//console.log("DropDown render", this.props);
|
2020-11-25 16:01:57 +00:00
|
|
|
|
2020-12-09 09:38:58 +00:00
|
|
|
if (!showDisabledItems) cleanChildren = this.hideDisabledItems();
|
2020-11-27 11:15:39 +00:00
|
|
|
|
2019-08-13 11:21:38 +00:00
|
|
|
return (
|
2020-01-28 07:01:05 +00:00
|
|
|
<StyledDropdown
|
|
|
|
ref={this.dropDownRef}
|
|
|
|
{...this.props}
|
|
|
|
directionX={directionX}
|
|
|
|
directionY={directionY}
|
|
|
|
{...dropDownMaxHeightProp}
|
|
|
|
>
|
2020-10-16 13:16:01 +00:00
|
|
|
{maxHeight ? (
|
|
|
|
<VariableSizeList
|
2020-01-28 07:01:05 +00:00
|
|
|
height={calculatedHeight}
|
|
|
|
width={width}
|
2020-04-13 12:56:47 +00:00
|
|
|
itemSize={getItemSize}
|
2020-01-28 07:01:05 +00:00
|
|
|
itemCount={children.length}
|
|
|
|
itemData={children}
|
|
|
|
outerElementType={CustomScrollbarsVirtualList}
|
|
|
|
>
|
|
|
|
{Row}
|
2020-04-13 12:56:47 +00:00
|
|
|
</VariableSizeList>
|
2020-12-10 16:41:42 +00:00
|
|
|
) : cleanChildren ? (
|
|
|
|
cleanChildren
|
2020-10-16 13:16:01 +00:00
|
|
|
) : (
|
|
|
|
children
|
|
|
|
)}
|
2020-01-28 07:01:05 +00:00
|
|
|
</StyledDropdown>
|
2019-08-13 11:21:38 +00:00
|
|
|
);
|
|
|
|
}
|
2019-09-09 13:45:07 +00:00
|
|
|
}
|
2019-08-13 11:21:38 +00:00
|
|
|
|
2020-01-09 13:27:49 +00:00
|
|
|
const EnhancedComponent = onClickOutside(DropDown);
|
|
|
|
|
|
|
|
class DropDownContainer extends React.Component {
|
2020-12-10 16:41:42 +00:00
|
|
|
toggleDropDown = (e) => {
|
2020-12-10 17:06:04 +00:00
|
|
|
this.props.clickOutsideAction({}, !this.props.open);
|
2020-12-10 16:41:42 +00:00
|
|
|
};
|
2020-01-09 13:27:49 +00:00
|
|
|
render() {
|
2020-12-10 16:41:42 +00:00
|
|
|
const { withBackdrop = true, open } = this.props;
|
2021-03-15 13:21:28 +00:00
|
|
|
const eventTypesProp = isMobile ? { eventTypes: ["touchend"] } : {};
|
2020-12-10 16:41:42 +00:00
|
|
|
|
2020-01-28 07:01:05 +00:00
|
|
|
return (
|
|
|
|
<>
|
2020-12-10 16:41:42 +00:00
|
|
|
{withBackdrop ? (
|
|
|
|
<Backdrop visible={open} zIndex={199} onClick={this.toggleDropDown} />
|
|
|
|
) : null}
|
2021-01-22 12:53:31 +00:00
|
|
|
<EnhancedComponent
|
2021-01-27 16:28:34 +00:00
|
|
|
{...eventTypesProp}
|
2021-01-22 12:53:31 +00:00
|
|
|
disableOnClickOutside={true}
|
|
|
|
{...this.props}
|
|
|
|
/>
|
2020-10-16 13:16:01 +00:00
|
|
|
</>
|
|
|
|
);
|
2020-01-09 13:27:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-07 11:57:26 +00:00
|
|
|
DropDown.propTypes = {
|
|
|
|
disableOnClickOutside: PropTypes.func,
|
|
|
|
enableOnClickOutside: PropTypes.func,
|
|
|
|
};
|
|
|
|
|
2020-01-28 07:01:05 +00:00
|
|
|
DropDownContainer.propTypes = {
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Children elements */
|
2021-03-03 15:09:00 +00:00
|
|
|
children: PropTypes.any,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Accepts class */
|
2021-03-03 15:09:00 +00:00
|
|
|
className: PropTypes.string,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Required for determining a click outside DropDown with the withBackdrop parameter */
|
2021-03-03 15:09:00 +00:00
|
|
|
clickOutsideAction: PropTypes.func,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Sets the opening direction relative to the parent */
|
2021-03-03 15:09:00 +00:00
|
|
|
directionX: PropTypes.oneOf(["left", "right"]), //TODO: make more informative
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Sets the opening direction relative to the parent */
|
2021-03-03 15:09:00 +00:00
|
|
|
directionY: PropTypes.oneOf(["bottom", "top"]),
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Accepts id */
|
2021-03-03 15:09:00 +00:00
|
|
|
id: PropTypes.string,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Required if you need to specify the exact width of the component, for example 100% */
|
2021-03-03 15:09:00 +00:00
|
|
|
manualWidth: PropTypes.string,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Required if you need to specify the exact distance from the parent component */
|
2021-03-03 15:09:00 +00:00
|
|
|
manualX: PropTypes.string,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Required if you need to specify the exact distance from the parent component */
|
2021-03-03 15:09:00 +00:00
|
|
|
manualY: PropTypes.string,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Required if the scrollbar is displayed */
|
2021-03-03 15:09:00 +00:00
|
|
|
maxHeight: PropTypes.number,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Tells when the dropdown should be opened */
|
2020-01-28 07:01:05 +00:00
|
|
|
open: PropTypes.bool,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Accepts css style */
|
2021-03-03 15:09:00 +00:00
|
|
|
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Used to display backdrop */
|
2020-10-16 13:16:01 +00:00
|
|
|
withBackdrop: PropTypes.bool,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Count of columns */
|
2021-03-03 15:09:00 +00:00
|
|
|
columnCount: PropTypes.number,
|
2021-03-07 11:57:26 +00:00
|
|
|
/** Display disabled items or not */
|
|
|
|
showDisabledItems: PropTypes.bool,
|
2021-03-03 15:09:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
DropDownContainer.defaultProps = {
|
|
|
|
directionX: "left",
|
|
|
|
directionY: "bottom",
|
2021-03-12 13:30:36 +00:00
|
|
|
withBackdrop: true,
|
2021-03-03 15:09:00 +00:00
|
|
|
showDisabledItems: false,
|
2020-10-16 13:16:01 +00:00
|
|
|
};
|
2020-01-28 07:01:05 +00:00
|
|
|
|
2020-01-09 13:27:49 +00:00
|
|
|
export default DropDownContainer;
|