@primer/react
Version:
An implementation of GitHub's Primer Design System using React
189 lines (180 loc) • 8.69 kB
JavaScript
'use strict';
var React = require('react');
var tabContainerElement = require('../../node_modules/@github/tab-container-element/dist/tab-container-element.js');
require('../../node_modules/@github/tab-container-element/dist/tab-container-element-define.js');
var createComponent = require('../../utils/create-component.js');
var UnderlineTabbedInterface = require('../../internal/components/UnderlineTabbedInterface.js');
var Box = require('../../Box/Box.js');
require('@primer/behaviors/utils');
require('@primer/behaviors');
var useResizeObserver = require('../../hooks/useResizeObserver.js');
var useIsomorphicLayoutEffect = require('../../utils/useIsomorphicLayoutEffect.js');
var useId = require('../../hooks/useId.js');
var invariant = require('../../utils/invariant.js');
require('@styled-system/css');
var merge = require('deepmerge');
var defaultSxProp = require('../../utils/defaultSxProp.js');
require('../../FeatureFlags/FeatureFlags.js');
var useFeatureFlag = require('../../FeatureFlags/useFeatureFlag.js');
require('../../FeatureFlags/DefaultFeatureFlags.js');
var UnderlinePanels_module = require('./UnderlinePanels.module.css.js');
var toggleStyledComponent = require('../../internal/utils/toggleStyledComponent.js');
var clsx = require('clsx');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var React__default = /*#__PURE__*/_interopDefault(React);
var merge__default = /*#__PURE__*/_interopDefault(merge);
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_staff';
const TabContainerComponent = createComponent.createComponent(tabContainerElement.TabContainerElement, 'tab-container');
const StyledTabContainerComponent = toggleStyledComponent.toggleStyledComponent(CSS_MODULES_FEATURE_FLAG, 'tab-container', TabContainerComponent);
const UnderlinePanels = ({
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
children,
loadingCounters,
sx: sxProp = defaultSxProp.defaultSxProp,
className,
...props
}) => {
const [iconsVisible, setIconsVisible] = React.useState(true);
const wrapperRef = React.useRef(null);
const listRef = React.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.useId(props.id);
const [tabs, setTabs] = React.useState([]);
const [tabPanels, setTabPanels] = React.useState([]);
// Make sure we have fresh prop data whenever the tabs or panels are updated (keep aria-selected current)
React.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 = React.Children.map(children, child => {
if (/*#__PURE__*/React.isValidElement(child) && child.type === Tab) {
return /*#__PURE__*/React.cloneElement(child, {
id: `${parentId}-tab-${tabIndex++}`,
loadingCounters,
iconsVisible
});
}
if (/*#__PURE__*/React.isValidElement(child) && child.type === Panel) {
return /*#__PURE__*/React.cloneElement(child, {
'aria-labelledby': `${parentId}-tab-${panelIndex++}`
});
}
return child;
});
const newTabs = React.Children.toArray(childrenWithProps).filter(child => {
return /*#__PURE__*/React.isValidElement(child) && child.type === Tab;
});
const newTabPanels = React.Children.toArray(childrenWithProps).filter(child => /*#__PURE__*/React.isValidElement(child) && child.type === Panel);
setTabs(newTabs);
setTabPanels(newTabPanels);
}, [children, parentId, loadingCounters, iconsVisible]);
const tabsHaveIcons = tabs.some(tab => /*#__PURE__*/React__default.default.isValidElement(tab) && tab.props.icon);
const enabled = useFeatureFlag.useFeatureFlag(CSS_MODULES_FEATURE_FLAG);
// this is a workaround to get the list's width on the first render
const [listWidth, setListWidth] = React.useState(0);
useIsomorphicLayoutEffect(() => {
var _listRef$current$getB, _listRef$current;
if (!tabsHaveIcons) {
return;
}
setListWidth((_listRef$current$getB = (_listRef$current = listRef.current) === null || _listRef$current === undefined ? undefined : _listRef$current.getBoundingClientRect().width) !== null && _listRef$current$getB !== undefined ? _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.useResizeObserver(resizeObserverEntries => {
if (!tabsHaveIcons) {
return;
}
const wrapperWidth = resizeObserverEntries[0].contentRect.width;
setIconsVisible(wrapperWidth > listWidth);
}, wrapperRef, [enabled]);
if (process.env.NODE_ENV !== "production") {
const selectedTabs = tabs.filter(tab => {
const ariaSelected = /*#__PURE__*/React__default.default.isValidElement(tab) && tab.props['aria-selected'];
return ariaSelected === true || ariaSelected === 'true';
});
!(selectedTabs.length <= 1) ? process.env.NODE_ENV !== "production" ? invariant.invariant(false, 'Only one tab can be selected at a time.') : invariant.invariant(false) : undefined;
!(tabs.length === tabPanels.length) ? process.env.NODE_ENV !== "production" ? invariant.invariant(false, `The number of tabs and panels must be equal. Counted ${tabs.length} tabs and ${tabPanels.length} panels.`) : invariant.invariant(false) : undefined;
}
if (enabled) {
return /*#__PURE__*/React__default.default.createElement(StyledTabContainerComponent, null, /*#__PURE__*/React__default.default.createElement(UnderlineTabbedInterface.StyledUnderlineWrapper, _extends({
ref: wrapperRef,
slot: "tablist-wrapper",
"data-icons-visible": iconsVisible,
sx: sxProp,
className: clsx.clsx(className, UnderlinePanels_module.StyledUnderlineWrapper)
}, props), /*#__PURE__*/React__default.default.createElement(UnderlineTabbedInterface.StyledUnderlineItemList, {
ref: listRef,
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledBy,
role: "tablist"
}, tabs)), tabPanels);
}
return /*#__PURE__*/React__default.default.createElement(StyledTabContainerComponent, null, /*#__PURE__*/React__default.default.createElement(UnderlineTabbedInterface.StyledUnderlineWrapper, _extends({
ref: wrapperRef,
slot: "tablist-wrapper",
"data-icons-visible": iconsVisible,
sx: merge__default.default({
width: '100%',
overflowX: 'auto',
overflowY: 'hidden',
'-webkit-overflow-scrolling': 'auto',
'&[data-icons-visible="false"] [data-component="icon"]': {
display: 'none'
}
}, sxProp),
className: className
}, props), /*#__PURE__*/React__default.default.createElement(UnderlineTabbedInterface.StyledUnderlineItemList, {
ref: listRef,
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledBy,
role: "tablist"
}, tabs)), tabPanels);
};
UnderlinePanels.displayName = "UnderlinePanels";
const Tab = ({
'aria-selected': ariaSelected,
sx: sxProp = defaultSxProp.defaultSxProp,
onSelect,
...props
}) => {
const clickHandler = React__default.default.useCallback(event => {
if (!event.defaultPrevented && typeof onSelect === 'function') {
onSelect(event);
}
}, [onSelect]);
const keyDownHandler = React__default.default.useCallback(event => {
if ((event.key === ' ' || event.key === 'Enter') && !event.defaultPrevented && typeof onSelect === 'function') {
onSelect(event);
}
}, [onSelect]);
return /*#__PURE__*/React__default.default.createElement(UnderlineTabbedInterface.UnderlineItem, _extends({
as: "button",
role: "tab",
tabIndex: ariaSelected ? 0 : -1,
"aria-selected": ariaSelected,
sx: sxProp,
type: "button",
onClick: clickHandler,
onKeyDown: keyDownHandler
}, props));
};
Tab.displayName = "Tab";
Tab.displayName = 'UnderlinePanels.Tab';
const Panel = props => {
return /*#__PURE__*/React__default.default.createElement(Box, _extends({
as: "div",
role: "tabpanel"
}, props));
};
Panel.displayName = "Panel";
Panel.displayName = 'UnderlinePanels.Panel';
var UnderlinePanels$1 = Object.assign(UnderlinePanels, {
Panel,
Tab
});
module.exports = UnderlinePanels$1;