UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

211 lines (201 loc) 9 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. */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import { Button, InlineLoading, ButtonSet } from '@carbon/react'; import React__default from 'react'; import { allPropTypes, prepareProps } from '../../global/js/utils/props-helper.js'; import PropTypes from '../../_virtual/index.js'; import cx from 'classnames'; import { pkg } from '../../settings.js'; var _InlineLoading; const blockClass = `${pkg.prefix}--action-set`; const componentName = 'ActionSet'; const ActionSetButton = /*#__PURE__*/React__default.forwardRef((_ref, ref) => { let { // The component props, in alphabetical order (for consistency). className, disabled, kind, label, loading, isExpressive = true, // Collect any other property values passed in. ...rest } = _ref; return /*#__PURE__*/React__default.createElement(Button, _extends({}, rest, { isExpressive: isExpressive, className: cx(className, [`${blockClass}__action-button`, { [`${blockClass}__action-button--ghost`]: kind === 'ghost' || kind === 'danger--ghost', [`${blockClass}__action-button--expressive`]: isExpressive }]), disabled: disabled || loading || false, kind, ref }), label, loading && (_InlineLoading || (_InlineLoading = /*#__PURE__*/React__default.createElement(InlineLoading, null)))); }); ActionSetButton.displayName = 'ActionSetButton'; ActionSetButton.propTypes = { /**@ts-ignore*/ ...Button.PropTypes, kind: PropTypes.oneOf(['ghost', 'danger--ghost', 'secondary', 'danger', 'primary']), label: PropTypes.string, loading: PropTypes.bool }; const defaultKind = 'primary'; const willStack = (size, numberOfActions) => size === 'sm' || size === 'md' && numberOfActions > 2; // Default values for props const defaults = { size: 'md' }; /** * An ActionSet presents a set of action buttons, constructed from bundles * of prop values and applying some layout rules. When the size is 'sm' * the buttons are stacked, and should only include primary and secondary * kinds. When the size is 'md' the buttons are stacked if there are three or * more. When the size is 'md' or 'lg', two buttons share the horizontal space. * When the size is 'lg', three or more buttons use a quarter of the available * horizontal space, and if the size is 'xlg' or 'max' the buttons always use * a quarter of the available horizontal space. If there is a ghost button, * it appears at the left side. If there is a primary button it appears at the * right. */ const ActionSet = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => { let { // The component props, in alphabetical order (for consistency). actions, buttonSize, className, size = defaults.size, // Collect any other property values passed in. ...rest } = _ref2; const buttons = actions && actions.slice?.(0) || []; // We stack the buttons in a sm set, or if there are three or more in a md set. const stacking = willStack(size, buttons.length); // Order of button kinds: ghost first, then danger--ghost, then most other types, // then danger, and finally primary const buttonOrder = kind => ({ ghost: 1, 'danger--ghost': 2, danger: 4, primary: 5 })[kind] ?? 3; // order the actions with ghost/ghost-danger buttons first and primary/danger buttons last // (or the opposite way if we're stacking) buttons.sort((action1, action2) => (buttonOrder(action1.kind || defaultKind) - buttonOrder(action2.kind || defaultKind)) * (stacking ? -1 : 1)); return /*#__PURE__*/React__default.createElement(ButtonSet, _extends({}, rest, { className: cx(blockClass, className, { [`${blockClass}--row-single`]: !stacking && buttons.length === 1, [`${blockClass}--row-double`]: !stacking && buttons.length === 2, [`${blockClass}--row-triple`]: !stacking && buttons.length === 3, [`${blockClass}--row-quadruple`]: !stacking && buttons.length >= 4, [`${blockClass}--stacking`]: stacking }, `${blockClass}--${size}`), ref: ref, role: "presentation", stacked: stacking }), buttons.map((action, index) => { const actionProps = prepareProps(action, ['key']); return /*#__PURE__*/React__default.createElement(ActionSetButton, _extends({ key: action.key || index }, actionProps, { size: buttonSize })); })); }); ActionSet.displayName = componentName; /** * A validator function to help validate the actions supplied for a particular * size of component. When the size is sm, or md with three actions, the * buttons will be stacked and a maximum of three buttons is applied with no * ghosts unless the ghost is the only button. Otherwise a maximum of four * buttons with a maximum of one ghost is applied. In either case, a maximum * of one primary button is allowed. * @param sizeFn An optional function which will be passed all the props and * returns the size that the component should be treated as being: if not * provided, a 'size' prop is used to determine the size of the component. * @returns null if the actions meet the requirements, or an Error object with * an explanatory message. */ /**@ts-ignore*/ ActionSet.validateActions = sizeFn => (props, propName, componentName, location, propFullName) => { const name = propFullName || propName; const prop = props[name]; const actions = prop && prop?.length; const problems = []; if (actions > 0) { const size = sizeFn ? sizeFn(props) : props.size || defaults.size; const stacking = willStack(size, actions); const countActions = kind => prop.filter(action => (action.kind || defaultKind) === kind).length; const primaryActions = countActions('primary'); const secondaryActions = countActions('secondary'); const dangerActions = countActions('danger'); const ghostActions = countActions('ghost') + countActions('danger--ghost'); if (stacking && actions > 3) { problems.push(`you cannot have more than three actions in this size of ${componentName}`); } if (actions > 4) { problems.push(`you cannot have more than four actions in a ${componentName}`); } if (primaryActions > 1) { problems.push(`you cannot have more than one 'primary' action in a ${componentName}`); } if (ghostActions > 1) { problems.push(`you cannot have more than one 'ghost' action in a ${componentName}`); } if (stacking && actions > 1 && ghostActions > 0) { problems.push(`you cannot have a 'ghost' button in conjunction with other action types in this size of ${componentName}`); } if (actions > primaryActions + secondaryActions + dangerActions + ghostActions) { problems.push(`you can only have 'primary', 'danger', 'secondary', 'ghost' and 'danger--ghost' buttons in a ${componentName}`); } } return problems.length > 0 ? new Error(`Invalid ${location} \`${name}\` supplied to \`${componentName}\`: ${problems.join(', and ')}.`) : null; }; ActionSet.propTypes = { /** * The action buttons to show. Each action is specified as an * object with optional fields 'label' to supply the button label, 'kind' * to select the button kind (must be 'primary', 'secondary' or 'ghost'), * 'loading' to display a loading indicator, and 'onClick' to receive * notifications when the button is clicked. Additional fields in the object * will be passed to the Button component, and these can include 'disabled', * 'ref', 'className', and any other Button props. Any other fields in the * object will be passed through to the button element as HTML attributes. * * See https://react.carbondesignsystem.com/?path=/docs/components-button--default#component-api */ actions: allPropTypes([/**@ts-ignore*/ ActionSet.validateActions(), PropTypes.arrayOf(PropTypes.shape({ /**@ts-ignore*/ ...Button.propTypes, kind: PropTypes.oneOf(['ghost', 'danger--ghost', 'secondary', 'danger', 'primary']), label: PropTypes.string, loading: PropTypes.bool, // we duplicate this Button prop to improve the DocGen here /**@ts-ignore*/ onClick: Button.propTypes.onClick }))]), /** * The size of buttons to use for the actions. The allowed values are * those for the size prop of carbon Button. If this prop is specified, all * the buttons will be set to this size, overriding any 'size' values (if any) * supplied in the actions array (if any). */ /**@ts-ignore*/ buttonSize: Button.propTypes.size, /** * An optional class or classes to be added to the outermost element. */ className: PropTypes.string, /** * The size of the action set. Different button arrangements are used at * different sizes, to make best use of the available space. */ size: PropTypes.oneOf(['sm', 'md', 'lg', 'xl', '2xl']) }; export { ActionSet };