@carbon/ibm-products
Version:
Carbon for IBM Products
202 lines (192 loc) • 7.05 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.
*/
import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
import React__default, { useState, useRef, useEffect } from 'react';
import PropTypes from '../../_virtual/index.js';
import cx from 'classnames';
import { useResizeObserver } from '../../global/js/hooks/useResizeObserver.js';
import { usePrefix, ButtonSet, Button, MenuButton, MenuItem } from '@carbon/react';
import { pkg } from '../../settings.js';
import { prepareProps } from '../../global/js/utils/props-helper.js';
const blockClass = `${pkg.prefix}--button-set-with-overflow`;
const componentName = 'ButtonSetWithOverflow';
const buttonSize = 'md';
const ButtonSetWithOverflow = _ref => {
let {
buttons,
className,
onWidthChange,
buttonSetOverflowLabel,
menuOptionsClass,
rightAlign
} = _ref;
const carbonPrefix = usePrefix();
const [showAsOverflow, setShowAsOverflow] = useState(false);
const spaceAvailableRef = useRef(null);
const sizingContainerRefSet = useRef(null);
const sizingContainerRefCombo = useRef(null);
const sizes = useRef({});
/**
* checkFullyVisibleItems determines display count based on space available and width of pageActions
*
* ButtonSetWithOverflow switches between a Carbon ButtonSet and use of the ButtonMenu component depending
* on the space available. While there is sufficient space to show all of the buttons side by side the
* ButtonSet is used, once this is no longer the case it switches to a ButtonMenu.
*
*/
const checkFullyVisibleItems = () => {
const spaceAvailable = spaceAvailableRef.current?.offsetWidth;
let newShowAsOverflow = true;
// get all of the hidden sizing buttons
const sizingSet = sizingContainerRefSet.current?.querySelectorAll(`.${carbonPrefix}--btn`);
// calculate total width of button set
let sizingSetTotalSize = 0;
for (let item of sizingSet) {
sizingSetTotalSize += item.offsetWidth;
}
// check ButtonMenu size
const sizingComboSize = sizingContainerRefCombo.current?.offsetWidth;
if (onWidthChange && (sizes.current.minWidth !== sizingComboSize || sizes.current.maxWidth !== sizingSetTotalSize)) {
sizes.current.minWidth = sizingComboSize;
sizes.current.maxWidth = sizingSetTotalSize;
// report min and max width required to host
onWidthChange({
...sizes.current
});
}
// only if space available use ButtonSet.
if (sizingSetTotalSize <= spaceAvailable) {
newShowAsOverflow = false;
}
setShowAsOverflow(newShowAsOverflow);
};
useEffect(() => {
checkFullyVisibleItems();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [buttons]);
const AButtonSet = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
let {
isHidden = false,
buttons,
...rest
} = _ref2;
return /*#__PURE__*/React__default.createElement(ButtonSet, _extends({}, rest, {
ref: ref
}), buttons.map(_ref3 => {
let {
label,
key,
kind,
id,
...other
} = _ref3;
const kindFix = kind === 'default' ? 'primary' : kind;
return /*#__PURE__*/React__default.createElement(Button, _extends({}, other, {
key: key && `button-set-${key}`,
size: buttonSize,
type: "button",
kind: kindFix,
id: id ? isHidden ? `${id}--hidden` : id : null
}), label);
}));
});
AButtonSet.propTypes = {
/**
* isHidden - Used to conditionally change id if one is passed to the `AButtonSet` component
* in order to avoid duplicate ids between the visible and hidden button set buttons
*/
isHidden: PropTypes.bool
};
const AButtonMenu = /*#__PURE__*/React__default.forwardRef((_ref4, ref) => {
let {
buttons,
...rest
} = _ref4;
return /*#__PURE__*/React__default.createElement(MenuButton, _extends({}, rest, {
ref: ref,
label: buttonSetOverflowLabel
}), buttons.map(_ref5 => {
let {
key,
kind,
...other
} = _ref5;
// menu items only come in default and danger flavors
const kindFix = kind === 'danger' ? 'danger' : 'default';
return /*#__PURE__*/React__default.createElement(MenuItem, _extends({}, prepareProps(other, ['iconDescription', 'renderIcon']), {
key: key && `button-menu-${key}`,
kind: kindFix
}));
}).reverse());
});
useResizeObserver(sizingContainerRefSet, checkFullyVisibleItems);
useResizeObserver(sizingContainerRefCombo, checkFullyVisibleItems);
useResizeObserver(spaceAvailableRef, checkFullyVisibleItems);
return /*#__PURE__*/React__default.createElement("div", {
className: cx([blockClass, className, {
[`${blockClass}--right`]: rightAlign
}]),
ref: spaceAvailableRef
}, /*#__PURE__*/React__default.createElement("div", {
className: `${blockClass}__button-container ${blockClass}__button-container--hidden`
}, /*#__PURE__*/React__default.createElement(AButtonSet, {
"aria-hidden": true,
ref: sizingContainerRefSet,
size: buttonSize,
buttons: buttons,
isHidden: true
})), /*#__PURE__*/React__default.createElement("div", {
className: `${blockClass}__button-container ${blockClass}__button-container--hidden`,
"aria-hidden": true
}, /*#__PURE__*/React__default.createElement(AButtonMenu, {
className: menuOptionsClass,
ref: sizingContainerRefCombo,
buttons: buttons,
size: buttonSize
})), /*#__PURE__*/React__default.createElement("div", {
className: `${blockClass}__button-container ${blockClass}__button-container--visible`
}, showAsOverflow ? /*#__PURE__*/React__default.createElement(AButtonMenu, {
buttons: buttons,
size: buttonSize,
className: menuOptionsClass
}) : /*#__PURE__*/React__default.createElement(AButtonSet, {
className: `${blockClass}__button-container`,
size: buttonSize,
buttons: buttons
})));
};
ButtonSetWithOverflow.propTypes = {
/**
* buttonSetOverflowLabel - used when button set is shown as combo button
*/
buttonSetOverflowLabel: PropTypes.node.isRequired,
/**
* Specifies the buttons for the ButtonSetWithOverflow. Each item is specified as an object
* with the properties of a Carbon Button plus a label.
*
* Carbon Button API https://react.carbondesignsystem.com/?path=/docs/components-button--default#component-api
*/
buttons: PropTypes.array.isRequired,
/**
* className
*/
className: PropTypes.string,
/**
* class name applied to the overflow options
*/
menuOptionsClass: PropTypes.string,
/**
* onResize reports maxSize on resize
*/
onWidthChange: PropTypes.func,
/**
* align buttons to right of available space
*/
rightAlign: PropTypes.bool
};
ButtonSetWithOverflow.displayName = componentName;
export { ButtonSetWithOverflow };