@carbon/ibm-products
Version:
Carbon for IBM Products
161 lines (153 loc) • 6.4 kB
JavaScript
/**
* 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.
*/
;
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
};