UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

272 lines (271 loc) • 10.6 kB
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 };