UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

161 lines (153 loc) 6.4 kB
/** * Copyright IBM Corp. 2020, 2025 * * 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'; var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var React = require('react'); var settings = require('../../settings.js'); var react = require('@carbon/react'); var index = require('../../_virtual/index.js'); var cx = require('classnames'); var devtools = require('../../global/js/utils/devtools.js'); var useIsomorphicEffect = require('../../global/js/hooks/useIsomorphicEffect.js'); // The block part of our conventional BEM class names (blockClass__E--M). const blockClass = `${settings.pkg.prefix}--truncated-list`; const componentName = 'TruncatedList'; const defaults = { as: 'ul', collapsedItemsLimit: 5, expandedItemsLimit: 10, onClick: () => {}, viewLessLabel: 'View less', viewMoreLabel: value => `View more (${value})` }; /** * The `TruncatedList` allows consumers to control how many items are * revealed to the user while giving the user the ability to expand * and see the entire list. */ exports.TruncatedList = /*#__PURE__*/React.forwardRef((_ref, ref) => { let { children, className, as: List = defaults.as, buttonClassName, collapsedItemsLimit = defaults.collapsedItemsLimit, expandedItemsLimit = defaults.expandedItemsLimit, onClick = defaults.onClick, viewLessLabel = defaults.viewLessLabel, viewMoreLabel = defaults.viewMoreLabel, // Collect any other property values passed in. ...rest } = _ref; const childrenArray = React.Children.toArray(children); // Exception handling: minimum number of items is 1. const minItems = Math.max(collapsedItemsLimit, 1); // Exception handling: maximum number of items is the number of items passed as children. const maxItems = Math.min(expandedItemsLimit, childrenArray.length); const [isCollapsed, setIsCollapsed] = React.useState(true); // To minimize *initial* animation, // (difference of zero height to component's rendered height) // guesstimate the initial height to reduce animation distance. // (difference of the guessed height to rendered height - a few pixels) const [listHeight, setListHeight] = React.useState(minItems * 16); const listRef = React.useRef(undefined); const handleToggle = () => { setIsCollapsed(prev => !prev); }; React.useEffect(() => { onClick(isCollapsed); }, [isCollapsed, onClick]); // Calculate height of the list by measuring the height of each item. // (E.g. the height of plain text is not the same height as a link.) // CSS animation requires an explicitly declared height for when the // list is both collapsed and expanded. React.useEffect(() => { if (listRef && childrenArray.length > 0) { const numItemsToShow = isCollapsed ? minItems : maxItems; const items = listRef.current?.childNodes; let listHeight = 0; for (let index = 0; index < numItemsToShow; index++) { if (items && items[index]) { const itemElement = items[index]; const height = window?.getComputedStyle(itemElement)?.height || '16'; listHeight += parseInt(height); } } setListHeight(listHeight); } }, [childrenArray, minItems, maxItems, isCollapsed, listRef]); useIsomorphicEffect.useIsomorphicEffect(() => { if (listRef.current) { listRef.current.style.height = `${listHeight}px`; } }, [listHeight]); return /*#__PURE__*/React.createElement("div", _rollupPluginBabelHelpers.extends({}, rest, { className: cx(blockClass, className, isCollapsed ? `${blockClass}--collapsed` : `${blockClass}--expanded`, // If the list is expanded && showing all the items, // then disable scrolling using CSS, instead of adding an arbitrary #px to the height of the list. !isCollapsed && childrenArray.length <= maxItems && `${blockClass}--expanded-all`), ref: ref }, devtools.getDevtoolsProps(componentName)), /*#__PURE__*/React.createElement(List, { className: `${blockClass}__list`, ref: listRef }, isCollapsed ? childrenArray.slice(0, minItems) : children), childrenArray.length > minItems && /*#__PURE__*/React.createElement(react.Button, { className: cx(`${blockClass}__button`, `${settings.carbon.prefix}--link`, buttonClassName), kind: "ghost", size: "sm", onClick: handleToggle }, isCollapsed ? viewMoreLabel(childrenArray.length - minItems) : viewLessLabel)); }); // Return a placeholder if not released and not enabled by feature flag exports.TruncatedList = settings.pkg.checkComponentEnabled(exports.TruncatedList, componentName); // The display name of the component, used by React. Note that displayName // is used in preference to relying on function.name. exports.TruncatedList.displayName = componentName; // The types and DocGen commentary for the component props, // in alphabetical order (for consistency). // See https://www.npmjs.com/package/prop-types#usage. exports.TruncatedList.propTypes = { /** * The type of list element to render. * This could be a `ul`, `ol`, or a custom React component. */ as: index.default.oneOfType([index.default.elementType, index.default.string]), /** * Optional class name for expand/collapse button. */ buttonClassName: index.default.string, /** * The contents of the TruncatedList. */ children: index.default.node.isRequired, /** * Provide an optional class to be applied to the containing node. */ className: index.default.string, /** * Number of items to render and display when the list is truncated and collapsed. * Scrolling is not enabled when collapsed. The smallest number is 1. */ collapsedItemsLimit: index.default.number, /** * Maximum number of items to show when the list is expanded. All * items are rendered when the list is expanded. Scrolling is enabled * if there are more items to display than this number. */ expandedItemsLimit: index.default.number, /** * Optional callback reports the collapsed state of the list. */ onClick: index.default.func, /** * Text label for when the list is expanded. */ viewLessLabel: index.default.string, /** * Callback function for building the label when the list is collapsed. */ viewMoreLabel: index.default.func };