orcs-design-system
Version:
TeamForm's Design System, aka: ORCS
341 lines (337 loc) • 11 kB
JavaScript
import React, { useRef, useState, useEffect } from "react";
import styled from "styled-components";
import { css } from "@styled-system/css";
import { themeGet } from "@styled-system/theme-get";
import PropTypes from "prop-types";
import Icon from "../Icon";
import Popover from "../Popover";
import Divider from "../Divider";
import NavItem from "./NavItem";
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
const SMALL_SCREEN_BREAKPOINT = 900;
const SideNavWrapper = styled("div").withConfig({
displayName: "SideNav__SideNavWrapper",
componentId: "sc-1heo0i9-0"
})(props => css({
bg: themeGet("colors.white")(props),
color: themeGet("colors.greyDarkest")(props),
minHeight: props.sideNavHeight,
height: props.sideNavHeight,
maxWidth: "max-content",
fontFamily: themeGet("fonts.main")(props),
borderRadius: themeGet("radii.2")(props),
border: `solid 1px ${themeGet("colors.greyLighter")(props)}`,
display: "flex",
alignItems: "stretch",
alignContent: "stretch",
[`@media screen and (max-width: ${SMALL_SCREEN_BREAKPOINT}px)`]: {
borderRadius: "0",
width: "100%",
height: "auto",
minWidth: "auto",
maxWidth: "initial",
minHeight: "initial",
maxHeight: themeGet("appScale.sidebarMobileHeight")(props),
position: "fixed",
bottom: "0",
left: "0",
zIndex: "6",
flexDirection: "column-reverse"
}
}));
const SideNavItems = styled("div").withConfig({
displayName: "SideNav__SideNavItems",
componentId: "sc-1heo0i9-1"
})(props => css({
minWidth: themeGet("appScale.sideNavSize")(props),
minHeight: "inherit",
height: "100%",
display: "flex",
flexDirection: "column",
padding: "s",
alignItems: "center",
justifyContent: "flex-start",
textAlign: "center",
position: "relative",
[`@media screen and (max-width: ${SMALL_SCREEN_BREAKPOINT}px)`]: {
height: `calc(${themeGet("space.s")(props)} * 2 + 30px)`,
flexDirection: "row",
justifyContent: "space-around",
alignItems: "center"
}
}));
const PanelControlTooltip = styled(Popover).withConfig({
displayName: "SideNav__PanelControlTooltip",
componentId: "sc-1heo0i9-2"
})(["position:absolute;right:", ";top:", ";"], themeGet("space.r"), themeGet("space.r"));
const PanelControl = styled("button").withConfig({
displayName: "SideNav__PanelControl",
componentId: "sc-1heo0i9-3"
})(props => css({
borderRadius: "50%",
width: "20px",
height: "20px",
background: themeGet("colors.greyLighter")(props),
color: themeGet("colors.greyDarker")(props),
fontSize: "1.2rem",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
border: "none"
}));
const SideNavExpanded = styled("div").withConfig({
displayName: "SideNav__SideNavExpanded",
componentId: "sc-1heo0i9-4"
})(props => css({
position: "relative",
display: props.active ? "block" : "none",
minWidth: props.large ? "calc(" + themeGet("appScale.sidebarMaxWidthLarge")(props) + " - " + themeGet("appScale.newNavBarSize")(props) + ")" : "calc(" + themeGet("appScale.sidebarMaxWidth")(props) + " - " + themeGet("appScale.newNavBarSize")(props) + ")",
maxWidth: props.large ? "calc(" + themeGet("appScale.sidebarMaxWidthLarge")(props) + " - " + themeGet("appScale.newNavBarSize")(props) + ")" : "calc(" + themeGet("appScale.sidebarMaxWidth")(props) + " - " + themeGet("appScale.newNavBarSize")(props) + ")",
height: "inherit",
overflowY: "auto",
padding: "16px",
borderLeft: `solid 1px ${themeGet("colors.greyLighter")(props)}`,
"&:focus": {
outline: "0"
},
[`@media screen and (max-width: ${SMALL_SCREEN_BREAKPOINT}px)`]: {
width: "100%",
minWidth: "initial",
maxWidth: "initial",
borderLeft: "none",
borderBottom: `solid 1px ${themeGet("colors.greyLighter")(props)}`,
height: "calc(" + themeGet("appScale.sidebarMobileHeight")(props) + " - " + themeGet("appScale.newNavBarSize")(props) + ")"
}
}));
const SideNav = _ref => {
let {
items,
sideNavHeight
} = _ref;
const [expandedItem, setExpandedItem] = useState(null);
// Initialize a ref for SideNavExpanded
const expandedRef = useRef(null);
const navItemRefs = useRef({});
const firstExpandedItemByDefault = items.findIndex(item => item.isExpandedByDefault && !item.hide);
useEffect(() => {
if (firstExpandedItemByDefault >= 0) {
setExpandedItem(firstExpandedItemByDefault);
}
}, [firstExpandedItemByDefault]);
const handleItemClick = item => {
const {
index: itemIndex,
actionType,
onClick: onButtonClick
} = item;
if (actionType === "link" || actionType === "button") {
setExpandedItem(null);
onButtonClick && onButtonClick(item);
} else {
setExpandedItem(itemIndex === expandedItem ? null : itemIndex);
onButtonClick && onButtonClick(item);
}
};
// Split items into two arrays based on the bottomAligned prop
const allItems = items.map((item, index) => ({
...item,
index
}));
const topAlignedItems = allItems.filter(item => !item.bottomAligned && !item.pageSpecific);
const pageSpecificItems = allItems.filter(item => !item.bottomAligned && item.pageSpecific);
const bottomAlignedItems = allItems.filter(item => item.bottomAligned);
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const isSmallScreen = windowWidth < SMALL_SCREEN_BREAKPOINT;
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
const debounceResize = () => setTimeout(handleResize, 300); // Wrap in a function
useEffect(() => {
return () => clearTimeout(handleResize);
}, []);
useEffect(() => {
window.addEventListener("resize", debounceResize);
return () => window.removeEventListener("resize", debounceResize);
});
// Use a useEffect to focus on the expanded item
useEffect(() => {
if (expandedItem !== null && expandedRef.current) {
expandedRef.current.focus();
}
}, [expandedItem]);
const handleBlur = item => {
handleItemClick(item);
};
const orderedItems = [...topAlignedItems, ...pageSpecificItems, ...bottomAlignedItems];
const renderItem = (item, index) => {
if (item.hide) {
return null;
}
const Component = item.component;
const Item = /*#__PURE__*/_jsx(NavItem, {
item: item,
isSmallScreen: isSmallScreen,
Component: Component,
isActive: item.actionType === "link" ? item.isActive : expandedItem == item.index,
handleItemClick: handleItemClick,
navItemRefs: navItemRefs
}, item.index);
if (item.pageSpecific && !orderedItems[index - 1].pageSpecific && !isSmallScreen) {
return /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(Divider, {
light: true,
mt: "s",
mb: "r",
display: ["none", "none", "none", "block"]
}), Item]
});
}
return Item;
};
return /*#__PURE__*/_jsxs(SideNavWrapper, {
sideNavHeight: sideNavHeight,
children: [/*#__PURE__*/_jsx(SideNavItems, {
children: orderedItems.map(renderItem)
}), allItems.map((item, index) => {
if (item.actionType !== "component") return null;
if (index !== expandedItem || item.hide) return null;
return /*#__PURE__*/_jsxs(SideNavExpanded, {
ref: expandedRef,
tabIndex: "0",
sideNavHeight: sideNavHeight,
active: expandedItem,
large: item.large,
children: [item.component(), /*#__PURE__*/_jsx(PanelControlTooltip, {
width: "80px",
textAlign: "center",
direction: "left",
text: "Hide panel",
children: /*#__PURE__*/_jsx(PanelControl, {
onClick: () => handleItemClick(item),
"aria-label": "toggle panel",
onBlur: () => handleBlur(item),
children: /*#__PURE__*/_jsx(Icon, {
icon: ["fas", isSmallScreen ? "chevron-down" : "chevron-left"]
})
})
})]
}, item.name);
})]
});
};
SideNav.propTypes = {
sideNavHeight: PropTypes.string.isRequired,
// Used to specify the height of the sideNav
initiallyExpandedItemIndex: PropTypes.number,
// Used to specify the index of the item that should be expanded by default
items: PropTypes.arrayOf(PropTypes.shape({
iconName: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
badgeNumber: PropTypes.string,
badgeDot: PropTypes.bool,
hide: PropTypes.bool,
large: PropTypes.bool,
bottomAligned: PropTypes.bool,
actionType: PropTypes.oneOf(["component", "link", "button"]).isRequired,
component: PropTypes.elementType,
link: PropTypes.string,
onClick: PropTypes.func
})).isRequired,
LinkComponent: PropTypes.elementType
};
SideNav.__docgenInfo = {
"description": "",
"methods": [],
"displayName": "SideNav",
"props": {
"sideNavHeight": {
"description": "",
"type": {
"name": "string"
},
"required": true
},
"initiallyExpandedItemIndex": {
"description": "",
"type": {
"name": "number"
},
"required": false
},
"items": {
"description": "",
"type": {
"name": "arrayOf",
"value": {
"name": "shape",
"value": {
"iconName": {
"name": "string",
"required": true
},
"name": {
"name": "string",
"required": true
},
"badgeNumber": {
"name": "string",
"required": false
},
"badgeDot": {
"name": "bool",
"required": false
},
"hide": {
"name": "bool",
"required": false
},
"large": {
"name": "bool",
"required": false
},
"bottomAligned": {
"name": "bool",
"required": false
},
"actionType": {
"name": "enum",
"value": [{
"value": "\"component\"",
"computed": false
}, {
"value": "\"link\"",
"computed": false
}, {
"value": "\"button\"",
"computed": false
}],
"required": true
},
"component": {
"name": "elementType",
"required": false
},
"link": {
"name": "string",
"required": false
},
"onClick": {
"name": "func",
"required": false
}
}
}
},
"required": true
},
"LinkComponent": {
"description": "",
"type": {
"name": "elementType"
},
"required": false
}
}
};
export default SideNav;