UNPKG

orcs-design-system

Version:
211 lines (206 loc) 9.34 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: "Tabs__TabsContainer", componentId: "sc-15tpvnt-0" })(["position:relative;"]); const TabWrapper = styled.div.withConfig({ displayName: "Tabs__TabWrapper", componentId: "sc-15tpvnt-1" })(["position:relative;"]); const VisibleTabs = styled.div.withConfig({ displayName: "Tabs__VisibleTabs", componentId: "sc-15tpvnt-2" })(["flex-shrink:1;display:flex;align-items:center;overflow:hidden;"]); const activeTabStyle = css(["background-color:", ";color:", ";cursor:default;&:hover{background-color:", ";color:", ";}&:focus{color:", ";}"], themeGet("colors.white"), themeGet("colors.primary"), themeGet("colors.white"), themeGet("colors.primary"), themeGet("colors.primary")); const Tab = styled(NavLink).withConfig({ displayName: "Tabs__Tab", componentId: "sc-15tpvnt-3" })(["width:", ";display:block;border-radius:", ";transition:background 200ms ease-in-out,color 200ms ease-in-out;padding:", " ", ";font-size:", ";font-weight:", ";position:relative;white-space:nowrap;text-decoration:none;text-align:center;text-transform:uppercase;background-color:", ";color:", ";cursor:pointer;", " &:hover{outline:0;background-color:", ";color:", ";}&:focus{outline:0;color:", ";box-shadow:inset ", " ", ";}&.active{", "}"], _ref => { let { fullWidth } = _ref; return fullWidth ? "100%" : "fit-content"; }, themeGet("radii.2"), themeGet("space.3"), themeGet("space.4"), themeGet("fontSizes.1"), themeGet("fontWeights.2"), themeGet("colors.greyLighter"), themeGet("colors.greyDarker"), _ref2 => { let { tabInShowMore } = _ref2; return tabInShowMore ? css(["position:absolute;visibility:hidden;"]) : ""; }, themeGet("colors.greyLight"), themeGet("colors.greyDarker"), themeGet("colors.greyDarker"), themeGet("shadows.thinOutline"), themeGet("colors.grey"), activeTabStyle); const ShowMoreButton = styled.button.withConfig({ displayName: "Tabs__ShowMoreButton", componentId: "sc-15tpvnt-4" })(["appearance:none;border:none;font-family:", ";font-size:", ";font-weight:", ";border-radius:", ";background-color:", ";transition:", ";padding:", " ", ";color:", ";display:", ";align-items:center;&:hover{background-color:", ";color:", ";outline:0;}&:focus{color:", ";outline:0;box-shadow:inset ", " ", ";}&.hasActive{", "}"], themeGet("fonts.main"), themeGet("fontSizes.1"), themeGet("fontWeights.2"), themeGet("radii.2"), themeGet("colors.greyLighter"), themeGet("transition.transitionDefault"), themeGet("space.3"), themeGet("space.4"), themeGet("colors.greyDarker"), _ref3 => { let { showMoreVisible } = _ref3; return showMoreVisible ? "flex" : "none"; }, themeGet("colors.greyLight"), themeGet("colors.greyDarker"), themeGet("colors.greyDarker"), themeGet("shadows.thinOutline"), themeGet("colors.grey"), activeTabStyle); const ShowMoreTabs = styled.div.withConfig({ displayName: "Tabs__ShowMoreTabs", componentId: "sc-15tpvnt-5" })(["border-radius:", ";background-color:", ";min-width:84px;display:flex;flex-direction:column;align-items:center;gap:6px;padding:6px;box-shadow:inset ", " ", ",", ";"], themeGet("radii.2"), themeGet("colors.white"), themeGet("shadows.thinOutline"), themeGet("colors.greyLighter"), themeGet("shadows.boxDefault")); const tabsGap = 6; const Tabs = _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: "s", 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)) }) })] }) }) }); }; Tabs.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 }; Tabs.__docgenInfo = { "description": "", "methods": [], "displayName": "Tabs", "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 Tabs;