[Demo] (https://codesandbox.io/s/react-codesandboxer-example-5sh93?file=/example.js)
import React, { useState, useEffect } from "react";
import Select, { components } from "react-select";
const menuHeaderStyle = {
padding: "8px 12px",
background: "#333",
color: "white"
};
/**
*
* @param { minTreeLevel: number default 1,
* treeLevel: number required,
* onChange: value, actionMeta => void } props
*/
const ReactTreeSelect = props => {
const { defaultValue, minTreeLevel, options, onChange, ...rest } = props;
const [valTree, setValueTree] = useState(defaultValue || []);
const [targetOption, setTargetOption] = useState([]);
const [menuIsOpen, toggleMenu] = useState(false);
const [isForceOpen, forceToggle] = useState(false);
const SingleValue = ({ children, ...props }) => {
return (
<components.SingleValue {...props}>
{valTree.reduce((b, v) => b + v.prevText + v.label, "")}
</components.SingleValue>
);
};
const MenuList = props => {
const headerTxt =
targetOption && targetOption.length && targetOption[0].prevText;
return headerTxt ? (
<components.MenuList {...props}>
<div style={menuHeaderStyle}>{headerTxt}</div>
{props.children}
</components.MenuList>
) : (
<components.MenuList {...props} />
);
};
useEffect(() => {
const defaultValue = [];
const currentOption = valTree.reduce((base, v, i) => {
const found = base.find(o => o.value === v.value);
if (found) {
defaultValue.push(found);
return found.subOptions && found.subOptions(); // will stop reducing if null
}
return base;
}, options);
// console.log(defaultValue);
// console.log(currentOption);
if (defaultValue.length === 0) {
setValueTree([]);
setTargetOption(options);
} else {
setTargetOption(currentOption);
}
return () => {};
}, []);
return (
<Select
onMenuOpen={() => {
toggleMenu(true);
if (!targetOption || targetOption.length === 0) {
forceToggle(true);
}
}}
onMenuClose={() => {
toggleMenu(false);
forceToggle(false);
}}
menuIsOpen={
(!!targetOption && targetOption.length > 0 && menuIsOpen) || isForceOpen
}
closeMenuOnSelect={!menuIsOpen}
onBlur={() => {
if (valTree.length < minTreeLevel) {
setValueTree([]);
setTargetOption(options);
onChange([], { action: "incomplete-clear" });
}
}}
isClearable
onChange={(o, actionmeta) => {
if (actionmeta.action === "select-option") {
const newTree = [
...valTree,
{
value: o.value,
label: o.label,
prevText: o.prevText || " "
}
];
setValueTree(newTree);
setTargetOption(o.subOptions && o.subOptions());
onChange(newTree, actionmeta);
} else if (actionmeta.action === "clear") {
setValueTree([]);
setTargetOption(options);
onChange([], actionmeta);
}
// console.log(actionmeta);
}}
components={{ SingleValue, MenuList }}
styles={{
option: base => ({
...base,
height: "100%"
})
}}
defaultValue={valTree}
options={targetOption}
{...rest}
/>
);
};
ReactTreeSelect.defaultProps = {
minTreeLevel: 1,
treeLevel: 0,
onChange: () => {}
};
export default ReactTreeSelect;
import React, { useState, useEffect, useRef } from "react";
// import Select, { components } from "react-select";
import { alwaysBasedOnOpts } from "./docs/data";
import ReactSelectTree from "./ReactSelectTree";
<ReactSelectTree
onChange={val => {
console.log(val);
}}
placeholder="Always based on"
minTreeLevel={2}
options={alwaysBasedOnOpts} />
const ymdhms = ["Year", "Month", "Day", "Hour", "Minute"];
const counterMetrics = [...Array(101).keys()];
export const alwaysBasedOnOpts = counterMetrics.map(w => {
return {
prevText: "Always based on Last ",
value: w,
label: w,
subOptions: () =>
ymdhms.map(x => ({
value: x,
label: x,
subOptions: () =>
counterMetrics.map(y => ({
prevText: " till advance ",
value: y,
label: y,
subOptions: () =>
ymdhms.map(z => ({
value: z,
label: z
}))
}))
}))
};
});