orcs-design-system
Version:
TeamForm's Design System, aka: ORCS
313 lines (301 loc) • 14 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import React, { useState, useRef, useEffect, useCallback, createElement as _createElement } 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 = /*#__PURE__*/styled.div.withConfig({
displayName: "TabsContainer",
componentId: "sc-15tpvnt-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 = /*#__PURE__*/styled.div.withConfig({
displayName: "TabWrapper",
componentId: "sc-15tpvnt-1"
})(["position:relative;width:100%;z-index:1;height:inherit;"]);
const VisibleTabs = /*#__PURE__*/styled.div.withConfig({
displayName: "VisibleTabs",
componentId: "sc-15tpvnt-2"
})(["flex-shrink:1;display:flex;align-items:center;justify-content:flex-start;overflow:hidden;height:inherit;"]);
const activeTabStyle = /*#__PURE__*/css(["background-color:", ";color:", ";border-bottom:2px solid ", ";cursor:default;font-weight:", ";&:hover,&:focus{background-color:", ";color:", ";border-bottom:2px solid ", ";box-shadow:none;}"], themeGet("colors.white"), themeGet("colors.primary"), themeGet("colors.primary"), themeGet("fontWeights.3"), themeGet("colors.white"), themeGet("colors.primary"), themeGet("colors.primary"));
const TabStyles = /*#__PURE__*/css(["width:", ";display:block;transition:background 200ms ease-in-out,color 200ms ease-in-out,border-bottom 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:not(.active){background-color:", ";color:", ";border-bottom:2px solid ", ";box-shadow:none;}&: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.primaryLight"), themeGet("colors.primaryLightest"), activeTabStyle);
const NavTab = /*#__PURE__*/styled(NavLink).withConfig({
displayName: "NavTab",
componentId: "sc-15tpvnt-3"
})(["", ""], TabStyles);
const Tab = /*#__PURE__*/styled.div.withConfig({
displayName: "Tab",
componentId: "sc-15tpvnt-4"
})(["", ""], TabStyles);
const ShowMoreButton = /*#__PURE__*/styled.button.withConfig({
displayName: "ShowMoreButton",
componentId: "sc-15tpvnt-5"
})(["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:", ";border-bottom:2px solid ", ";box-shadow:none;}&: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.primaryLight"), themeGet("colors.primaryLightest"), activeTabStyle);
const ShowMoreTabs = /*#__PURE__*/styled.div.withConfig({
displayName: "ShowMoreTabs",
componentId: "sc-15tpvnt-6"
})(["border-radius:", ";background-color:", ";width:fit-content;overflow:hidden;display:flex;flex-direction:column;justify-content:flex-start;box-shadow:", ";& [class^=\"Tabs__Tab\"]{border:0;padding:", ";text-align:left;}"], themeGet("radii.2"), themeGet("colors.greyLighter"), themeGet("shadows.boxDefault"), themeGet("space.3"));
const tabsGap = 0;
// Helper: Check if element is a tab (not ActionsMenu wrapper)
const isTabElement = el => {
const hasTabRole = el.getAttribute("role") === "tab";
return (el.tagName === "A" || el.tagName === "DIV") && hasTabRole;
};
// Helper: Generate consistent tab key
const getTabKey = tab => tab.id || tab.path || tab.label;
// Helper: Get shared props for tab element
const getTabSharedProps = function (tab) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
tabInShowMore = false,
fullWidth = false
} = options;
return _objectSpread(_objectSpread(_objectSpread({
className: tab.className,
id: tab.id,
"data-testid": tab["data-testid"]
}, tabInShowMore && {
$tabInShowMore: true
}), fullWidth && {
fullWidth: true
}), {}, {
role: "tab"
});
};
// Helper: Validate tab configuration
const validateTab = tab => {
if (!tab.onClick && !tab.path) {
console.warn("Tabs: Tab \"".concat(tab.label, "\" requires either an onClick handler or a path for routing."));
return false;
}
return true;
};
// Helper: Check if routing tab is active
const isTabActive = (tab, location) => {
// Only check path for routing tabs (not onClick-only tabs)
if (tab.onClick || !tab.path) {
return false;
}
try {
return location.pathname.endsWith(tab.path);
} catch (_unused) {
// Fallback for environments without router context
return false;
}
};
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 => {
var _showMoreButton$offse;
const showMoreButton = document.getElementById("show-more-button");
const showMoreButtonWidth = (_showMoreButton$offse = showMoreButton === null || showMoreButton === void 0 ? void 0 : showMoreButton.offsetWidth) !== null && _showMoreButton$offse !== void 0 ? _showMoreButton$offse : 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(isTabElement).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(() => {
var _containerRef$current;
const actionElements = ((_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _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 => {
var _tab$isVisible;
return (_tab$isVisible = tab.isVisible) !== null && _tab$isVisible !== void 0 ? _tab$isVisible : true;
});
const showMoreTabsList = visibleTabsList.filter((_tab, i) => showMoreTabs.includes(i));
const location = useLocation();
const showMoreItemActive = showMoreTabsList.find(action => isTabActive(action, location));
// Render a single tab element (for visible tabs)
const renderVisibleTab = (tab, index) => {
if (!validateTab(tab)) {
return null;
}
const sharedProps = getTabSharedProps(tab, {
tabInShowMore: showMoreTabs.includes(index)
});
const tabKey = getTabKey(tab);
if (tab !== null && tab !== void 0 && tab.onClick) {
return /*#__PURE__*/_createElement(Tab, _objectSpread(_objectSpread({}, sharedProps), {}, {
onClick: tab.onClick,
key: tabKey
}), tab.label);
}
return /*#__PURE__*/_createElement(NavTab, _objectSpread(_objectSpread({}, sharedProps), {}, {
to: tab.path,
key: tabKey
}), tab.label);
};
// Render a tab for the "More" menu dropdown
const renderMenuTab = tab => {
if (!validateTab(tab)) {
return null;
}
const sharedProps = getTabSharedProps(tab, {
fullWidth: true
});
const tabKey = getTabKey(tab);
if (tab !== null && tab !== void 0 && tab.onClick) {
return /*#__PURE__*/_jsx(ActionsMenuItem, _objectSpread(_objectSpread({}, sharedProps), {}, {
Component: Tab,
onClick: tab.onClick,
children: tab.label
}), tabKey);
}
return /*#__PURE__*/_jsx(ActionsMenuItem, _objectSpread(_objectSpread({}, sharedProps), {}, {
Component: NavTab,
to: tab.path,
children: tab.label
}), tabKey);
};
return /*#__PURE__*/_jsx(TabsContainer, {
children: /*#__PURE__*/_jsx(TabWrapper, {
children: /*#__PURE__*/_jsxs(VisibleTabs, {
style: {
gap: tabsGap
},
ref: containerRef,
role: "tablist",
children: [visibleTabsList.map((tab, i) => renderVisibleTab(tab, i)), /*#__PURE__*/_jsx(ActionsMenu, {
direction: "bottom-end",
role: "presentation",
renderTrigger: props => /*#__PURE__*/_jsxs(ShowMoreButton, _objectSpread(_objectSpread({}, 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 => renderMenuTab(tab))
})
})]
})
})
});
};
Tabs.propTypes = {
/** isVisible defaults to true if not passed */
tabsList: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
path: PropTypes.string,
// Optional when onClick is provided
isVisible: PropTypes.bool,
onClick: PropTypes.func,
id: PropTypes.string
})).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": false
},
"isVisible": {
"name": "bool",
"required": false
},
"onClick": {
"name": "func",
"required": false
},
"id": {
"name": "string",
"required": false
}
}
}
},
"required": true
}
}
};
export default Tabs;