@hitachivantara/uikit-react-core
Version:
Core React components for the NEXT Design System.
286 lines (285 loc) • 8.86 kB
JavaScript
import { jsx, jsxs } from "react/jsx-runtime";
import { useContext, useState, useCallback, useMemo, useEffect } from "react";
import { useDefaultProps } from "@hitachivantara/uikit-react-utils";
import { useControlled } from "../../hooks/useControlled.js";
import { uniqueId } from "../../utils/helpers.js";
import { setId } from "../../utils/setId.js";
import { NavigationPopupContainer } from "../NavigationPopup/NavigationPopupContainer.js";
import { getParentItemById } from "../NavigationSlider/utils.js";
import { VerticalNavigationContext } from "../VerticalNavigationContext.js";
import { useClasses } from "./Navigation.styles.js";
import { staticClasses } from "./Navigation.styles.js";
import { HvVerticalNavigationSlider } from "../NavigationSlider/NavigationSlider.js";
import { HvVerticalNavigationTreeView } from "../TreeView/TreeView.js";
import { HvVerticalNavigationTreeViewItem } from "../TreeView/TreeViewItem.js";
const createListHierarchy = (items, id, classes, mouseEnterHandler, disableTooltip = false) => items.map((item) => {
const {
id: itemId,
label: itemLabel,
icon,
data: children,
selectable,
disabled,
href,
target
} = item;
const itemMouseEnterHandler = (event) => {
mouseEnterHandler?.(event, item);
};
return /* @__PURE__ */ jsx(
HvVerticalNavigationTreeViewItem,
{
id: setId(id, itemId),
className: classes?.listItem,
href,
target,
nodeId: itemId,
label: itemLabel,
icon,
payload: item,
selectable,
disabled,
onMouseEnter: itemMouseEnterHandler,
disableTooltip,
children: children ? createListHierarchy(
children,
id,
classes,
mouseEnterHandler,
disableTooltip
) : void 0
},
itemId
);
});
const getAllParents = (items) => {
const parents = items.filter(
(item) => item.data != null && item.data.length > 0
);
const childParents = parents.flatMap((item) => getAllParents(item.data));
return [...parents, ...childParents];
};
function pathToElement(data, targetId) {
const path = [];
if (data != null && data.length > 0) {
for (let i = 0; i !== data.length; ++i) {
const item = data[i];
if (item.id === targetId) {
path.push(item.id);
break;
}
const subPaths = pathToElement(item.data, targetId);
if (subPaths.length > 0) {
path.push(item.id);
path.push(...subPaths);
break;
}
}
}
return path;
}
const HvVerticalNavigationTree = (props) => {
const {
id,
className,
classes: classesProp,
data,
mode = "navigation",
collapsible = false,
expanded: expandedProp,
defaultExpanded,
onToggle,
selected: selectedProp,
defaultSelected,
onChange,
sliderForwardButtonAriaLabel = "Navigate to submenu",
...others
} = useDefaultProps("HvVerticalNavigationTree", props);
const { classes, cx } = useClasses(classesProp);
const [selected, setSelected] = useControlled(selectedProp, defaultSelected);
const [expanded, setExpanded] = useControlled(expandedProp, () => {
if (defaultExpanded === true) {
return getAllParents(data).map((item) => item.id);
}
if (defaultExpanded === false) {
return [];
}
if (defaultExpanded == null) {
if (selected != null) {
const path = pathToElement(data, selected);
return path.slice(0, -1);
}
return [];
}
return defaultExpanded;
});
const {
isOpen,
useIcons,
slider,
parentItem,
setParentItem,
withParentData,
navigateToChildHandler,
setParentData,
setParentSelected
} = useContext(VerticalNavigationContext);
const [navigationPopup, setNavigationPopup] = useState(null);
const handleChange = useCallback(
(event, selectedId, selectedItem) => {
if (useIcons && !isOpen && selectedItem.data) {
const currentEventTarget = event.currentTarget;
setNavigationPopup((prevState) => {
return prevState?.anchorEl === currentEventTarget ? null : {
uniqueKey: uniqueId(),
anchorEl: currentEventTarget,
fixedMode: true,
data: selectedItem.data
};
});
event.stopPropagation();
} else {
setSelected(selectedId);
setExpanded((prevState) => {
if (!isOpen) {
return [...prevState, ...pathToElement(data, selectedId)];
}
return [...prevState];
});
setNavigationPopup(null);
onChange?.(event, selectedItem);
}
},
[onChange, setSelected, setExpanded, isOpen, useIcons, data]
);
const treeViewItemMouseEnterHandler = useCallback(
(event, item) => {
const isCollapsed = useIcons && !isOpen;
if (isCollapsed && item.data && !navigationPopup?.fixedMode) {
const currentEventTarget = event.currentTarget;
setNavigationPopup?.({
uniqueKey: uniqueId(),
anchorEl: currentEventTarget,
fixedMode: false,
data: item.data
});
} else if (isCollapsed && !item.data && !navigationPopup?.fixedMode) {
setNavigationPopup(null);
}
},
[isOpen, useIcons, navigationPopup]
);
const handleToggle = useCallback(
(event, newExpanded) => {
setExpanded(newExpanded);
onToggle?.(event, newExpanded);
},
[onToggle, setExpanded]
);
const children = useMemo(
() => data && createListHierarchy(
data,
id,
classes,
treeViewItemMouseEnterHandler,
navigationPopup?.fixedMode
),
[classes, data, id, navigationPopup, treeViewItemMouseEnterHandler]
);
useEffect(() => {
if (!isOpen) {
setNavigationPopup?.(null);
}
}, [isOpen]);
useEffect(() => {
if (setParentSelected) setParentSelected(selected);
}, [selected, setSelected, setParentSelected]);
useEffect(() => {
if (setParentData) setParentData(data);
}, [data, setParentData]);
useEffect(() => {
if (withParentData && selected && setParentItem && getParentItemById(withParentData, selected)) {
setParentItem(getParentItemById(withParentData, selected));
}
}, [withParentData, selected, setParentItem]);
const navigateToTargetHandler = (event, selectedItem) => handleChange(event, selectedItem.id, selectedItem);
const handleNavigationPopupClose = () => setNavigationPopup(null);
const handleStyledNavMouseLeave = () => {
if (useIcons && !isOpen && !navigationPopup?.fixedMode) {
setNavigationPopup(null);
}
};
const handleNavigationPopupMouseLeave = () => {
if (!navigationPopup?.fixedMode) {
handleNavigationPopupClose();
}
};
const handleNavigationPopupChange = (event, selectedItem) => {
handleChange(event, selectedItem.id, selectedItem);
};
return /* @__PURE__ */ jsx(
"nav",
{
id,
className: cx(
classes.root,
{ [classes.collapsed]: !isOpen && !useIcons },
className
),
onMouseLeave: handleStyledNavMouseLeave,
...others,
children: slider ? /* @__PURE__ */ jsx(
HvVerticalNavigationSlider,
{
data: parentItem.data || withParentData,
selected,
onNavigateToTarget: navigateToTargetHandler,
onNavigateToChild: navigateToChildHandler,
forwardButtonAriaLabel: sliderForwardButtonAriaLabel
}
) : /* @__PURE__ */ jsxs(
HvVerticalNavigationTreeView,
{
id: setId(id, "tree"),
className: classes.list,
selectable: true,
mode,
collapsible,
selected,
onChange: handleChange,
expanded,
onToggle: handleToggle,
children: [
useIcons && !isOpen && navigationPopup && /* @__PURE__ */ jsx(
NavigationPopupContainer,
{
anchorEl: navigationPopup.anchorEl,
onClose: handleNavigationPopupClose,
className: classes.navigationPopup,
children: /* @__PURE__ */ jsx(
HvVerticalNavigationTree,
{
className: classes.popup,
id: setId(id, "navigation-popup-tree"),
collapsible: true,
defaultExpanded: true,
selected,
data: navigationPopup.data,
onChange: handleNavigationPopupChange,
onMouseLeave: handleNavigationPopupMouseLeave
}
)
},
navigationPopup.uniqueKey
),
children
]
}
)
}
);
};
export {
HvVerticalNavigationTree,
staticClasses as verticalNavigationTreeClasses
};