@kenshooui/react-tree
Version:
React Tree is a straight forward component that allows a user to display and manage a hierarchical structure of items in a clear and comfortable way.
433 lines (418 loc) • 18.2 kB
JavaScript
;
function _interopDefault(ex) {
return ex && "object" == typeof ex && "default" in ex ? ex.default : ex;
}
Object.defineProperty(exports, "__esModule", {
value: !0
});
var core = require("@emotion/core"), React = require("react"), React__default = _interopDefault(React), containerCss = function(_ref) {
return {
width: _ref.width,
height: _ref.height,
backgroundColor: "#F8F9FA",
boxShadow: "0 6px 10px 0 rgba(27, 32, 70, 0.09)",
border: "1px solid #E1E4EB",
color: "#545769",
borderRadius: "4px",
position: "relative",
fontFamily: "Arial, Helvetica, sans-serif"
};
}, wrapperCss = function() {
return {
height: "40px",
display: "flex",
alignItems: "center",
padding: "0 6px"
};
}, backIconCss = function() {
return {
marginRight: "3px",
cursor: "pointer"
};
}, css = function(props) {
return {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
cursor: "pointer",
height: "" !== props.searchTerm ? "50px" : "30px",
marginTop: "3px",
padding: "0 6px",
fontSize: "14px",
"&:hover": {
backgroundColor: "#ECEEF3"
},
"&:active": {
backgroundColor: "#E1E4EB"
}
};
}, forwardIconCss = function() {
return {};
}, selectedItem = function(props) {
return {
fontWeight: props.isSelected ? 600 : 400
};
}, initialCss = function(props) {
return {
color: "#545769",
fontWeight: props.isSelected ? 600 : 400
};
}, highlightCss = function() {
return {
fontWeight: 600,
color: "#268DEC"
};
}, parentsCss = function() {
return {
color: "#98A1B8",
marginTop: "2px"
};
}, itemsCss = function(_ref) {
return {
maxHeight: _ref.height,
overflowY: "auto"
};
}, css$1 = function() {
return {
width: "100%",
height: "35px",
paddingLeft: "35px",
border: "1px solid #cfd1d2",
borderRight: "none",
borderLeft: "none",
backgroundColor: "#F8F9FA",
"&:focus": {
outline: "none !important"
},
"&:active": {
outline: "none !important"
}
};
}, searchIconCss = function() {
return {
position: "absolute",
top: "8px",
left: "8px"
};
}, clearIconCss = function() {
return {
position: "absolute",
top: "8px",
right: "8px",
cursor: "pointer"
};
}, wrapperCss$1 = function() {
return {
position: "relative"
};
}, css$2 = function(_ref) {
return {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: _ref.height
};
}, icon = function() {
return {
fontSize: "20px",
marginBottom: "5px"
};
}, text = function() {
return {
fontSize: "14px"
};
}, defaultStyles = {
container: containerCss,
header: wrapperCss,
headerBackIcon: backIconCss,
item: css,
highlight: highlightCss,
searchItemInitial: initialCss,
parents: parentsCss,
items: itemsCss,
noResults: css$2,
noResultsIcon: icon,
noResultsText: text,
input: css$1,
searchInput: searchIconCss,
clearInput: clearIconCss,
forwardIcon: forwardIconCss,
selectedItem: selectedItem,
inputWrapper: wrapperCss$1
}, SearchedItem = function(props) {
var _props$item = props.item, item = void 0 === _props$item ? [ "" ] : _props$item, _props$searchIndex = props.searchIndex, searchIndex = void 0 === _props$searchIndex ? 0 : _props$searchIndex, _props$searchTerm = props.searchTerm, searchTerm = void 0 === _props$searchTerm ? "" : _props$searchTerm, getStyles = props.getStyles, leaf = item[item.length - 1], parents = item.slice(0, item.length - 1).join(" / ");
return core.jsx("div", null, core.jsx("div", null, core.jsx("span", {
css: getStyles("searchItemInitial", props)
}, leaf.substring(0, searchIndex)), core.jsx("span", {
css: getStyles("highlight", props)
}, leaf.substring(searchIndex, searchIndex + searchTerm.length)), core.jsx("span", {
css: getStyles("searchItemInitial", props)
}, leaf.substring(searchIndex + searchTerm.length))), core.jsx("div", {
css: getStyles("parents", props)
}, parents));
}, BasicItem = function(props) {
var _props$label = props.label, label = void 0 === _props$label ? "" : _props$label, getStyles = props.getStyles;
return core.jsx("span", {
css: getStyles("selectedItem", props)
}, label);
}, ForwardIconRenderer = function() {
return core.jsx(React__default.Fragment, null, "➡️");
}, ItemRenderer = function(props) {
var _selectedItem$item, getStyles = props.getStyles, _props$searchTerm = props.searchTerm, searchTerm = void 0 === _props$searchTerm ? "" : _props$searchTerm, _props$item = props.item, item = (_props$item = void 0 === _props$item ? {
item: [ "" ],
hasChild: !1,
currentDepth: 0
} : _props$item).item, hasChild = _props$item.hasChild, currentDepth = _props$item.currentDepth, _onClick = props.onClick, _props$forwardIconRen = props.forwardIconRenderer, ForwardIcon = void 0 === _props$forwardIconRen ? ForwardIconRenderer : _props$forwardIconRen, selectedItem = props.selectedItem, isItemSelected = item.toString() === (null == selectedItem ? void 0 : null === (_selectedItem$item = selectedItem.item) || void 0 === _selectedItem$item ? void 0 : _selectedItem$item.toString()), searchIndex = item[item.length - 1].toLowerCase().indexOf(searchTerm.trim().toLowerCase());
return core.jsx("div", {
onClick: function() {
return _onClick(item[currentDepth], item, hasChild);
},
css: getStyles("item", props)
}, "" !== searchTerm && core.jsx(SearchedItem, {
item: item,
searchIndex: searchIndex,
searchTerm: searchTerm.trim(),
getStyles: getStyles,
isSelected: isItemSelected && item[item.length - 1] === selectedItem.leaf
}), "" === searchTerm && core.jsx(BasicItem, {
label: item[currentDepth],
getStyles: getStyles,
isSelected: isItemSelected && item[currentDepth] === selectedItem.leaf
}), hasChild && core.jsx("span", {
css: getStyles("forwardIcon", props)
}, core.jsx(ForwardIcon, null)));
}, BackIconRenderer = function() {
return core.jsx(React__default.Fragment, null, "⬅️");
}, Header = function(props) {
var headerRef = props.headerRef, _props$parents = props.parents, parents = void 0 === _props$parents ? [] : _props$parents, onClick = props.onClick, _props$title = props.title, title = void 0 === _props$title ? "" : _props$title, getStyles = props.getStyles, _props$backIconRender = props.backIconRenderer, BackIcon = void 0 === _props$backIconRender ? BackIconRenderer : _props$backIconRender;
return core.jsx("div", {
ref: headerRef,
css: getStyles("header", props)
}, parents.length > 0 && core.jsx(React__default.Fragment, null, core.jsx("span", {
css: getStyles("headerBackIcon", props),
onClick: onClick
}, core.jsx(BackIcon, null)), parents[parents.length - 1]), 0 === parents.length && core.jsx(React__default.Fragment, null, title));
}, InputIconRenderer = function() {
return core.jsx(React__default.Fragment, null, "🔍");
}, ClearIconRenderer = function() {
return core.jsx(React__default.Fragment, null, "✘️");
}, Input = function(props) {
var inputRef = props.inputRef, searchTerm = props.searchTerm, onInputChange = props.onInputChange, getStyles = props.getStyles, _props$inputIconRende = props.inputIconRenderer, InputIcon = void 0 === _props$inputIconRende ? InputIconRenderer : _props$inputIconRende, _props$clearIconRende = props.clearIconRenderer, ClearIcon = void 0 === _props$clearIconRende ? ClearIconRenderer : _props$clearIconRende;
return core.jsx("div", {
ref: inputRef,
css: getStyles("inputWrapper", props)
}, core.jsx("span", {
css: getStyles("searchInput", props)
}, core.jsx(InputIcon, null)), core.jsx("input", {
css: getStyles("input", props),
value: searchTerm,
onChange: onInputChange
}), "" !== searchTerm && core.jsx("button", {
css: getStyles("clearInput", props),
onClick: onInputChange
}, core.jsx(ClearIcon, null)));
}, NoResultsIconRenderer = function() {
return core.jsx(React__default.Fragment, null, "⚠️");
}, NoResults = function(props) {
var text = props.text, getStyles = props.getStyles, _props$noResultsIconR = props.noResultsIconRenderer, NoResultsIcon = void 0 === _props$noResultsIconR ? NoResultsIconRenderer : _props$noResultsIconR;
return core.jsx("div", {
css: getStyles("noResults", props)
}, core.jsx("div", {
css: getStyles("noResultsIcon", props)
}, core.jsx(NoResultsIcon, null)), core.jsx("div", {
css: getStyles("noResultsText", props)
}, text));
}, containsAllParents = function(leaf, parents) {
if (0 === parents.length) return !0;
var containsAll = !0;
return parents.forEach((function(parent, index) {
parent !== leaf[index] && (containsAll = !1);
})), containsAll;
}, filterBySearchTerm = function(leaf, searchTerm) {
return "" === searchTerm || leaf[leaf.length - 1].toLowerCase().includes(searchTerm.toLowerCase());
}, buildLeafForItemRenderer = function(leaf, currentDepth, searchTerm) {
return {
item: leaf,
hasChild: leaf.length - 1 > currentDepth && "" === searchTerm,
currentDepth: currentDepth,
key: "" === searchTerm ? leaf[currentDepth] : leaf.slice().toString()
};
}, removeDuplicateLeafs = function(leaves) {
return leaves.filter((function(item, index) {
return leaves.findIndex((function(leaf) {
return leaf.key === item.key;
})) === index;
}));
};
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
function _iterableToArrayLimit(arr, i) {
if (Symbol.iterator in Object(arr) || "[object Arguments]" === Object.prototype.toString.call(arr)) {
var _arr = [], _n = !0, _d = !1, _e = void 0;
try {
for (var _s, _i = arr[Symbol.iterator](); !(_n = (_s = _i.next()).done) && (_arr.push(_s.value),
!i || _arr.length !== i); _n = !0) ;
} catch (err) {
_d = !0, _e = err;
} finally {
try {
_n || null == _i.return || _i.return();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
var useLeavesManager = function(_ref) {
var structure = _ref.structure, parents = _ref.parents, currentDepth = _ref.currentDepth, _useState2 = _slicedToArray(React.useState(""), 2), searchTerm = _useState2[0], setSearchTerm = _useState2[1], _useState4 = _slicedToArray(React.useState([]), 2), leaves = _useState4[0], setLeaves = _useState4[1];
return React.useEffect((function() {
var leaves = structure.filter((function(leaf) {
return filterBySearchTerm(leaf, searchTerm) && containsAllParents(leaf, parents);
})).map((function(leaf) {
return buildLeafForItemRenderer(leaf, currentDepth, searchTerm);
}));
setLeaves(removeDuplicateLeafs(leaves));
}), [ searchTerm, parents, currentDepth ]), {
searchTerm: searchTerm,
setSearchTerm: setSearchTerm,
onInputChange: function(event) {
return setSearchTerm(event.target.value || "");
},
leaves: leaves
};
};
function _slicedToArray$1(arr, i) {
return _arrayWithHoles$1(arr) || _iterableToArrayLimit$1(arr, i) || _nonIterableRest$1();
}
function _nonIterableRest$1() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
function _iterableToArrayLimit$1(arr, i) {
if (Symbol.iterator in Object(arr) || "[object Arguments]" === Object.prototype.toString.call(arr)) {
var _arr = [], _n = !0, _d = !1, _e = void 0;
try {
for (var _s, _i = arr[Symbol.iterator](); !(_n = (_s = _i.next()).done) && (_arr.push(_s.value),
!i || _arr.length !== i); _n = !0) ;
} catch (err) {
_d = !0, _e = err;
} finally {
try {
_n || null == _i.return || _i.return();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayWithHoles$1(arr) {
if (Array.isArray(arr)) return arr;
}
var useItemCallbacks = function(_ref) {
var onSelect = _ref.onSelect, _useState2 = _slicedToArray$1(React.useState(0), 2), currentDepth = _useState2[0], setCurrentDepth = _useState2[1], _useState4 = _slicedToArray$1(React.useState([]), 2), parents = _useState4[0], setParents = _useState4[1];
return {
onClick: function(label, item, hasChild) {
hasChild ? (setParents(parents.concat(label)), setCurrentDepth(currentDepth + 1)) : onSelect(item);
},
onBackClick: function() {
setParents(parents.filter((function(parent, index) {
return index < parents.length - 1;
}))), setCurrentDepth(currentDepth - 1);
},
currentDepth: currentDepth,
setCurrentDepth: setCurrentDepth,
parents: parents,
setParents: setParents
};
}, TreeContainerRenderer = function(props) {
var containerRef = props.containerRef, getStyles = props.getStyles, children = props.children;
return core.jsx("div", {
ref: containerRef,
css: getStyles("container", props)
}, children);
}, ItemsRenderer = function(props) {
var getStyles = props.getStyles, children = props.children;
return core.jsx("div", {
css: getStyles("items", props)
}, children);
}, useComponentsRefs = function() {
var _containerRef$current, _headerRef$current, _inputRef$current, containerRef = React.useRef(), headerRef = React.useRef(), inputRef = React.useRef();
return {
containerRef: containerRef,
headerRef: headerRef,
inputRef: inputRef,
itemsHeight: ((null == containerRef ? void 0 : null === (_containerRef$current = containerRef.current) || void 0 === _containerRef$current ? void 0 : _containerRef$current.clientHeight) || 0) - ((null == headerRef ? void 0 : null === (_headerRef$current = headerRef.current) || void 0 === _headerRef$current ? void 0 : _headerRef$current.clientHeight) || 0) - ((null == inputRef ? void 0 : null === (_inputRef$current = inputRef.current) || void 0 === _inputRef$current ? void 0 : _inputRef$current.clientHeight) || 0)
};
}, DEFAULT_WIDTH = 230, DEFAULT_HEIGHT = 300, PIXEL_SUFFIX = "px", Tree = function(props) {
var _props$structure = props.structure, structure = void 0 === _props$structure ? [] : _props$structure, title = props.title, onSelect = props.onSelect, width = props.width, height = props.height, styles = props.styles, _props$noResultsText = (props.className,
props.noResultsText), noResultsText = void 0 === _props$noResultsText ? "No matching results" : _props$noResultsText, _props$headerRenderer = props.headerRenderer, Header$1 = void 0 === _props$headerRenderer ? Header : _props$headerRenderer, backIconRenderer = props.backIconRenderer, _props$inputRenderer = props.inputRenderer, Input$1 = void 0 === _props$inputRenderer ? Input : _props$inputRenderer, inputIconRenderer = props.inputIconRenderer, clearIconRenderer = props.clearIconRenderer, _props$noResultsRende = props.noResultsRenderer, NoResults$1 = void 0 === _props$noResultsRende ? NoResults : _props$noResultsRende, noResultsIconRenderer = props.noResultsIconRenderer, _props$itemRenderer = props.itemRenderer, Item = void 0 === _props$itemRenderer ? ItemRenderer : _props$itemRenderer, _props$itemsRenderer = props.itemsRenderer, Items = void 0 === _props$itemsRenderer ? ItemsRenderer : _props$itemsRenderer, forwardIconRenderer = props.forwardIconRenderer, _props$treeContainerR = props.treeContainerRenderer, TreeContainer = void 0 === _props$treeContainerR ? TreeContainerRenderer : _props$treeContainerR, customItemsRenderer = props.customItemsRenderer, selectedItem = props.selectedItem, getStyles = function(key) {
var props = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}, base = defaultStyles[key](props);
base.boxSizing = "border-box";
var custom = props.styles && props.styles[key];
return custom ? custom(base, props) : base;
}, _useContainerHeight = useComponentsRefs(), containerRef = _useContainerHeight.containerRef, headerRef = _useContainerHeight.headerRef, inputRef = _useContainerHeight.inputRef, itemsHeight = _useContainerHeight.itemsHeight, _useItemCallbacks = useItemCallbacks({
onSelect: onSelect
}), onClick = _useItemCallbacks.onClick, onBackClick = _useItemCallbacks.onBackClick, currentDepth = _useItemCallbacks.currentDepth, parents = _useItemCallbacks.parents, _useLeavesManager = useLeavesManager({
structure: structure,
parents: parents,
currentDepth: currentDepth
}), searchTerm = _useLeavesManager.searchTerm, onInputChange = _useLeavesManager.onInputChange, leaves = _useLeavesManager.leaves;
return core.jsx(TreeContainer, {
containerRef: containerRef,
getStyles: getStyles,
styles: styles,
width: (width || DEFAULT_WIDTH) + PIXEL_SUFFIX,
height: (height || DEFAULT_HEIGHT) + PIXEL_SUFFIX
}, core.jsx(Header$1, {
headerRef: headerRef,
parents: parents,
title: title,
onClick: onBackClick,
getStyles: getStyles,
styles: styles,
backIconRenderer: backIconRenderer
}, title), core.jsx(Input$1, {
inputRef: inputRef,
getStyles: getStyles,
styles: styles,
searchTerm: searchTerm,
onInputChange: onInputChange,
inputIconRenderer: inputIconRenderer,
clearIconRenderer: clearIconRenderer
}), customItemsRenderer ? React__default.cloneElement(customItemsRenderer, {
leaves: leaves,
searchTerm: searchTerm,
onClick: onClick,
selectedItem: selectedItem,
forwardIconRenderer: forwardIconRenderer
}) : core.jsx(Items, {
styles: styles,
getStyles: getStyles,
height: itemsHeight
}, leaves && leaves.length > 0 && leaves.map((function(item) {
return core.jsx(Item, {
getStyles: getStyles,
searchTerm: searchTerm,
item: item,
onClick: onClick,
forwardIconRenderer: forwardIconRenderer,
selectedItem: selectedItem
});
}))), leaves && 0 === leaves.length && core.jsx(NoResults$1, {
height: itemsHeight,
text: noResultsText,
getStyles: getStyles,
styles: styles,
noResultsIconRenderer: noResultsIconRenderer
}));
};
exports.default = Tree;