@primer/react
Version:
An implementation of GitHub's Primer Design System using React
745 lines (722 loc) • 18.8 kB
JavaScript
import { c } from 'react-compiler-runtime';
import { PlusIcon, ChevronDownIcon } from '@primer/octicons-react';
import React, { createElement, isValidElement } from 'react';
import { ActionList } from '../ActionList/index.js';
import { SubItem } from '../ActionList/Item.js';
import { ActionListContainerContext } from '../ActionList/ActionListContainerContext.js';
import '@styled-system/css';
import merge from 'deepmerge';
import { defaultSxProp } from '../utils/defaultSxProp.js';
import { useId } from '../hooks/useId.js';
import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect.js';
import classes from '../ActionList/ActionList.module.css.js';
import { flushSync } from 'react-dom';
import { BoxWithFallback } from '../internal/components/BoxWithFallback.js';
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
import Box from '../Box/Box.js';
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(BoxWithFallback, {
as: "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 Item = /*#__PURE__*/React.forwardRef((t0, ref) => {
const $ = c(31);
let ariaCurrent;
let children;
let defaultOpen;
let props;
let t1;
if ($[0] !== t0) {
({
"aria-current": ariaCurrent,
children,
defaultOpen,
sx: t1,
...props
} = t0);
$[0] = t0;
$[1] = ariaCurrent;
$[2] = children;
$[3] = defaultOpen;
$[4] = props;
$[5] = t1;
} else {
ariaCurrent = $[1];
children = $[2];
defaultOpen = $[3];
props = $[4];
t1 = $[5];
}
const sxProp = t1 === undefined ? defaultSxProp : t1;
const {
depth
} = React.useContext(SubNavContext);
let childrenWithoutSubNavOrTrailingAction;
let subNav;
let t2;
if ($[6] !== children || $[7] !== defaultOpen) {
subNav = React.Children.toArray(children).find(_temp);
let t3;
if ($[11] !== children) {
t3 = React.Children.toArray(children).filter(_temp2);
$[11] = children;
$[12] = t3;
} else {
t3 = $[12];
}
childrenWithoutSubNavOrTrailingAction = t3;
if (! /*#__PURE__*/isValidElement(subNav) && defaultOpen) {
console.error("NavList.Item must have a NavList.SubNav to use defaultOpen.");
}
t2 = subNav && /*#__PURE__*/isValidElement(subNav);
$[6] = children;
$[7] = defaultOpen;
$[8] = childrenWithoutSubNavOrTrailingAction;
$[9] = subNav;
$[10] = t2;
} else {
childrenWithoutSubNavOrTrailingAction = $[8];
subNav = $[9];
t2 = $[10];
}
if (t2) {
let t3;
if ($[13] !== depth) {
t3 = {
"--subitem-depth": depth
};
$[13] = depth;
$[14] = t3;
} else {
t3 = $[14];
}
const t4 = t3;
let t5;
if ($[15] !== childrenWithoutSubNavOrTrailingAction || $[16] !== defaultOpen || $[17] !== depth || $[18] !== subNav || $[19] !== sxProp || $[20] !== t4) {
t5 = /*#__PURE__*/jsx(ItemWithSubNav, {
subNav: subNav,
depth: depth,
defaultOpen: defaultOpen,
sx: sxProp,
style: t4,
children: childrenWithoutSubNavOrTrailingAction
});
$[15] = childrenWithoutSubNavOrTrailingAction;
$[16] = defaultOpen;
$[17] = depth;
$[18] = subNav;
$[19] = sxProp;
$[20] = t4;
$[21] = t5;
} else {
t5 = $[21];
}
return t5;
}
const t3 = Boolean(ariaCurrent) && ariaCurrent !== "false";
let t4;
if ($[22] !== depth) {
t4 = {
"--subitem-depth": depth
};
$[22] = depth;
$[23] = t4;
} else {
t4 = $[23];
}
const t5 = t4;
let t6;
if ($[24] !== ariaCurrent || $[25] !== children || $[26] !== props || $[27] !== ref || $[28] !== t3 || $[29] !== t5) {
t6 = /*#__PURE__*/jsx(ActionList.LinkItem, {
ref: ref,
"aria-current": ariaCurrent,
active: t3,
style: t5,
...props,
children: children
});
$[24] = ariaCurrent;
$[25] = children;
$[26] = props;
$[27] = ref;
$[28] = t3;
$[29] = t5;
$[30] = t6;
} else {
t6 = $[30];
}
return t6;
});
Item.displayName = 'NavList.Item';
// ----------------------------------------------------------------------------
// ItemWithSubNav (internal)
const ItemWithSubNavContext = /*#__PURE__*/React.createContext({
buttonId: '',
subNavId: '',
isOpen: false
});
function ItemWithSubNav({
children,
subNav,
depth: _depth,
defaultOpen,
style,
sx: sxProp = defaultSxProp
}) {
var _ref;
const buttonId = useId();
const subNavId = useId();
const [isOpen, setIsOpen] = React.useState((_ref = defaultOpen || null) !== null && _ref !== void 0 ? _ref : false);
const subNavRef = React.useRef(null);
const [containsCurrentItem, setContainsCurrentItem] = React.useState(false);
useIsomorphicLayoutEffect(() => {
if (subNavRef.current) {
// Check if SubNav contains current item
// valid values: page, step, location, date, time, true and false
const currentItem = subNavRef.current.querySelector('[aria-current]:not([aria-current=false])');
if (currentItem) {
setContainsCurrentItem(true);
setIsOpen(true);
}
}
}, [subNav, buttonId]);
if (sxProp !== defaultSxProp) {
return /*#__PURE__*/jsx(ItemWithSubNavContext.Provider, {
value: {
buttonId,
subNavId,
isOpen
},
children: /*#__PURE__*/jsxs(ActionList.Item, {
id: buttonId,
"aria-expanded": isOpen,
"aria-controls": subNavId,
active: !isOpen && containsCurrentItem,
onSelect: () => setIsOpen(open => !open),
style: style,
sx: sxProp,
children: [children, /*#__PURE__*/jsx(ActionList.TrailingVisual, {
children: /*#__PURE__*/jsx(ChevronDownIcon, {
className: classes.ExpandIcon
})
}), /*#__PURE__*/jsx(SubItem, {
children: /*#__PURE__*/React.cloneElement(subNav, {
ref: subNavRef
})
})]
})
});
}
return /*#__PURE__*/jsx(ItemWithSubNavContext.Provider, {
value: {
buttonId,
subNavId,
isOpen
},
children: /*#__PURE__*/jsxs(ActionList.Item, {
id: buttonId,
"aria-expanded": isOpen,
"aria-controls": subNavId,
active: !isOpen && containsCurrentItem,
onSelect: () => setIsOpen(open_0 => !open_0),
style: style,
children: [children, /*#__PURE__*/jsx(ActionList.TrailingVisual, {
children: /*#__PURE__*/jsx(ChevronDownIcon, {
className: classes.ExpandIcon
})
}), /*#__PURE__*/jsx(SubItem, {
children: /*#__PURE__*/React.cloneElement(subNav, {
ref: subNavRef
})
})]
})
});
}
ItemWithSubNav.displayName = "ItemWithSubNav"; // ----------------------------------------------------------------------------
// NavList.SubNav
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(21);
const {
children,
sx: t1
} = t0;
const sxProp = t1 === undefined ? defaultSxProp : t1;
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;
}
if (sxProp !== defaultSxProp) {
const t2 = depth + 1;
let t3;
if ($[0] !== t2) {
t3 = {
depth: t2
};
$[0] = t2;
$[1] = t3;
} else {
t3 = $[1];
}
let t4;
if ($[2] !== buttonId || $[3] !== children || $[4] !== forwardedRef || $[5] !== subNavId || $[6] !== sxProp) {
t4 = /*#__PURE__*/jsx(Box, {
as: "ul",
id: subNavId,
"aria-labelledby": buttonId,
className: classes.SubGroup,
ref: forwardedRef,
sx: sxProp,
children: children
});
$[2] = buttonId;
$[3] = children;
$[4] = forwardedRef;
$[5] = subNavId;
$[6] = sxProp;
$[7] = t4;
} else {
t4 = $[7];
}
let t5;
if ($[8] !== t3 || $[9] !== t4) {
t5 = /*#__PURE__*/jsx(SubNavContext.Provider, {
value: t3,
children: t4
});
$[8] = t3;
$[9] = t4;
$[10] = t5;
} else {
t5 = $[10];
}
return t5;
}
const t2 = depth + 1;
let t3;
if ($[11] !== t2) {
t3 = {
depth: t2
};
$[11] = t2;
$[12] = t3;
} else {
t3 = $[12];
}
let t4;
if ($[13] !== buttonId || $[14] !== children || $[15] !== forwardedRef || $[16] !== subNavId) {
t4 = /*#__PURE__*/jsx("ul", {
className: classes.SubGroup,
id: subNavId,
"aria-labelledby": buttonId,
ref: forwardedRef,
children: children
});
$[13] = buttonId;
$[14] = children;
$[15] = forwardedRef;
$[16] = subNavId;
$[17] = t4;
} else {
t4 = $[17];
}
let t5;
if ($[18] !== t3 || $[19] !== t4) {
t5 = /*#__PURE__*/jsx(SubNavContext.Provider, {
value: t3,
children: t4
});
$[18] = t3;
$[19] = t4;
$[20] = t5;
} else {
t5 = $[20];
}
return t5;
});
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 defaultSx = {};
const Group = t0 => {
const $ = c(18);
let children;
let props;
let t1;
let title;
if ($[0] !== t0) {
({
title,
children,
sx: t1,
...props
} = t0);
$[0] = t0;
$[1] = children;
$[2] = props;
$[3] = t1;
$[4] = title;
} else {
children = $[1];
props = $[2];
t1 = $[3];
title = $[4];
}
const sxProp = t1 === undefined ? defaultSx : t1;
if (sxProp !== defaultSx) {
let t2;
if ($[5] !== title) {
t2 = title ? /*#__PURE__*/jsx(ActionList.GroupHeading, {
children: title
}) : null;
$[5] = title;
$[6] = t2;
} else {
t2 = $[6];
}
let t3;
if ($[7] !== children || $[8] !== sxProp || $[9] !== t2) {
t3 = /*#__PURE__*/jsxs(Box, {
sx: sxProp,
as: "li",
"data-component": "ActionList.Group",
children: [t2, children]
});
$[7] = children;
$[8] = sxProp;
$[9] = t2;
$[10] = t3;
} else {
t3 = $[10];
}
return t3;
}
let t2;
if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
t2 = /*#__PURE__*/jsx(ActionList.Divider, {});
$[11] = t2;
} else {
t2 = $[11];
}
let t3;
if ($[12] !== title) {
t3 = title ? /*#__PURE__*/jsx(ActionList.GroupHeading, {
as: "h3",
"data-component": "ActionList.GroupHeading",
children: title
}) : null;
$[12] = title;
$[13] = t3;
} else {
t3 = $[13];
}
let t4;
if ($[14] !== children || $[15] !== props || $[16] !== t3) {
t4 = /*#__PURE__*/jsxs(Fragment, {
children: [t2, /*#__PURE__*/jsxs(ActionList.Group, {
...props,
children: [t3, children]
})]
});
$[14] = children;
$[15] = props;
$[16] = t3;
$[17] = t4;
} else {
t4 = $[17];
}
return t4;
};
// ----------------------------------------------------------------------------
// 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 rest;
let t1;
let t2;
if ($[0] !== t0) {
({
as: t1,
sx: t2,
...rest
} = t0);
$[0] = t0;
$[1] = rest;
$[2] = t1;
$[3] = t2;
} else {
rest = $[1];
t1 = $[2];
t2 = $[3];
}
const as = t1 === undefined ? "h3" : t1;
const sxProp = t2 === undefined ? defaultSxProp : t2;
let t3;
if ($[4] !== sxProp) {
t3 = merge({
"> a {": {
color: "var(--fgColor-default)",
textDecoration: "inherit",
":hover": {
textDecoration: "underline"
}
}
}, sxProp);
$[4] = sxProp;
$[5] = t3;
} else {
t3 = $[5];
}
let t4;
if ($[6] !== as || $[7] !== rest || $[8] !== t3) {
t4 = /*#__PURE__*/jsx(ActionList.GroupHeading, {
as: as,
sx: t3,
"data-component": "NavList.GroupHeading",
headingWrapElement: "li",
...rest
});
$[6] = as;
$[7] = rest;
$[8] = t3;
$[9] = t4;
} else {
t4 = $[9];
}
return t4;
};
// ----------------------------------------------------------------------------
// Export
const NavList = Object.assign(Root, {
Item,
SubNav,
LeadingVisual,
TrailingVisual,
TrailingAction,
Divider,
Group,
GroupExpand,
GroupHeading
});
function _temp(child) {
return /*#__PURE__*/isValidElement(child) && child.type === SubNav;
}
function _temp2(child_0) {
return /*#__PURE__*/isValidElement(child_0) ? child_0.type !== SubNav && child_0.type !== TrailingAction : true;
}
export { GroupExpand, NavList };