irisrad-ui
Version:
UI elements developered for IRIS R&D Group Inc
203 lines (191 loc) • 7.33 kB
JavaScript
/**
* IRIS R&D Group Inc. All rights reserved.
*
* Author: Lucien Chu
* Create Date: Apr 07, 2021
*
* Description: Defines the left hand side navigation wrapper and its nav links
*/
/**
* IRIS R&D Group Inc. All rights reserved.
*
* Author: Lucien Chu
* Create Date: Apr 07, 2021
*
* Description: The nav link wrapper which could display an specified icon on the left
* and the label titel on the right.
*
* It uses Google Font which styles material ui icon and has been imported via css. As a result, simply find the specific
* icon via https://fonts.google.com/icons, select an icon and specify necessary inforatmion, you
* should see the html implementation like, <span class="material-icons-variant">icon_title</span>,
* which this component is using.
*
* the icon_title specifies the icon to be show
* the value after "material-icons" specifies the variants of the icon, ie: "material-icons-round" specifies that the icon
* should be styled as rounded. Current support variants are "two-tone", "sharp", "round", "outlined".
*
* In order to show the icon in this component, specify the iconProps like this
* iconProps: {iconTitle: icon_title, variant: value_after_"material-icons-", size: "small" | "medium" | "large" | "xlarge"}
*
*/
import React, { useEffect, useRef } from "react";
import "../../style/styles.css";
import PropTypes from "prop-types";
import {
IrisDrawer,
DRAWER_MIN_WIDTHS,
DRAWER_MAX_WIDTHS,
} from "../irisDrawer/IrisDrawer";
import { IrisMenu, IrisMenuItem } from "../irisMenu/IrisMenu";
const SIDE_NAV_BASE_CLASS = "iris-side-nav";
const MATERIAL_UI_ICON_BASIC_CLASS = "material-icons";
// different icons styles predefined by the google font for the material icons
const MUI_VARIANTS = ["two-tone", "sharp", "round", "outlined"];
// sizes of the material icons, defiend locally as the css is modified, easier to read and use
const MUI_SIZES = ["small", "medium", "large", "xlarge"];
function IrisSideNavLink({ iconProps, label }) {
const { variant, size, iconTitle } = iconProps;
const iconStyles = [MATERIAL_UI_ICON_BASIC_CLASS];
if (MUI_VARIANTS.indexOf(variant) >= 0) {
iconStyles.push(`${MATERIAL_UI_ICON_BASIC_CLASS}-${variant}`);
}
if (MUI_SIZES.indexOf(size) >= 0) {
iconStyles.push(`${MATERIAL_UI_ICON_BASIC_CLASS}__size-${size}`);
}
return (
<span className="iris-nav-link">
<span className={iconStyles.join(" ")}>{iconTitle}</span>{" "}
<span>{label}</span>
</span>
);
}
IrisSideNavLink.propTypes = {
label: PropTypes.string.isRequired, // title of the nav link, ie: Home, About Us, etc
iconProps: PropTypes.shape({
iconTitle: PropTypes.string, // material icons title, ie: home, local_fire_department, etc
size: PropTypes.oneOf(MUI_SIZES), // icon sizes
variant: PropTypes.oneOf(MUI_VARIANTS), // "" is default material icon styles, if given, value should be one of "small" | "medium" | "large" | "xlarge"
}),
onClick: PropTypes.func,
};
/**
* IRIS R&D Group Inc. All rights reserved.
*
* Author: Lucien Chu
* Create Date: Apr 07, 2021
*
* Description: This is a container of navigation links (SideNavLinks), it uses the IrisDrawer
* so that the links are expandable.
*
* Each navigation link is wrapper in side the IrisMenuItem and then all menu items are wrapper
* inside the IrisMenu, for styling purpose.
*
* The IrisMenuItem of a navigation link should be highlighted when the link is clicked. As a result,
* before executing the passed in callback function, when a navigtaion link is clicked, previous
* highlighted IrisMenuItem should be cleared and a the clicked navigation link's parent (another IrisMenuItem)
* should be highlighted.
*
* LIMITATION: the height of this wrapper's parent MUST be specified.
*
*/
function IrisSideNav({
links,
minWidth = 60,
maxWidth = 200,
className = "",
...props
}) {
const styles = [
SIDE_NAV_BASE_CLASS,
`${SIDE_NAV_BASE_CLASS}--fixed-${minWidth}`,
`${SIDE_NAV_BASE_CLASS}--max-width-${maxWidth}`,
];
const sideNaveRef = useRef(null);
/**
* @summary toggle the active styles of the list elements and executed the passed in callback function
*
* @description before executing the passed in callback function,
* firstly, find the list item which wraps the clicked navigation link should be maked as active later on
* secondly, find list item (IrisMenu), remove all "active" class from all of its children
* lastly, marked the found list item as "active"
*
*
* @param {ClickEvent} event click event
* @param {Function} callBack function to be executed
*/
const handleClicked = (event, callBack) => {
let listNode = event.target;
let listItemNode;
while (listNode) {
if (listNode.classList.contains("iris-list__item")) {
// keep the clicked list reference
listItemNode = listNode;
}
if (listNode.classList.contains("iris-list")) {
for (let i = 0; i < listNode.childNodes.length; i++) {
// use the clicked list referece to determined whehter or not
// to toggle the active class of an list item
listNode.childNodes[i].classList.toggle(
"active",
listNode.childNodes[i] === listItemNode
);
}
break;
}
listNode = listNode.parentElement;
}
callBack();
};
useEffect(() => {
const current = sideNaveRef?.current;
const pathName = window.location.pathname;
if (pathName === "/iframe.html") {
if (current) {
const list = current.querySelector(".iris-list");
list.childNodes[0].classList.toggle("active");
}
}
}, []);
if (typeof className === "string" && className != "") {
styles.push(className);
}
return (
<div className={styles.join(" ")} ref={sideNaveRef} {...props}>
<IrisDrawer minWidth={minWidth} maxWidth={maxWidth}>
{links && links.length > 0 ? (
<IrisMenu>
{links.map(({ label, onClick, iconProps }) => (
<IrisMenuItem
key={label}
onClick={(event) => handleClicked(event, onClick)}
>
<IrisSideNavLink label={label} iconProps={iconProps} />
</IrisMenuItem>
))}
</IrisMenu>
) : (
<p>
Please specify property <em>links</em>
</p>
)}
</IrisDrawer>
</div>
);
}
IrisSideNav.propTypes = {
className: PropTypes.string,
links: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
iconProps: PropTypes.shape({
iconTitle: PropTypes.string, // material icons title, ie: home, local_fire_department, etc
size: PropTypes.oneOf(MUI_SIZES), // icon sizes
variant: PropTypes.oneOf(MUI_VARIANTS), // "" is default material icon styles, if given, value should be one of "small" | "medium" | "large" | "xlarge"
}),
onclick: PropTypes.func,
})
).isRequired,
minWidth: PropTypes.oneOf(DRAWER_MIN_WIDTHS),
maxWidth: PropTypes.oneOf(DRAWER_MAX_WIDTHS),
};
export { IrisSideNav };