UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

655 lines (627 loc) • 16.6 kB
import { c } from 'react-compiler-runtime'; import { PlusIcon, ChevronDownIcon } from '@primer/octicons-react'; import React, { createElement, isValidElement } from 'react'; import { clsx } from 'clsx'; import { ActionList } from '../ActionList/index.js'; import { SubItem } from '../ActionList/Item.js'; import { ActionListContainerContext } from '../ActionList/ActionListContainerContext.js'; import { useId } from '../hooks/useId.js'; import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect.js'; import classes from '../ActionList/ActionList.module.css.js'; import navListClasses from './NavList.module.css.js'; import { flushSync } from 'react-dom'; import { isSlot } from '../utils/is-slot.js'; import { fixedForwardRef } from '../utils/modern-polymorphic.js'; import { jsx, Fragment, jsxs } from 'react/jsx-runtime'; const Root = /*#__PURE__*/React.forwardRef((t0, ref) => { const $ = c(10); let children; let props; if ($[0] !== t0) { ({ children, ...props } = t0); $[0] = t0; $[1] = children; $[2] = props; } else { children = $[1]; props = $[2]; } let t1; if ($[3] === Symbol.for("react.memo_cache_sentinel")) { t1 = { container: "NavList" }; $[3] = t1; } else { t1 = $[3]; } let t2; if ($[4] !== children) { t2 = /*#__PURE__*/jsx(ActionListContainerContext.Provider, { value: t1, children: /*#__PURE__*/jsx(ActionList, { children: children }) }); $[4] = children; $[5] = t2; } else { t2 = $[5]; } let t3; if ($[6] !== props || $[7] !== ref || $[8] !== t2) { t3 = /*#__PURE__*/jsx("nav", { ...props, ref: ref, children: t2 }); $[6] = props; $[7] = ref; $[8] = t2; $[9] = t3; } else { t3 = $[9]; } return t3; }); Root.displayName = 'NavList'; // ---------------------------------------------------------------------------- // NavList.Item const ItemComponent = fixedForwardRef(({ 'aria-current': ariaCurrent, children, defaultOpen, as: Component, ...props }, ref) => { const { depth } = React.useContext(SubNavContext); // Get SubNav from children const subNav = React.Children.toArray(children).find(child => /*#__PURE__*/isValidElement(child) && (child.type === SubNav || isSlot(child, SubNav))); // Get children without SubNav or TrailingAction const childrenWithoutSubNavOrTrailingAction = React.Children.toArray(children).filter(child => /*#__PURE__*/isValidElement(child) ? child.type !== SubNav && child.type !== TrailingAction && !isSlot(child, SubNav) && !isSlot(child, TrailingAction) : true); if (! /*#__PURE__*/isValidElement(subNav) && defaultOpen) // eslint-disable-next-line no-console console.error('NavList.Item must have a NavList.SubNav to use defaultOpen.'); // Render ItemWithSubNav if SubNav is present if (subNav && /*#__PURE__*/isValidElement(subNav)) { return /*#__PURE__*/jsx(ItemWithSubNav, { subNav: subNav, depth: depth, defaultOpen: defaultOpen, style: { '--subitem-depth': depth }, children: childrenWithoutSubNavOrTrailingAction }); } // Type safety for the polymorphic `as` prop is enforced at the // Item boundary via fixedForwardRef. Internally we widen // LinkItem's type so TypeScript doesn't re-check the generic // constraint across two polymorphic layers. const InternalLinkItem = ActionList.LinkItem; return /*#__PURE__*/jsx(InternalLinkItem, { ref: ref, as: Component, "aria-current": ariaCurrent, active: Boolean(ariaCurrent) && ariaCurrent !== 'false', style: { '--subitem-depth': depth }, ...props, children: children }); }); const Item = Object.assign(ItemComponent, { displayName: 'NavList.Item' }); // ---------------------------------------------------------------------------- // ItemWithSubNav (internal) const ItemWithSubNavContext = /*#__PURE__*/React.createContext({ buttonId: '', subNavId: '', isOpen: false }); function hasCurrentNavItem(node) { if (! /*#__PURE__*/isValidElement(node)) { return false; } const ariaCurrent = node.props['aria-current']; if (Boolean(ariaCurrent) && ariaCurrent !== 'false') { return true; } if (!node.props.children) { return false; } return React.Children.toArray(node.props.children).some(hasCurrentNavItem); } function ItemWithSubNav(t0) { var _ref; const $ = c(29); const { children, subNav, defaultOpen, style } = t0; const buttonId = useId(); const subNavId = useId(); let t1; if ($[0] !== subNav) { t1 = hasCurrentNavItem(subNav); $[0] = subNav; $[1] = t1; } else { t1 = $[1]; } const hasCurrentItem = t1; const [isOpen, setIsOpen] = React.useState((_ref = defaultOpen || null) !== null && _ref !== void 0 ? _ref : hasCurrentItem); const subNavRef = React.useRef(null); const [containsCurrentItem, setContainsCurrentItem] = React.useState(hasCurrentItem); let t2; if ($[2] !== subNav) { t2 = () => { var _subNavRef$current; const currentItem = hasCurrentNavItem(subNav) || Boolean((_subNavRef$current = subNavRef.current) === null || _subNavRef$current === void 0 ? void 0 : _subNavRef$current.querySelector("[aria-current]:not([aria-current=false])")); setContainsCurrentItem(currentItem); if (currentItem) { setIsOpen(true); } }; $[2] = subNav; $[3] = t2; } else { t2 = $[3]; } let t3; if ($[4] !== buttonId || $[5] !== subNav) { t3 = [subNav, buttonId]; $[4] = buttonId; $[5] = subNav; $[6] = t3; } else { t3 = $[6]; } useIsomorphicLayoutEffect(t2, t3); let t4; if ($[7] !== buttonId || $[8] !== isOpen || $[9] !== subNavId) { t4 = { buttonId, subNavId, isOpen }; $[7] = buttonId; $[8] = isOpen; $[9] = subNavId; $[10] = t4; } else { t4 = $[10]; } const t5 = !isOpen && containsCurrentItem; let t6; if ($[11] === Symbol.for("react.memo_cache_sentinel")) { t6 = () => setIsOpen(_temp); $[11] = t6; } else { t6 = $[11]; } let t7; if ($[12] === Symbol.for("react.memo_cache_sentinel")) { t7 = /*#__PURE__*/jsx(ActionList.TrailingVisual, { children: /*#__PURE__*/jsx(ChevronDownIcon, { className: classes.ExpandIcon }) }); $[12] = t7; } else { t7 = $[12]; } let t8; if ($[13] !== subNav) { let t9; if ($[15] === Symbol.for("react.memo_cache_sentinel")) { t9 = { ref: subNavRef }; $[15] = t9; } else { t9 = $[15]; } t8 = /*#__PURE__*/React.cloneElement(subNav, t9); $[13] = subNav; $[14] = t8; } else { t8 = $[14]; } let t9; if ($[16] !== t8) { t9 = /*#__PURE__*/jsx(SubItem, { children: t8 }); $[16] = t8; $[17] = t9; } else { t9 = $[17]; } let t10; if ($[18] !== buttonId || $[19] !== children || $[20] !== isOpen || $[21] !== style || $[22] !== subNavId || $[23] !== t5 || $[24] !== t9) { t10 = /*#__PURE__*/jsxs(ActionList.Item, { id: buttonId, "aria-expanded": isOpen, "aria-controls": subNavId, active: t5, onSelect: t6, style: style, children: [children, t7, t9] }); $[18] = buttonId; $[19] = children; $[20] = isOpen; $[21] = style; $[22] = subNavId; $[23] = t5; $[24] = t9; $[25] = t10; } else { t10 = $[25]; } let t11; if ($[26] !== t10 || $[27] !== t4) { t11 = /*#__PURE__*/jsx(ItemWithSubNavContext.Provider, { value: t4, children: t10 }); $[26] = t10; $[27] = t4; $[28] = t11; } else { t11 = $[28]; } return t11; } // ---------------------------------------------------------------------------- // NavList.SubNav function _temp(open) { return !open; } const SubNavContext = /*#__PURE__*/React.createContext({ depth: 0 }); // NOTE: SubNav must be a direct child of an Item const SubNav = /*#__PURE__*/React.forwardRef((t0, forwardedRef) => { const $ = c(10); const { children } = t0; const { buttonId, subNavId } = React.useContext(ItemWithSubNavContext); const { depth } = React.useContext(SubNavContext); if (!buttonId || !subNavId) { console.error("NavList.SubNav must be a child of a NavList.Item"); } if (depth > 3) { console.error("NavList.SubNav only supports four levels of nesting"); return null; } const t1 = depth + 1; let t2; if ($[0] !== t1) { t2 = { depth: t1 }; $[0] = t1; $[1] = t2; } else { t2 = $[1]; } let t3; if ($[2] !== buttonId || $[3] !== children || $[4] !== forwardedRef || $[5] !== subNavId) { t3 = /*#__PURE__*/jsx("ul", { className: classes.SubGroup, id: subNavId, "aria-labelledby": buttonId, ref: forwardedRef, children: children }); $[2] = buttonId; $[3] = children; $[4] = forwardedRef; $[5] = subNavId; $[6] = t3; } else { t3 = $[6]; } let t4; if ($[7] !== t2 || $[8] !== t3) { t4 = /*#__PURE__*/jsx(SubNavContext.Provider, { value: t2, children: t3 }); $[7] = t2; $[8] = t3; $[9] = t4; } else { t4 = $[9]; } return t4; }); SubNav.displayName = 'NavList.SubNav'; // ---------------------------------------------------------------------------- // NavList.LeadingVisual const LeadingVisual = ActionList.LeadingVisual; LeadingVisual.displayName = 'NavList.LeadingVisual'; // ---------------------------------------------------------------------------- // NavList.TrailingVisual const TrailingVisual = ActionList.TrailingVisual; TrailingVisual.displayName = 'NavList.TrailingVisual'; // ---------------------------------------------------------------------------- // NavList.Divider const Divider = ActionList.Divider; Divider.displayName = 'NavList.Divider'; // NavList.TrailingAction const TrailingAction = ActionList.TrailingAction; TrailingAction.displayName = 'NavList.TrailingAction'; // ---------------------------------------------------------------------------- // NavList.Group const Group = t0 => { const $ = c(11); let children; let props; let title; if ($[0] !== t0) { ({ title, children, ...props } = t0); $[0] = t0; $[1] = children; $[2] = props; $[3] = title; } else { children = $[1]; props = $[2]; title = $[3]; } let t1; if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = /*#__PURE__*/jsx(ActionList.Divider, {}); $[4] = t1; } else { t1 = $[4]; } let t2; if ($[5] !== title) { t2 = title ? /*#__PURE__*/jsx(ActionList.GroupHeading, { as: "h3", "data-component": "ActionList.GroupHeading", children: title }) : null; $[5] = title; $[6] = t2; } else { t2 = $[6]; } let t3; if ($[7] !== children || $[8] !== props || $[9] !== t2) { t3 = /*#__PURE__*/jsxs(Fragment, { children: [t1, /*#__PURE__*/jsxs(ActionList.Group, { ...props, children: [t2, children] })] }); $[7] = children; $[8] = props; $[9] = t2; $[10] = t3; } else { t3 = $[10]; } return t3; }; // ---------------------------------------------------------------------------- // NavList.GroupExpand const GroupExpand = /*#__PURE__*/React.forwardRef((t0, forwardedRef) => { const $ = c(23); let items; let props; let renderItem; let t1; let t2; if ($[0] !== t0) { ({ label: t1, pages: t2, items, renderItem, ...props } = t0); $[0] = t0; $[1] = items; $[2] = props; $[3] = renderItem; $[4] = t1; $[5] = t2; } else { items = $[1]; props = $[2]; renderItem = $[3]; t1 = $[4]; t2 = $[5]; } const label = t1 === undefined ? "Show more" : t1; const pages = t2 === undefined ? 0 : t2; const [currentPage, setCurrentPage] = React.useState(0); const groupId = useId(); const itemsPerPage = items.length / pages; const amountToShow = pages === 0 ? items.length : Math.ceil(itemsPerPage * currentPage); const focusTargetIndex = currentPage === 1 ? 0 : amountToShow - Math.floor(itemsPerPage); let t3; if ($[6] !== amountToShow || $[7] !== currentPage || $[8] !== focusTargetIndex || $[9] !== groupId || $[10] !== items || $[11] !== renderItem) { t3 = currentPage > 0 ? /*#__PURE__*/jsx(Fragment, { children: items.map((itemArr, index) => { const { text, trailingVisual: TrailingVisualIcon, leadingVisual: LeadingVisualIcon, trailingAction, ...rest } = itemArr; const { icon, label: actionLabel, ...actionProps } = trailingAction || {}; const focusTarget = index === focusTargetIndex ? groupId : undefined; if (index < amountToShow) { if (renderItem) { return renderItem({ ...itemArr, "data-expand-focus-target": focusTarget }); } return /*#__PURE__*/createElement(Item, { ...rest, key: index, "data-expand-focus-target": focusTarget }, LeadingVisualIcon ? /*#__PURE__*/jsx(LeadingVisual, { children: /*#__PURE__*/jsx(LeadingVisualIcon, {}) }) : null, text, TrailingVisualIcon ? /*#__PURE__*/jsx(TrailingVisual, { children: /*#__PURE__*/jsx(TrailingVisualIcon, {}) }) : null, trailingAction ? /*#__PURE__*/jsx(TrailingAction, { ...actionProps, icon: icon, label: actionLabel || "" }) : null); } }) }) : null; $[6] = amountToShow; $[7] = currentPage; $[8] = focusTargetIndex; $[9] = groupId; $[10] = items; $[11] = renderItem; $[12] = t3; } else { t3 = $[12]; } let t4; if ($[13] !== currentPage || $[14] !== forwardedRef || $[15] !== groupId || $[16] !== label || $[17] !== pages || $[18] !== props) { t4 = currentPage < pages || currentPage === 0 ? /*#__PURE__*/jsxs(ActionList.Item, { as: "button", "aria-expanded": "false", ref: forwardedRef, onSelect: () => { flushSync(() => { setCurrentPage(currentPage + 1); }); const focusTarget_0 = Array.from(document.querySelectorAll(`[data-expand-focus-target="${groupId}"]`)); if (focusTarget_0.length > 0) { focusTarget_0[focusTarget_0.length - 1].focus(); } }, ...props, children: [label, /*#__PURE__*/jsx(TrailingVisual, { children: /*#__PURE__*/jsx(PlusIcon, {}) })] }) : null; $[13] = currentPage; $[14] = forwardedRef; $[15] = groupId; $[16] = label; $[17] = pages; $[18] = props; $[19] = t4; } else { t4 = $[19]; } let t5; if ($[20] !== t3 || $[21] !== t4) { t5 = /*#__PURE__*/jsxs(Fragment, { children: [t3, t4] }); $[20] = t3; $[21] = t4; $[22] = t5; } else { t5 = $[22]; } return t5; }); // ---------------------------------------------------------------------------- // NavList.GroupHeading /** * This is an alternative to the `title` prop on `NavList.Group`. * It was primarily added to allow links in group headings. */ const GroupHeading = t0 => { const $ = c(10); let className; let rest; let t1; if ($[0] !== t0) { ({ as: t1, className, ...rest } = t0); $[0] = t0; $[1] = className; $[2] = rest; $[3] = t1; } else { className = $[1]; rest = $[2]; t1 = $[3]; } const as = t1 === undefined ? "h3" : t1; let t2; if ($[4] !== className) { t2 = clsx(navListClasses.GroupHeading, className); $[4] = className; $[5] = t2; } else { t2 = $[5]; } let t3; if ($[6] !== as || $[7] !== rest || $[8] !== t2) { t3 = /*#__PURE__*/jsx(ActionList.GroupHeading, { as: as, className: t2, "data-component": "NavList.GroupHeading", headingWrapElement: "li", ...rest }); $[6] = as; $[7] = rest; $[8] = t2; $[9] = t3; } else { t3 = $[9]; } return t3; }; // ---------------------------------------------------------------------------- // Export const NavList = Object.assign(Root, { Description: ActionList.Description, Item, SubNav, LeadingVisual, TrailingVisual, TrailingAction, Divider, Group, GroupExpand, GroupHeading }); export { GroupExpand, NavList };