@primer/react
Version:
An implementation of GitHub's Primer Design System using React
589 lines (588 loc) • 17.4 kB
JavaScript
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 };