UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

221 lines (215 loc) • 7.48 kB
import { c } from 'react-compiler-runtime'; import React, { useState, useRef, useEffect, Children, isValidElement, cloneElement } from 'react'; import { TabContainerElement } from '@github/tab-container-element'; import { createComponent } from '../../utils/create-component.js'; import { UnderlineWrapper, UnderlineItemList, UnderlineItem } from '../../internal/components/UnderlineTabbedInterface.js'; import { invariant } from '../../utils/invariant.js'; import { useResizeObserver } from '../../hooks/useResizeObserver.js'; import useIsomorphicLayoutEffect from '../../utils/useIsomorphicLayoutEffect.js'; import classes from './UnderlinePanels.module.css.js'; import { clsx } from 'clsx'; import { isSlot } from '../../utils/is-slot.js'; import { jsxs, jsx } from 'react/jsx-runtime'; import { useId } from '../../hooks/useId.js'; const TabContainerComponent = createComponent(TabContainerElement, 'tab-container'); const UnderlinePanels = ({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, children, loadingCounters, className, ...props }) => { const [iconsVisible, setIconsVisible] = useState(true); const wrapperRef = useRef(null); const listRef = useRef(null); // We need to always call useId() because React Hooks must be // called in the exact same order in every component render const parentId = useId(props.id); const [tabs, setTabs] = useState([]); const [tabPanels, setTabPanels] = useState([]); // Make sure we have fresh prop data whenever the tabs or panels are updated (keep aria-selected current) useEffect(() => { // Loop through the chidren, if it's a tab, then add id="{id}-tab-{index}" // If it's a panel, then add aria-labelledby="{id}-tab-{index}" let tabIndex = 0; let panelIndex = 0; const childrenWithProps = Children.map(children, child => { if (/*#__PURE__*/isValidElement(child) && (child.type === Tab || isSlot(child, Tab))) { return /*#__PURE__*/cloneElement(child, { id: `${parentId}-tab-${tabIndex++}`, loadingCounters, iconsVisible }); } if (/*#__PURE__*/isValidElement(child) && (child.type === Panel || isSlot(child, Panel))) { const childPanel = child; return /*#__PURE__*/cloneElement(childPanel, { 'aria-labelledby': `${parentId}-tab-${panelIndex++}` }); } return child; }); const newTabs = Children.toArray(childrenWithProps).filter(child_0 => { return /*#__PURE__*/isValidElement(child_0) && (child_0.type === Tab || isSlot(child_0, Tab)); }); const newTabPanels = Children.toArray(childrenWithProps).filter(child_1 => /*#__PURE__*/isValidElement(child_1) && (child_1.type === Panel || isSlot(child_1, Panel))); setTabs(newTabs); setTabPanels(newTabPanels); }, [children, parentId, loadingCounters, iconsVisible]); const tabsHaveIcons = tabs.some(tab => /*#__PURE__*/React.isValidElement(tab) && tab.props.icon); // this is a workaround to get the list's width on the first render const [listWidth, setListWidth] = useState(0); useIsomorphicLayoutEffect(() => { var _listRef$current$getB, _listRef$current; if (!tabsHaveIcons) { return; } setListWidth((_listRef$current$getB = (_listRef$current = listRef.current) === null || _listRef$current === void 0 ? void 0 : _listRef$current.getBoundingClientRect().width) !== null && _listRef$current$getB !== void 0 ? _listRef$current$getB : 0); }, [tabsHaveIcons]); // when the wrapper resizes, check if the icons should be visible // by comparing the wrapper width to the list width useResizeObserver(resizeObserverEntries => { if (!tabsHaveIcons) { return; } const wrapperWidth = resizeObserverEntries[0].contentRect.width; setIconsVisible(wrapperWidth > listWidth); }, wrapperRef, []); if (process.env.NODE_ENV !== "production") { const selectedTabs = tabs.filter(tab_0 => { const ariaSelected = /*#__PURE__*/React.isValidElement(tab_0) && tab_0.props['aria-selected']; return ariaSelected === true || ariaSelected === 'true'; }); !(selectedTabs.length <= 1) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Only one tab can be selected at a time.') : invariant(false) : void 0; !(tabs.length === tabPanels.length) ? process.env.NODE_ENV !== "production" ? invariant(false, `The number of tabs and panels must be equal. Counted ${tabs.length} tabs and ${tabPanels.length} panels.`) : invariant(false) : void 0; } return /*#__PURE__*/jsxs(TabContainerComponent, { children: [/*#__PURE__*/jsx(UnderlineWrapper, { ref: wrapperRef, slot: "tablist-wrapper", "data-icons-visible": iconsVisible, className: clsx(className, classes.StyledUnderlineWrapper), ...props, children: /*#__PURE__*/jsx(UnderlineItemList, { ref: listRef, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, role: "tablist", children: tabs }) }), tabPanels] }); }; UnderlinePanels.displayName = "UnderlinePanels"; const Tab = t0 => { const $ = c(14); let ariaSelected; let onSelect; let props; if ($[0] !== t0) { ({ "aria-selected": ariaSelected, onSelect, ...props } = t0); $[0] = t0; $[1] = ariaSelected; $[2] = onSelect; $[3] = props; } else { ariaSelected = $[1]; onSelect = $[2]; props = $[3]; } let t1; if ($[4] !== onSelect) { t1 = event => { if (!event.defaultPrevented && typeof onSelect === "function") { onSelect(event); } }; $[4] = onSelect; $[5] = t1; } else { t1 = $[5]; } const clickHandler = t1; let t2; if ($[6] !== onSelect) { t2 = event_0 => { if ((event_0.key === " " || event_0.key === "Enter") && !event_0.defaultPrevented && typeof onSelect === "function") { onSelect(event_0); } }; $[6] = onSelect; $[7] = t2; } else { t2 = $[7]; } const keyDownHandler = t2; const t3 = ariaSelected ? 0 : -1; let t4; if ($[8] !== ariaSelected || $[9] !== clickHandler || $[10] !== keyDownHandler || $[11] !== props || $[12] !== t3) { t4 = /*#__PURE__*/jsx(UnderlineItem, { as: "button", role: "tab", tabIndex: t3, "aria-selected": ariaSelected, type: "button", onClick: clickHandler, onKeyDown: keyDownHandler, ...props }); $[8] = ariaSelected; $[9] = clickHandler; $[10] = keyDownHandler; $[11] = props; $[12] = t3; $[13] = t4; } else { t4 = $[13]; } return t4; }; Tab.displayName = 'UnderlinePanels.Tab'; const Panel = t0 => { const $ = c(6); let children; let rest; if ($[0] !== t0) { ({ children, ...rest } = t0); $[0] = t0; $[1] = children; $[2] = rest; } else { children = $[1]; rest = $[2]; } let t1; if ($[3] !== children || $[4] !== rest) { t1 = /*#__PURE__*/jsx("div", { role: "tabpanel", ...rest, children: children }); $[3] = children; $[4] = rest; $[5] = t1; } else { t1 = $[5]; } return t1; }; Panel.displayName = 'UnderlinePanels.Panel'; var UnderlinePanels_default = Object.assign(UnderlinePanels, { Panel, Tab }); UnderlinePanels.__SLOT__ = Symbol('UnderlinePanels'); Tab.__SLOT__ = Symbol('UnderlinePanels.Tab'); Panel.__SLOT__ = Symbol('UnderlinePanels.Panel'); export { UnderlinePanels_default as default };