@primer/react
Version:
An implementation of GitHub's Primer Design System using React
130 lines (123 loc) • 5.42 kB
JavaScript
import React__default, { forwardRef, useRef, useContext } from 'react';
import '../sx.js';
import { UnderlineNavContext } from './UnderlineNavContext.js';
import { getLinkStyles, iconWrapStyles, counterStyles } from './styles.js';
import { LoadingCounter } from './LoadingCounter.js';
import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect.js';
import { defaultSxProp } from '../utils/defaultSxProp.js';
import Box from '../Box/Box.js';
import CounterLabel from '../CounterLabel/CounterLabel.js';
import merge from 'deepmerge';
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
// adopted from React.AnchorHTMLAttributes
const UnderlineNavItem = /*#__PURE__*/forwardRef(({
sx: sxProp = defaultSxProp,
as: Component = 'a',
href = '#',
children,
counter,
onSelect,
'aria-current': ariaCurrent,
icon: Icon,
...props
}, forwardedRef) => {
const backupRef = useRef(null);
const ref = forwardedRef !== null && forwardedRef !== void 0 ? forwardedRef : backupRef;
const {
theme,
setChildrenWidth,
setNoIconChildrenWidth,
selectedLink,
setSelectedLink,
selectedLinkText,
setSelectedLinkText,
selectEvent,
afterSelect,
variant,
loadingCounters,
iconsVisible
} = useContext(UnderlineNavContext);
useIsomorphicLayoutEffect(() => {
if (ref.current) {
const domRect = ref.current.getBoundingClientRect();
const icon = Array.from(ref.current.children).find(child => child.getAttribute('data-component') === 'icon');
const content = Array.from(ref.current.children).find(child => child.getAttribute('data-component') === 'text');
const text = content.textContent;
const iconWidthWithMargin = icon ? icon.getBoundingClientRect().width + Number(getComputedStyle(icon).marginRight.slice(0, -2)) + Number(getComputedStyle(icon).marginLeft.slice(0, -2)) : 0;
setChildrenWidth({
text,
width: domRect.width
});
setNoIconChildrenWidth({
text,
width: domRect.width - iconWidthWithMargin
});
// When an item has aria-current !== false while rendering, we should be sure to select it.
// It can happen when the page is loaded (selectedLink === undefined)
// or if the item is coming out of the menu when there is enough space to show items along with the more menu. (selectedLink.current === null)
if ((selectedLink === undefined || selectedLink.current === null) && Boolean(ariaCurrent) && ariaCurrent !== 'false') {
setSelectedLink(ref);
}
// Only runs when a menu item is selected (swapping the menu item with the list item to keep it visible)
if (selectedLinkText === text) {
setSelectedLink(ref);
if (typeof onSelect === 'function' && selectEvent !== null) onSelect(selectEvent);
setSelectedLinkText('');
}
}
}, [ref, ariaCurrent, selectedLink, selectedLinkText, setSelectedLinkText, setSelectedLink, setChildrenWidth, setNoIconChildrenWidth, onSelect, selectEvent]);
const keyPressHandler = React__default.useCallback(event => {
if (event.key === ' ' || event.key === 'Enter') {
if (!event.defaultPrevented && typeof onSelect === 'function') onSelect(event);
if (!event.defaultPrevented && typeof afterSelect === 'function') afterSelect(event);
setSelectedLink(ref);
}
}, [onSelect, afterSelect, ref, setSelectedLink]);
const clickHandler = React__default.useCallback(event => {
if (!event.defaultPrevented) {
if (typeof onSelect === 'function') onSelect(event);
if (typeof afterSelect === 'function') afterSelect(event);
}
setSelectedLink(ref);
}, [onSelect, afterSelect, ref, setSelectedLink]);
return /*#__PURE__*/React__default.createElement(Box, {
as: "li",
sx: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}
}, /*#__PURE__*/React__default.createElement(Box, _extends({
as: Component,
href: href,
onKeyPress: keyPressHandler,
onClick: clickHandler,
"aria-current": ariaCurrent,
sx: merge(getLinkStyles(theme, {
variant
}, selectedLink, ref), sxProp)
}, props, {
ref: ref
}), iconsVisible && Icon && /*#__PURE__*/React__default.createElement(Box, {
as: "span",
"data-component": "icon",
sx: iconWrapStyles
}, /*#__PURE__*/React__default.createElement(Icon, null)), children && /*#__PURE__*/React__default.createElement(Box, {
as: "span",
"data-component": "text",
"data-content": children,
sx: selectedLink === ref ? {
fontWeight: 600
} : {}
}, children), loadingCounters ? /*#__PURE__*/React__default.createElement(Box, {
as: "span",
"data-component": "counter",
sx: counterStyles
}, /*#__PURE__*/React__default.createElement(LoadingCounter, null)) : counter !== undefined && /*#__PURE__*/React__default.createElement(Box, {
as: "span",
"data-component": "counter",
sx: counterStyles
}, /*#__PURE__*/React__default.createElement(CounterLabel, null, counter))));
});
UnderlineNavItem.displayName = 'UnderlineNavItem';
export { UnderlineNavItem };