@primer/react
Version:
An implementation of GitHub's Primer Design System using React
272 lines (271 loc) • 10.6 kB
JavaScript
import VisuallyHidden from "../_VisuallyHidden.js";
import { IconButton } from "../Button/IconButton.js";
import { ButtonComponent } from "../Button/Button.js";
import { AnchoredOverlay } from "../AnchoredOverlay/AnchoredOverlay.js";
import LabelGroup_module_css_default from "./LabelGroup.module.css.js";
import { c } from "react-compiler-runtime";
import { clsx } from "clsx";
import { jsx, jsxs } from "react/jsx-runtime";
import React from "react";
import { getFocusableChild } from "@primer/behaviors/utils";
import { XIcon } from "@primer/octicons-react";
//#region src/LabelGroup/LabelGroup.tsx
const getOverlayWidth = (buttonClientRect, containerRef, overlayPaddingPx) => {
var _containerRef$current;
return overlayPaddingPx + buttonClientRect.right - (((_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.getBoundingClientRect().left) || 0);
};
const InlineToggle = (t0) => {
const $ = c(7);
const { collapseButtonRef, collapseInlineExpandedChildren, expandButtonRef, hiddenItemIds, isOverflowShown, showAllTokensInline } = t0;
let t1;
if ($[0] !== collapseButtonRef || $[1] !== collapseInlineExpandedChildren || $[2] !== expandButtonRef || $[3] !== hiddenItemIds || $[4] !== isOverflowShown || $[5] !== showAllTokensInline) {
t1 = isOverflowShown ? /*#__PURE__*/ jsx(ButtonComponent, {
ref: collapseButtonRef,
onClick: collapseInlineExpandedChildren,
size: "small",
variant: "invisible",
children: "Show less"
}) : hiddenItemIds.length ? /*#__PURE__*/ jsxs(ButtonComponent, {
ref: expandButtonRef,
variant: "invisible",
size: "small",
onClick: showAllTokensInline,
children: [/*#__PURE__*/ jsxs(VisuallyHidden, { children: [
"Show +",
hiddenItemIds.length,
" more"
] }), /*#__PURE__*/ jsxs("span", {
"aria-hidden": "true",
children: ["+", hiddenItemIds.length]
})]
}) : null;
$[0] = collapseButtonRef;
$[1] = collapseInlineExpandedChildren;
$[2] = expandButtonRef;
$[3] = hiddenItemIds;
$[4] = isOverflowShown;
$[5] = showAllTokensInline;
$[6] = t1;
} else t1 = $[6];
return t1;
};
const OverlayToggle = (t0) => {
const $ = c(10);
const { children, closeOverflowOverlay, expandButtonRef, hiddenItemIds, isOverflowShown, openOverflowOverlay, overlayPaddingPx, overlayWidth, totalLength } = t0;
let t1;
if ($[0] !== children || $[1] !== closeOverflowOverlay || $[2] !== expandButtonRef || $[3] !== hiddenItemIds.length || $[4] !== isOverflowShown || $[5] !== openOverflowOverlay || $[6] !== overlayPaddingPx || $[7] !== overlayWidth || $[8] !== totalLength) {
t1 = hiddenItemIds.length ? /*#__PURE__*/ jsx(AnchoredOverlay, {
open: isOverflowShown,
onOpen: openOverflowOverlay,
onClose: closeOverflowOverlay,
width: "auto",
height: "auto",
align: "start",
side: "inside-right",
anchorRef: expandButtonRef,
anchorOffset: overlayPaddingPx * -1,
alignmentOffset: overlayPaddingPx * -1,
renderAnchor: (props) => /*#__PURE__*/ jsxs(ButtonComponent, {
variant: "invisible",
size: "small",
...props,
ref: expandButtonRef,
children: [/*#__PURE__*/ jsxs(VisuallyHidden, { children: [
"Show +",
hiddenItemIds.length,
" more"
] }), /*#__PURE__*/ jsxs("span", {
"aria-hidden": "true",
children: ["+", hiddenItemIds.length]
})]
}),
focusZoneSettings: { disabled: true },
overlayProps: {
role: "dialog",
"aria-label": `All ${totalLength} labels`,
"aria-modal": true
},
children: /*#__PURE__*/ jsxs("div", {
className: LabelGroup_module_css_default.OverlayContainer,
style: {
width: overlayWidth,
padding: `${overlayPaddingPx}px`
},
children: [/*#__PURE__*/ jsx("div", {
className: LabelGroup_module_css_default.OverlayInner,
children
}), /*#__PURE__*/ jsx(IconButton, {
onClick: closeOverflowOverlay,
icon: XIcon,
"aria-label": "Close",
variant: "invisible",
className: LabelGroup_module_css_default.CloseButton
})]
})
}) : null;
$[0] = children;
$[1] = closeOverflowOverlay;
$[2] = expandButtonRef;
$[3] = hiddenItemIds.length;
$[4] = isOverflowShown;
$[5] = openOverflowOverlay;
$[6] = overlayPaddingPx;
$[7] = overlayWidth;
$[8] = totalLength;
$[9] = t1;
} else t1 = $[9];
return t1;
};
const LabelGroup = ({ children, visibleChildCount, overflowStyle = "overlay", as: Component = "ul", className }) => {
const containerRef = React.useRef(null);
const collapseButtonRef = React.useRef(null);
const firstHiddenIndexRef = React.useRef(void 0);
const [visibilityMap, setVisibilityMap] = React.useState({});
const [isOverflowShown, setIsOverflowShown] = React.useState(false);
const [buttonClientRect, setButtonClientRect] = React.useState({
width: 0,
right: 0,
height: 0,
x: 0,
y: 0,
top: 0,
left: 0,
bottom: 0,
toJSON: () => void 0
});
const overlayPaddingPx = 8;
const hiddenItemIds = Object.keys(visibilityMap).filter((key) => !visibilityMap[key]);
const overlayWidth = hiddenItemIds.length && overflowStyle === "overlay" ? getOverlayWidth(buttonClientRect, containerRef, overlayPaddingPx) : void 0;
const expandButtonRef = React.useCallback((node) => {
if (node !== null) {
const nodeClientRect = node.getBoundingClientRect();
if (nodeClientRect.width !== buttonClientRect.width || nodeClientRect.right !== buttonClientRect.right) setButtonClientRect(nodeClientRect);
expandButtonRef.current = node;
}
}, [buttonClientRect]);
const hideChildrenAfterIndex = React.useCallback((truncateAfter) => {
var _containerRef$current2;
const containerChildren = ((_containerRef$current2 = containerRef.current) === null || _containerRef$current2 === void 0 ? void 0 : _containerRef$current2.children) || [];
const updatedEntries = {};
for (const child of containerChildren) {
const targetId = child.getAttribute("data-index");
if (targetId) updatedEntries[targetId] = parseInt(targetId, 10) < truncateAfter;
}
setVisibilityMap(updatedEntries);
}, []);
const openOverflowOverlay = React.useCallback(() => setIsOverflowShown(true), [setIsOverflowShown]);
const closeOverflowOverlay = React.useCallback(() => {
setIsOverflowShown(false);
}, [setIsOverflowShown]);
const collapseInlineExpandedChildren = React.useCallback(() => {
setIsOverflowShown(false);
if (visibleChildCount && typeof visibleChildCount === "number") hideChildrenAfterIndex(visibleChildCount);
setTimeout(() => {
var _expandButtonRef$curr;
(_expandButtonRef$curr = expandButtonRef.current) === null || _expandButtonRef$curr === void 0 || _expandButtonRef$curr.focus();
}, 10);
}, [
expandButtonRef,
hideChildrenAfterIndex,
visibleChildCount
]);
const showAllTokensInline = React.useCallback(() => {
setVisibilityMap({});
setIsOverflowShown(true);
}, [setVisibilityMap, setIsOverflowShown]);
React.useEffect(() => {
if (!visibleChildCount || isOverflowShown) return;
if (visibleChildCount === "auto") {
const observer = new IntersectionObserver((entries) => {
const updatedEntries_0 = {};
for (const entry of entries) {
const targetId_0 = entry.target.getAttribute("data-index");
if (targetId_0) updatedEntries_0[targetId_0] = entry.isIntersecting;
}
setVisibilityMap((prev) => ({
...prev,
...updatedEntries_0
}));
}, {
root: containerRef.current,
rootMargin: `0px -${buttonClientRect.width}px 0px 0px`,
threshold: 1
});
for (const item of ((_containerRef$current3 = containerRef.current) === null || _containerRef$current3 === void 0 ? void 0 : _containerRef$current3.children) || []) {
var _containerRef$current3;
if (item.getAttribute("data-index")) observer.observe(item);
}
return () => observer.disconnect();
} else hideChildrenAfterIndex(visibleChildCount);
}, [
buttonClientRect,
visibleChildCount,
hideChildrenAfterIndex,
isOverflowShown
]);
React.useEffect(() => {
if (overflowStyle === "overlay") return;
if (hiddenItemIds.length) firstHiddenIndexRef.current = parseInt(hiddenItemIds[0], 10);
}, [
hiddenItemIds,
overflowStyle,
isOverflowShown
]);
React.useEffect(() => {
if (overflowStyle === "overlay") return;
const firstHiddenChildDOM = document.querySelector(`[data-index="${firstHiddenIndexRef.current}"]`);
const focusableChild = firstHiddenChildDOM ? getFocusableChild(firstHiddenChildDOM) : null;
if (isOverflowShown) if (focusableChild) focusableChild.focus();
else {
var _collapseButtonRef$cu;
(_collapseButtonRef$cu = collapseButtonRef.current) === null || _collapseButtonRef$cu === void 0 || _collapseButtonRef$cu.focus();
}
}, [overflowStyle, isOverflowShown]);
const isList = Component === "ul" || Component === "ol";
const ToggleWrapper = isList ? "li" : "span";
const ItemWrapperComponent = isList ? "li" : "span";
return visibleChildCount ? /*#__PURE__*/ jsxs(Component, {
ref: containerRef,
"data-overflow": overflowStyle === "inline" && isOverflowShown ? "inline" : void 0,
"data-list": isList || void 0,
className: clsx(className, LabelGroup_module_css_default.Container),
"data-component": "LabelGroup",
children: [React.Children.map(children, (child_0, index) => /*#__PURE__*/ jsx(ItemWrapperComponent, {
"data-index": index,
className: clsx(LabelGroup_module_css_default.ItemWrapper, { [LabelGroup_module_css_default["ItemWrapper--hidden"]]: hiddenItemIds.includes(index.toString()) }),
children: child_0
}, index)), /*#__PURE__*/ jsx(ToggleWrapper, {
"data-component": "LabelGroup.Toggle",
children: overflowStyle === "inline" ? /*#__PURE__*/ jsx(InlineToggle, {
collapseButtonRef,
collapseInlineExpandedChildren,
expandButtonRef,
hiddenItemIds,
isOverflowShown,
showAllTokensInline,
totalLength: React.Children.toArray(children).length
}) : /*#__PURE__*/ jsx(OverlayToggle, {
closeOverflowOverlay,
expandButtonRef,
hiddenItemIds,
isOverflowShown,
openOverflowOverlay,
overlayPaddingPx,
overlayWidth,
totalLength: React.Children.toArray(children).length,
children
})
})]
}) : /*#__PURE__*/ jsx(Component, {
"data-overflow": "inline",
"data-list": isList || void 0,
className: clsx(className, LabelGroup_module_css_default.Container),
"data-component": "LabelGroup",
children: isList ? React.Children.map(children, (child_1, index_0) => {
return /*#__PURE__*/ jsx("li", { children: child_1 }, index_0);
}) : children
});
};
LabelGroup.displayName = "LabelGroup";
//#endregion
export { LabelGroup as default };