UNPKG

orcs-design-system

Version:
313 lines (301 loc) 14 kB
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;