orcs-design-system
Version:
TeamForm's Design System, aka: ORCS
211 lines (206 loc) • 9.34 kB
JavaScript
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;