UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

589 lines (588 loc) • 17.4 kB
import useIsomorphicLayoutEffect from "../utils/useIsomorphicLayoutEffect.js"; import { useId as useId$1 } from "../hooks/useId.js"; import { fixedForwardRef } from "../utils/modern-polymorphic.js"; import _VisuallyHidden_module_css_default from "../_VisuallyHidden.module.css.js"; import { useSlots } from "../hooks/useSlots.js"; import { ActionListContainerContext } from "../ActionList/ActionListContainerContext.js"; import Heading$1 from "../Heading/Heading.js"; import ActionList_module_css_default from "../ActionList/ActionList.module.css.js"; import { SubItem } from "../ActionList/Item.js"; import { ActionList } from "../ActionList/index.js"; import NavList_module_css_default from "./NavList.module.css.js"; import { c } from "react-compiler-runtime"; import { clsx } from "clsx"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import React, { createElement, isValidElement } from "react"; import { ChevronDownIcon, PlusIcon } from "@primer/octicons-react"; import { flushSync } from "react-dom"; //#region src/NavList/NavList.tsx const NavListHeadingLevelContext = /*#__PURE__*/ React.createContext(null); function headingTagToLevel(as) { return Number.parseInt(as.slice(1), 10); } function levelToHeadingTag(level) { return `h${Math.min(Math.max(level, 1), 4)}`; } const Root = /*#__PURE__*/ React.forwardRef((t0, ref) => { var _heading$props$id, _ariaLabelledby; const $ = c(24); let ariaLabel; let ariaLabelledby; let children; let props; if ($[0] !== t0) { ({children, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props} = t0); $[0] = t0; $[1] = ariaLabel; $[2] = ariaLabelledby; $[3] = children; $[4] = props; } else { ariaLabel = $[1]; ariaLabelledby = $[2]; children = $[3]; props = $[4]; } let t1; if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = { heading: Heading }; $[5] = t1; } else t1 = $[5]; const [slots, childrenWithoutHeading] = useSlots(children, t1); const fallbackHeadingId = useId$1(); const heading = slots.heading; const headingId = heading ? (_heading$props$id = heading.props.id) !== null && _heading$props$id !== void 0 ? _heading$props$id : fallbackHeadingId : void 0; let t2; if ($[6] !== heading) { var _heading$props$as; t2 = heading ? headingTagToLevel((_heading$props$as = heading.props.as) !== null && _heading$props$as !== void 0 ? _heading$props$as : "h2") : null; $[6] = heading; $[7] = t2; } else t2 = $[7]; const headingLevel = t2; const navLabelledby = (_ariaLabelledby = ariaLabelledby) !== null && _ariaLabelledby !== void 0 ? _ariaLabelledby : ariaLabel ? void 0 : headingId; let t3; if ($[8] !== heading || $[9] !== headingId) { t3 = heading ? /*#__PURE__*/ React.cloneElement(heading, { id: headingId }) : null; $[8] = heading; $[9] = headingId; $[10] = t3; } else t3 = $[10]; let t4; if ($[11] === Symbol.for("react.memo_cache_sentinel")) { t4 = { container: "NavList" }; $[11] = t4; } else t4 = $[11]; let t5; if ($[12] !== childrenWithoutHeading) { t5 = /*#__PURE__*/ jsx(ActionListContainerContext.Provider, { value: t4, children: /*#__PURE__*/ jsx(ActionList, { children: childrenWithoutHeading }) }); $[12] = childrenWithoutHeading; $[13] = t5; } else t5 = $[13]; let t6; if ($[14] !== headingLevel || $[15] !== t3 || $[16] !== t5) { t6 = /*#__PURE__*/ jsxs(NavListHeadingLevelContext.Provider, { value: headingLevel, children: [t3, t5] }); $[14] = headingLevel; $[15] = t3; $[16] = t5; $[17] = t6; } else t6 = $[17]; let t7; if ($[18] !== ariaLabel || $[19] !== navLabelledby || $[20] !== props || $[21] !== ref || $[22] !== t6) { t7 = /*#__PURE__*/ jsx("nav", { ...props, "aria-label": ariaLabel, "aria-labelledby": navLabelledby, ref, "data-component": "NavList", children: t6 }); $[18] = ariaLabel; $[19] = navLabelledby; $[20] = props; $[21] = ref; $[22] = t6; $[23] = t7; } else t7 = $[23]; return t7; }); Root.displayName = "NavList"; const Heading = (t0) => { const $ = c(14); let children; let className; let props; let t1; let t2; if ($[0] !== t0) { ({as: t1, visuallyHidden: t2, className, children, ...props} = t0); $[0] = t0; $[1] = children; $[2] = className; $[3] = props; $[4] = t1; $[5] = t2; } else { children = $[1]; className = $[2]; props = $[3]; t1 = $[4]; t2 = $[5]; } const as = t1 === void 0 ? "h2" : t1; const t3 = (t2 === void 0 ? false : t2) ? _VisuallyHidden_module_css_default.InternalVisuallyHidden : NavList_module_css_default.Heading; let t4; if ($[6] !== className || $[7] !== t3) { t4 = clsx(t3, className); $[6] = className; $[7] = t3; $[8] = t4; } else t4 = $[8]; let t5; if ($[9] !== as || $[10] !== children || $[11] !== props || $[12] !== t4) { t5 = /*#__PURE__*/ jsx(Heading$1, { as, variant: "small", className: t4, "data-component": "NavList.Heading", ...props, children }); $[9] = as; $[10] = children; $[11] = props; $[12] = t4; $[13] = t5; } else t5 = $[13]; return t5; }; Heading.displayName = "NavList.Heading"; Heading.__SLOT__ = Symbol("NavList.Heading"); const ItemComponent = fixedForwardRef(({ "aria-current": ariaCurrent, children, defaultOpen, as: Component, ...props }, ref) => { const { depth } = React.useContext(SubNavContext); const [slots, childrenWithoutSubNavOrTrailingAction] = useSlots(children, { subNav: SubNav, trailingAction: TrailingAction }); const subNav = slots.subNav; if (!/*#__PURE__*/ isValidElement(subNav) && defaultOpen) console.error("NavList.Item must have a NavList.SubNav to use defaultOpen."); if (subNav && /*#__PURE__*/ isValidElement(subNav)) return /*#__PURE__*/ jsx(ItemWithSubNav, { subNav, depth, defaultOpen, style: { "--subitem-depth": depth }, children: childrenWithoutSubNavOrTrailingAction }); const InternalLinkItem = ActionList.LinkItem; return /*#__PURE__*/ jsx(InternalLinkItem, { ref, as: Component, "aria-current": ariaCurrent, active: Boolean(ariaCurrent) && ariaCurrent !== "false", style: { "--subitem-depth": depth }, "data-component": "NavList.Item", ...props, children }); }); const Item = Object.assign(ItemComponent, { displayName: "NavList.Item" }); 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$1(); const subNavId = useId$1(); 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: ActionList_module_css_default.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, "data-component": "NavList.Item", 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; } function _temp(open) { return !open; } const SubNavContext = /*#__PURE__*/ React.createContext({ depth: 0 }); 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: ActionList_module_css_default.SubGroup, id: subNavId, "aria-labelledby": buttonId, ref: forwardedRef, "data-component": "NavList.SubNav", 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"; const LeadingVisual = ActionList.LeadingVisual; LeadingVisual.displayName = "NavList.LeadingVisual"; const TrailingVisual = ActionList.TrailingVisual; TrailingVisual.displayName = "NavList.TrailingVisual"; const Divider = ActionList.Divider; Divider.displayName = "NavList.Divider"; const TrailingAction = ActionList.TrailingAction; TrailingAction.displayName = "NavList.TrailingAction"; const Group = (t0) => { const $ = c(14); 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]; } const headingLevel = React.useContext(NavListHeadingLevelContext); let t1; if ($[4] !== headingLevel) { t1 = headingLevel ? levelToHeadingTag(headingLevel + 1) : "h3"; $[4] = headingLevel; $[5] = t1; } else t1 = $[5]; const groupHeadingAs = t1; let t2; if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t2 = /*#__PURE__*/ jsx(ActionList.Divider, {}); $[6] = t2; } else t2 = $[6]; let t3; if ($[7] !== groupHeadingAs || $[8] !== title) { t3 = title ? /*#__PURE__*/ jsx(ActionList.GroupHeading, { as: groupHeadingAs, "data-component": "ActionList.GroupHeading", children: title }) : null; $[7] = groupHeadingAs; $[8] = title; $[9] = t3; } else t3 = $[9]; let t4; if ($[10] !== children || $[11] !== props || $[12] !== t3) { t4 = /*#__PURE__*/ jsxs(Fragment, { children: [t2, /*#__PURE__*/ jsxs(ActionList.Group, { ...props, children: [t3, children] })] }); $[10] = children; $[11] = props; $[12] = t3; $[13] = t4; } else t4 = $[13]; return t4; }; 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 === void 0 ? "Show more" : t1; const pages = t2 === void 0 ? 0 : t2; const [currentPage, setCurrentPage] = React.useState(0); const groupId = useId$1(); 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 : void 0; 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, 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; }); /** * 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(13); let as; let className; let rest; if ($[0] !== t0) { ({as, className, ...rest} = t0); $[0] = t0; $[1] = as; $[2] = className; $[3] = rest; } else { as = $[1]; className = $[2]; rest = $[3]; } const headingLevel = React.useContext(NavListHeadingLevelContext); let t1; if ($[4] !== as || $[5] !== headingLevel) { var _as; t1 = (_as = as) !== null && _as !== void 0 ? _as : headingLevel ? levelToHeadingTag(headingLevel + 1) : "h3"; $[4] = as; $[5] = headingLevel; $[6] = t1; } else t1 = $[6]; const resolvedAs = t1; let t2; if ($[7] !== className) { t2 = clsx(NavList_module_css_default.GroupHeading, className); $[7] = className; $[8] = t2; } else t2 = $[8]; let t3; if ($[9] !== resolvedAs || $[10] !== rest || $[11] !== t2) { t3 = /*#__PURE__*/ jsx(ActionList.GroupHeading, { as: resolvedAs, className: t2, "data-component": "NavList.GroupHeading", headingWrapElement: "li", ...rest }); $[9] = resolvedAs; $[10] = rest; $[11] = t2; $[12] = t3; } else t3 = $[12]; return t3; }; const NavList = Object.assign(Root, { Description: ActionList.Description, Heading, Item, SubNav, LeadingVisual, TrailingVisual, TrailingAction, Divider, Group, GroupExpand, GroupHeading }); //#endregion export { GroupExpand, NavList };