UNPKG

@carbon/react

Version:

React components for the Carbon Design System

99 lines (97 loc) 3.46 kB
/** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { useResizeObserver } from "./useResizeObserver.js"; import { usePreviousValue } from "./usePreviousValue.js"; import { useEffect, useMemo, useRef, useState } from "react"; //#region src/internal/useOverflowItems.ts /** * Copyright IBM Corp. 2025, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ /** * Manages overflow items in a container by automatically hiding items that don't fit. * @param items - Array of items to manage for overflow, each must have an `id` property. * @param containerRef - React ref to the container element that holds the items. * @param offsetRef - Optional ref to an offset element (like a "more" button) whose width is reserved when calculating available space. * @param maxItems - Optional maximum number of visible items. If undefined, only container space constrains visibility. * @param onChange - Optional callback called when hidden items change. Receives array of currently hidden items. * @returns Object with `visibleItems` (items to display), `hiddenItems` (items that don't fit), and `itemRefHandler` (function to attach refs to items for width measurement). */ const useOverflowItems = (items, containerRef, offsetRef, maxItems, onChange) => { const itemsRef = useRef(null); const [maxWidth, setMaxWidth] = useState(0); const overflowItems = useMemo(() => Array.isArray(items) ? items : [], [items]); const handleResize = () => { if (containerRef.current) { const offset = offsetRef?.current?.offsetWidth || 0; setMaxWidth(containerRef.current.offsetWidth - offset); } }; useResizeObserver({ ref: containerRef, onResize: handleResize }); const getMap = () => { if (!itemsRef.current) itemsRef.current = /* @__PURE__ */ new Map(); return itemsRef.current; }; const itemRefHandler = (id, node) => { const map = getMap(); if (node) { const style = getComputedStyle?.(node); const totalWidth = node.offsetWidth + parseInt(style.marginLeft) + parseInt(style.marginRight); map.set(id, totalWidth); } return () => { map.delete(id); }; }; const getVisibleItems = () => { if (!containerRef) return overflowItems; const map = getMap(); let maxReached = false; let accumulatedWidth = 0; return overflowItems.slice(0, maxItems).reduce((prev, cur) => { if (maxReached) return prev; const itemWidth = map.get(cur.id) || 0; if (accumulatedWidth + itemWidth <= maxWidth) { accumulatedWidth += itemWidth; prev.push(cur); } else maxReached = true; return prev; }, []); }; const visibleItems = useMemo(() => { return getVisibleItems(); }, [ overflowItems, maxWidth, maxItems ]); const hiddenItems = useMemo(() => { return overflowItems.slice(visibleItems.length); }, [overflowItems, visibleItems]); const previousHiddenItems = usePreviousValue(hiddenItems); useEffect(() => { if (previousHiddenItems && onChange) { if (hiddenItems.length !== previousHiddenItems.length || hiddenItems.some((item, index) => item !== previousHiddenItems[index])) onChange(hiddenItems); } }, [ hiddenItems, previousHiddenItems, onChange ]); return { visibleItems, itemRefHandler, hiddenItems }; }; //#endregion export { useOverflowItems as default };