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