@carbon/ibm-products
Version:
Carbon for IBM Products
198 lines (196 loc) • 8.49 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 2026
*
* 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 { __toESM } from "../../_virtual/_rolldown/runtime.js";
import { require_classnames } from "../../node_modules/classnames/index.js";
import pconsole_default from "../../global/js/utils/pconsole.js";
import { pkg } from "../../settings.js";
import { allPropTypes, prepareProps } from "../../global/js/utils/props-helper.js";
import React from "react";
import PropTypes from "prop-types";
import { Button, ButtonSet, InlineLoading } from "@carbon/react";
//#region src/components/ActionSet/ActionSet.tsx
/**
* Copyright IBM Corp. 2021, 2024
*
* 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 import_classnames = /* @__PURE__ */ __toESM(require_classnames());
const blockClass = `${pkg.prefix}--action-set`;
const componentName = "ActionSet";
const ActionSetButton = React.forwardRef(({ className, disabled, kind, label, loading, isExpressive = true, ...rest }, ref) => /* @__PURE__ */ React.createElement(Button, {
...rest,
isExpressive,
className: (0, import_classnames.default)(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 && /* @__PURE__ */ React.createElement(InlineLoading, null)));
ActionSetButton.displayName = "ActionSetButton";
ActionSetButton.propTypes = {
/**@ts-ignore*/
...Button.PropTypes,
kind: PropTypes.oneOf([
"ghost",
"danger--ghost",
"tertiary",
"secondary",
"danger",
"primary"
]),
label: PropTypes.string,
loading: PropTypes.bool
};
const defaultKind = "primary";
const willStack = (size, numberOfActions) => size === "sm" || size === "md" && numberOfActions > 2;
const defaults = { size: "md" };
const validateActionSetProps = ({ actions, size }) => {
if (actions && actions.length) {
const problems = [];
const stacking = willStack(size, actions.length);
const countActions = (kind) => actions.filter((action) => (action.kind || defaultKind) === kind).length;
const primaryActions = countActions("primary");
const secondaryActions = countActions("secondary");
const tertiaryActions = countActions("tertiary");
const dangerActions = countActions("danger");
const ghostActions = countActions("ghost") + countActions("danger--ghost");
if (stacking && actions.length > 3) problems.push(`you cannot have more than three actions in this size of ${componentName}`);
if (actions.length > 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.length > 1 && ghostActions > 0) problems.push(`you cannot have a 'ghost' button in conjunction with other action types in this size of ${componentName}`);
if (actions.length > primaryActions + secondaryActions + tertiaryActions + dangerActions + ghostActions) problems.push(`you can only have 'primary', 'danger', 'secondary', 'tertiary', 'ghost' and 'danger--ghost' buttons in a ${componentName}`);
return problems.length > 0 ? pconsole_default.error(`Invalid prop \`actions\` supplied to \`${componentName}\`: ${problems.join(", and ")}.`) : null;
}
};
/**
* 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 = React.forwardRef((props, ref) => {
const { actions, buttonSize, className, disableStacking = false, size = defaults.size, ...rest } = props;
validateActionSetProps({
actions,
size
});
const buttons = actions && actions.slice?.(0) || [];
const stacking = disableStacking ? false : willStack(size, buttons.length);
const buttonOrder = (kind) => ({
ghost: 1,
"danger--ghost": 2,
tertiary: 3,
danger: 5,
primary: 6
})[kind] ?? 4;
buttons.sort((action1, action2) => (buttonOrder(action1.kind || defaultKind) - buttonOrder(action2.kind || defaultKind)) * (stacking ? -1 : 1));
return /* @__PURE__ */ React.createElement(ButtonSet, {
...rest,
className: (0, import_classnames.default)(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,
role: "presentation",
stacked: stacking
}, buttons.map((action, index) => {
const actionProps = prepareProps(action, ["key"]);
return /* @__PURE__ */ React.createElement(ActionSetButton, {
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.
*/
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([PropTypes.arrayOf(PropTypes.shape({
/**@ts-ignore*/
...Button.propTypes,
kind: PropTypes.oneOf([
"ghost",
"danger--ghost",
"tertiary",
"secondary",
"danger",
"primary"
]),
label: PropTypes.string,
loading: PropTypes.bool,
/**@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,
/**
* When true, prevents automatic stacking of buttons even when size would
* normally trigger stacking (e.g., 'sm' size or 'md' with 3+ actions).
* Buttons will remain in a horizontal layout.
*/
disableStacking: PropTypes.bool,
/**
* 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"
])
};
//#endregion
export { ActionSet };