UNPKG

@carbon/react

Version:

React components for the Carbon Design System

127 lines (116 loc) 4.57 kB
/** * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var React = require('react'); var useResizeObserver = require('./useResizeObserver.js'); var usePreviousValue = require('./usePreviousValue.js'); /** * 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 = React.useRef(null); const [maxWidth, setMaxWidth] = React.useState(0); if (!items || !Array.isArray(items)) { return { visibleItems: [], hiddenItems: [], itemRefHandler: () => {} }; } const handleResize = () => { if (containerRef.current) { const offset = offsetRef?.current?.offsetWidth || 0; const newMax = containerRef.current.offsetWidth - offset; setMaxWidth(newMax); } }; // eslint-disable-next-line react-hooks/rules-of-hooks -- https://github.com/carbon-design-system/carbon/issues/20452 useResizeObserver.useResizeObserver({ ref: containerRef, onResize: handleResize }); const getMap = () => { if (!itemsRef.current) { itemsRef.current = 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 (!items || Array.isArray(items) === false) { return []; } if (!containerRef) { return items; } const map = getMap(); let maxReached = false; let accumulatedWidth = 0; const visibleItems = items.slice(0, maxItems).reduce((prev, cur) => { if (maxReached) { return prev; } const itemWidth = map.get(cur.id) || 0; const willFit = accumulatedWidth + itemWidth <= maxWidth; if (willFit) { accumulatedWidth += itemWidth; prev.push(cur); } else { maxReached = true; } return prev; }, []); return visibleItems; }; // Memoize visible items calculation to avoid recalculating on every render // eslint-disable-next-line react-hooks/rules-of-hooks -- https://github.com/carbon-design-system/carbon/issues/20452 const visibleItems = React.useMemo(() => { if (!Array.isArray(items)) { return []; } return getVisibleItems(); // eslint-disable-next-line react-hooks/exhaustive-deps -- https://github.com/carbon-design-system/carbon/issues/20452 }, [items, maxWidth, maxItems]); // Memoize hidden items calculation // eslint-disable-next-line react-hooks/rules-of-hooks -- https://github.com/carbon-design-system/carbon/issues/20452 const hiddenItems = React.useMemo(() => { if (!Array.isArray(items)) { return []; } return items.slice(visibleItems.length); }, [items, visibleItems]); // Use previous value to compare and only call onChange when needed // eslint-disable-next-line react-hooks/rules-of-hooks -- https://github.com/carbon-design-system/carbon/issues/20452 const previousHiddenItems = usePreviousValue.usePreviousValue(hiddenItems); // Only call onChange if hidden items actually changed // eslint-disable-next-line react-hooks/rules-of-hooks -- https://github.com/carbon-design-system/carbon/issues/20452 React.useEffect(() => { }, [hiddenItems, previousHiddenItems, onChange]); return { visibleItems, itemRefHandler, hiddenItems }; }; exports.default = useOverflowItems;