UNPKG

orcs-design-system

Version:
341 lines (337 loc) 11 kB
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;