UNPKG

orcs-design-system

Version:
211 lines (206 loc) 9.84 kB
import React, { useState, useRef, useEffect, useCallback } from "react"; import styled, { css } from "styled-components"; import { NavLink, useLocation } from "react-router-dom"; import { isEqual } from "lodash"; import ActionsMenu, { ActionsMenuItem } from "../ActionsMenu"; import Icon from "../Icon"; import FlexItem from "../Flex"; import { themeGet } from "@styled-system/theme-get"; import PropTypes from "prop-types"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const TabsContainer = styled.div.withConfig({ displayName: "TabsAlt__TabsContainer", componentId: "sc-hkpvfu-0" })(["position:relative;width:100%;height:", ";&:after{content:\"\";position:absolute;bottom:0;width:100%;height:100%;background-color:", ";border-bottom:2px solid ", ";z-index:0;}"], themeGet("appScale.tabsHeight"), themeGet("colors.white"), themeGet("colors.greyLighter")); const TabWrapper = styled.div.withConfig({ displayName: "TabsAlt__TabWrapper", componentId: "sc-hkpvfu-1" })(["position:relative;width:100%;z-index:1;height:inherit;"]); const VisibleTabs = styled.div.withConfig({ displayName: "TabsAlt__VisibleTabs", componentId: "sc-hkpvfu-2" })(["flex-shrink:1;display:flex;align-items:center;justify-content:flex-start;overflow:hidden;height:inherit;"]); const activeTabStyle = css(["background-color:", ";color:", ";border-bottom:2px solid ", ";cursor:default;&:hover,&:focus{background-color:", ";color:", ";box-shadow:none;}"], themeGet("colors.white"), themeGet("colors.primary"), themeGet("colors.primary"), themeGet("colors.white"), themeGet("colors.primary")); const Tab = styled(NavLink).withConfig({ displayName: "TabsAlt__Tab", componentId: "sc-hkpvfu-3" })(["width:", ";display:block;transition:background 200ms ease-in-out,color 200ms ease-in-out;border-bottom:2px solid ", ";padding:0 ", ";height:", ";display:flex;align-items:center;font-size:", ";font-weight:", ";position:relative;white-space:nowrap;text-decoration:none;text-align:center;background-color:", ";color:", ";cursor:pointer;", " &:hover{background-color:", ";color:", ";box-shadow:inset 0 2px 5px 0 ", ";}&:focus{outline:0;box-shadow:inset 0 2px 5px 0 ", ";}&.active{", "}"], _ref => { let { fullWidth } = _ref; return fullWidth ? "100%" : "fit-content"; }, themeGet("colors.greyLighter"), themeGet("space.4"), themeGet("appScale.tabsHeight"), themeGet("fontSizes.1"), themeGet("fontWeights.2"), themeGet("colors.white"), themeGet("colors.greyDark"), _ref2 => { let { tabInShowMore } = _ref2; return tabInShowMore ? css(["position:absolute;visibility:hidden;"]) : ""; }, themeGet("colors.white"), themeGet("colors.primary"), themeGet("colors.primaryLightest"), themeGet("colors.primaryLightest"), activeTabStyle); const ShowMoreButton = styled.button.withConfig({ displayName: "TabsAlt__ShowMoreButton", componentId: "sc-hkpvfu-4" })(["appearance:none;border:none;font-family:", ";font-size:", ";font-weight:", ";background-color:", ";border-bottom:2px solid ", ";transition:", ";padding:0 ", ";height:", ";color:", ";display:", ";align-items:center;cursor:pointer;&:hover{background-color:", ";color:", ";box-shadow:inset 0 2px 5px 0 ", ";}&:focus{outline:0;box-shadow:inset 0 2px 5px 0 ", ";}&.hasActive{", "}"], themeGet("fonts.main"), themeGet("fontSizes.1"), themeGet("fontWeights.2"), themeGet("colors.white"), themeGet("colors.greyLighter"), themeGet("transition.transitionDefault"), themeGet("space.4"), themeGet("appScale.tabsHeight"), themeGet("colors.greyDark"), _ref3 => { let { showMoreVisible } = _ref3; return showMoreVisible ? "flex" : "none"; }, themeGet("colors.white"), themeGet("colors.primary"), themeGet("colors.primaryLightest"), themeGet("colors.primaryLightest"), activeTabStyle); const ShowMoreTabs = styled.div.withConfig({ displayName: "TabsAlt__ShowMoreTabs", componentId: "sc-hkpvfu-5" })(["border-radius:", ";background-color:", ";width:fit-content;overflow:hidden;display:flex;gap:1px;flex-direction:column;justify-content:flex-start;box-shadow:", ";& [class^=\"TabsAlt__Tab\"]{border:0;padding:", ";text-align:left;}"], themeGet("radii.2"), themeGet("colors.greyLighter"), themeGet("shadows.boxDefault"), themeGet("space.3")); const tabsGap = 0; const TabsAlt = _ref4 => { let { tabsList } = _ref4; const [isMounted, setIsMounted] = useState(false); const containerRef = useRef(null); const containerVisibleWidth = useRef(0); const [showMoreTabs, setShowMoreTabs] = useState([]); const calculateVisibility = useCallback(actionElements => { const showMoreButton = document.getElementById("show-more-button"); const showMoreButtonWidth = showMoreButton?.offsetWidth ?? 0; // as we loop through the tabs, we need to calculate the width of the visible tabs. let calculatedWidth = showMoreTabs.length ? showMoreButtonWidth : 0; // variable for the list of hidden tabs which will be put to react state const newShowMoreTabs = []; [...actionElements].filter(el => el.tagName === "A") // this ensures that the ShowMore button is not included in this logic .forEach((actionEl, i) => { // visibleElementsWidth will be increased by // the corresponding width of the element + gapWidth calculatedWidth += actionEl.offsetWidth + tabsGap; // compare computed widths and push into newShowMoreTabs if current tab width is bigger than container width if (calculatedWidth >= containerVisibleWidth.current) { newShowMoreTabs.push(i); } }); if (!isEqual(showMoreTabs, newShowMoreTabs)) { // update React state with the list of hidden tabs setShowMoreTabs(newShowMoreTabs); } }, [showMoreTabs]); useEffect(() => { const actionElements = containerRef.current?.children || []; const resizeObserver = new ResizeObserver(entries => { for (const entry of entries) { if (entry.contentBoxSize) { const contentBoxSize = entry.contentBoxSize[0]; // Math.ceil is necessary to round up and return // the smallest integer for the size of observed element containerVisibleWidth.current = Math.ceil(contentBoxSize.inlineSize); // invoke the functions which calculates tabs visibility // and sets data to the list of hidden tabs calculateVisibility(actionElements); } } }); // This is to help in cases where useEffect callback is triggered before React repaint. if (!isMounted) { setTimeout(() => { setIsMounted(true); calculateVisibility(actionElements); }, 500); } else { // adding ResizeObserver to the observed container resizeObserver.observe(containerRef.current); } }, [calculateVisibility, isMounted]); const visibleTabsList = tabsList.filter(tab => tab.isVisible ?? true); const showMoreTabsList = visibleTabsList.filter((_tab, i) => showMoreTabs.includes(i)); const location = useLocation(); const showMoreItemActive = showMoreTabsList.find(action => location.pathname.endsWith(action.path)); return /*#__PURE__*/_jsx(TabsContainer, { children: /*#__PURE__*/_jsx(TabWrapper, { children: /*#__PURE__*/_jsxs(VisibleTabs, { style: { gap: tabsGap }, ref: containerRef, role: "tablist", children: [visibleTabsList.map((tab, i) => /*#__PURE__*/_jsx(Tab, { className: tab.className, id: tab.id, "data-testid": tab["data-testid"], tabInShowMore: showMoreTabs.includes(i), to: tab.path, role: "tab", children: tab.label }, tab.path)), /*#__PURE__*/_jsx(ActionsMenu, { direction: "bottom-end", role: "presentation", renderTrigger: props => /*#__PURE__*/_jsxs(ShowMoreButton, { ...props, showMoreVisible: showMoreTabsList.length, id: "show-more-button", className: showMoreItemActive && "hasActive", role: "tab", children: [/*#__PURE__*/_jsx(FlexItem, { flex: "0 0 auto", children: "More" }), /*#__PURE__*/_jsx(FlexItem, { flex: "0 0 auto", children: /*#__PURE__*/_jsx(Icon, { icon: ["fas", "chevron-down"], title: "Down", ml: "xs", size: "sm" }) })] }), closeOnClick: true, children: /*#__PURE__*/_jsx(ShowMoreTabs, { children: showMoreTabsList.map(tab => /*#__PURE__*/_jsx(ActionsMenuItem, { className: tab.className, id: tab.id, "data-testid": tab["data-testid"], Component: Tab, fullWidth: true, to: tab.path, children: tab.label }, tab.path)) }) })] }) }) }); }; TabsAlt.propTypes = { /** isVisible defaults to true if not passed */ tabsList: PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string.isRequired, path: PropTypes.string.isRequired, isVisible: PropTypes.bool })).isRequired }; TabsAlt.__docgenInfo = { "description": "", "methods": [], "displayName": "TabsAlt", "props": { "tabsList": { "description": "isVisible defaults to true if not passed", "type": { "name": "arrayOf", "value": { "name": "shape", "value": { "label": { "name": "string", "required": true }, "path": { "name": "string", "required": true }, "isVisible": { "name": "bool", "required": false } } } }, "required": true } } }; export default TabsAlt;