@carbon/ibm-products
Version:
Carbon for IBM Products
357 lines (350 loc) • 12.9 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.
*/
'use strict';
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var React = require('react');
var cx = require('classnames');
var react = require('@carbon/react');
var icons = require('@carbon/react/icons');
var index = require('../../_virtual/index.js');
var CardHeader = require('./CardHeader.js');
var CardFooter = require('./CardFooter.js');
var settings = require('../../settings.js');
var _Incomplete, _CheckmarkOutline;
const componentName = 'Card';
const Card = /*#__PURE__*/React.forwardRef((_ref, ref) => {
let {
// The component props, in alphabetical order (for consistency).
actionIcons = Object.freeze([]),
actionsPlacement = 'bottom',
decorator,
metadata = Object.freeze([]),
children,
className,
clickZone = 'one',
description,
disabled,
footerActionIcon,
getStarted,
label,
media,
mediaPosition = 'top',
onClick,
onKeyDown,
onPrimaryButtonClick,
onSecondaryButtonClick,
overflowActions = Object.freeze([]),
overflowAriaLabel,
pictogram: Pictogram,
primaryButtonDisabled,
primaryButtonHref,
primaryButtonIcon,
primaryButtonKind = 'primary',
primaryButtonPlacement = 'bottom',
primaryButtonText,
productive = false,
secondaryButtonDisabled,
secondaryButtonHref,
secondaryButtonIcon,
secondaryButtonKind = 'secondary',
secondaryButtonPlacement = 'bottom',
secondaryButtonText,
slug,
status,
sequence,
title,
titleSize = 'default',
iconDescription = 'Options',
// Collect any other property values passed in.
...rest
} = _ref;
const getIcons = () => getStarted ? metadata : actionIcons;
const blockClass = `${settings.pkg.prefix}--card`;
const hasActions = getIcons().length > 0 || overflowActions.length > 0 || !!primaryButtonText && primaryButtonPlacement === 'top';
const hasHeaderActions = hasActions && actionsPlacement === 'top';
const hasFooterActions = hasActions && actionsPlacement === 'bottom';
const hasFooterButton = !!secondaryButtonText || !!primaryButtonText && primaryButtonPlacement === 'bottom';
const hasBottomBar = hasFooterActions || hasFooterButton;
const hasClickEvent = !!onClick || !!onKeyDown;
const clickableProps = {
onClick,
onKeyDown,
role: 'button',
tabIndex: 0
};
// actions can either be an overflow menu or series of icons
const getActions = () => {
if (overflowActions.length > 0) {
const pos = actionsPlacement === 'top' ? 'bottom-end' : 'top-end';
const size = actionsPlacement === 'top' ? 'sm' : 'md';
return /*#__PURE__*/React.createElement(react.Layer, {
level: 2
}, /*#__PURE__*/React.createElement(react.unstable_FeatureFlags, {
enableV12Overflowmenu: true
}, /*#__PURE__*/React.createElement(react.OverflowMenu, {
autoAlign: true,
menuAlignment: pos,
size: size,
label: overflowAriaLabel || iconDescription
}, overflowActions.map(_ref2 => {
let {
id,
itemText,
...rest
} = _ref2;
return /*#__PURE__*/React.createElement(react.MenuItem, _rollupPluginBabelHelpers.extends({
key: id,
label: itemText ?? ''
}, rest));
}))));
}
const icons = getIcons().map(_ref3 => {
let {
id,
icon: Icon,
onClick,
iconDescription,
href: deprecatedHref,
link,
...rest
} = _ref3;
const {
href,
...linkProps
} = link ?? {
href: deprecatedHref
};
if (getStarted) {
return /*#__PURE__*/React.createElement("span", {
key: id,
className: `${blockClass}__icon`
}, Icon && /*#__PURE__*/React.createElement(Icon, {
"aria-label": iconDescription
}), iconDescription);
}
if (productive) {
return /*#__PURE__*/React.createElement(react.Button, _rollupPluginBabelHelpers.extends({}, rest, {
key: id,
renderIcon: Icon,
hasIconOnly: true,
onClick: onClick,
size: actionsPlacement === 'top' ? 'sm' : 'md',
iconDescription: iconDescription,
kind: "ghost",
href: href
}, linkProps));
}
if (href) {
return /*#__PURE__*/React.createElement("a", _rollupPluginBabelHelpers.extends({
key: id,
className: `${blockClass}__icon`,
href: href,
onClick: onClick
}, linkProps), Icon && /*#__PURE__*/React.createElement(Icon, {
"aria-label": iconDescription
}));
}
return /*#__PURE__*/React.createElement(react.IconButton, {
key: id,
label: iconDescription,
className: `${blockClass}__icon`,
onClick: onClick,
kind: "ghost",
size: "sm"
}, Icon && /*#__PURE__*/React.createElement(Icon, {
"aria-label": iconDescription
}));
});
return icons;
};
const getCardProps = () => {
const clickable = hasClickEvent && !productive || hasClickEvent && productive && clickZone === 'one';
const cardProps = {
...rest,
ref,
className: cx(blockClass, {
[`${blockClass}__disabled`]: disabled,
[`${blockClass}__get-started`]: getStarted,
[`${blockClass}__productive`]: productive,
[`${blockClass}__clickable`]: clickable,
[`${blockClass}__media-left`]: mediaPosition === 'left',
[`${blockClass}--has-slug`]: !!slug,
[`${blockClass}--has-decorator`]: !!decorator && decorator['type']?.displayName !== 'AILabel',
[`${blockClass}--has-ai-label`]: !!decorator && decorator['type']?.displayName === 'AILabel'
}, className),
...(clickable && clickableProps)
};
return cardProps;
};
// the only reason this is necessary is for click zone 2
const getHeaderBodyProps = () => {
const clickable = hasClickEvent && clickZone === 'two';
const headerBodyProps = {
className: cx(`${blockClass}__header-body-container`, {
[`${blockClass}__clickable`]: clickable
}),
...(clickable && clickableProps)
};
return headerBodyProps;
};
const getHeaderProps = () => ({
actions: actionsPlacement === 'top' ? getActions() : '',
decorator,
noActionIcons: getIcons().length > 0 && actionsPlacement === 'top' ? false : true,
actionsPlacement,
onPrimaryButtonClick,
onSecondaryButtonClick,
primaryButtonIcon,
primaryButtonPlacement,
primaryButtonText,
primaryButtonDisabled,
description,
hasActions: hasHeaderActions,
inClickableCard: hasClickEvent,
label,
secondaryButtonDisabled,
secondaryButtonHref,
secondaryButtonIcon,
secondaryButtonPlacement,
secondaryButtonText,
slug,
title,
titleSize
});
const getBodyProps = () => {
const clickable = hasClickEvent && clickZone === 'three';
const bodyProps = {
className: cx(`${blockClass}__body`, {
[`${blockClass}__clickable`]: clickable
}),
...(clickable && clickableProps)
};
return bodyProps;
};
const getFooterProps = () => ({
actions: actionsPlacement === 'bottom' ? getActions() : '',
actionsPlacement,
disabled,
footerActionIcon,
hasActions: hasFooterActions,
hasButton: hasFooterButton,
onPrimaryButtonClick,
onSecondaryButtonClick,
primaryButtonDisabled,
primaryButtonHref,
primaryButtonIcon,
primaryButtonKind,
primaryButtonPlacement,
primaryButtonText,
productive,
secondaryButtonDisabled,
secondaryButtonHref,
secondaryButtonIcon,
secondaryButtonKind,
secondaryButtonPlacement,
secondaryButtonText
});
return /*#__PURE__*/React.createElement(react.Section, _rollupPluginBabelHelpers.extends({
as: "div",
"aria-disabled": disabled
}, getCardProps()), !getStarted && media && /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__media`
}, media), Pictogram && /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__pictogram`
}, /*#__PURE__*/React.createElement(Pictogram, null)), getStarted && sequence && /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__sequence`
}, sequence), getStarted && status && /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__status`
}, status === 'incomplete' && (_Incomplete || (_Incomplete = /*#__PURE__*/React.createElement(icons.Incomplete, null))), status === 'complete' && (_CheckmarkOutline || (_CheckmarkOutline = /*#__PURE__*/React.createElement(icons.CheckmarkOutline, null)))), /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__content-container`
}, /*#__PURE__*/React.createElement("div", getHeaderBodyProps(), /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__header-wrapper`
}, /*#__PURE__*/React.createElement(CardHeader.CardHeader, getHeaderProps()), /*#__PURE__*/React.createElement("div", getBodyProps(), children)), getStarted && media && /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__media`
}, media)), hasBottomBar && /*#__PURE__*/React.createElement(CardFooter.CardFooter, getFooterProps())));
});
Card.propTypes = {
/**@ts-ignore */
actionIcons: index.default.arrayOf(index.default.shape({
id: index.default.string,
icon: index.default.oneOfType([index.default.func, index.default.object]),
onKeyDown: index.default.func,
onClick: index.default.func,
iconDescription: index.default.string,
/**
* @deprecated please use the `link.href` instead
*/
href: index.default.string,
link: index.default.shape({
href: index.default.string.isRequired
})
})),
actionsPlacement: index.default.oneOf(['top', 'bottom']),
children: index.default.node,
className: index.default.string,
clickZone: index.default.oneOf(['one', 'two', 'three']),
/**
* Optional prop that allows you to pass any component.
*/
decorator: index.default.oneOfType([index.default.node, index.default.bool]),
/**@ts-ignore */
description: index.default.oneOfType([index.default.string, index.default.node]),
disabled: index.default.bool,
/**@ts-ignore */
footerActionIcon: index.default.oneOfType([index.default.func, index.default.object]),
getStarted: index.default.bool,
label: index.default.oneOfType([index.default.string, index.default.node]),
media: index.default.node,
mediaPosition: index.default.oneOf(['top', 'left']),
/**@ts-ignore */
metadata: index.default.arrayOf(index.default.shape({
id: index.default.string,
icon: index.default.oneOfType([index.default.func, index.default.object]),
iconDescription: index.default.string
})),
onClick: index.default.func,
onKeyDown: index.default.func,
onPrimaryButtonClick: index.default.func,
onSecondaryButtonClick: index.default.func,
/**@ts-ignore */
overflowActions: index.default.arrayOf(index.default.shape({
id: index.default.string,
itemText: index.default.string,
onClick: index.default.func,
onKeyDown: index.default.func
})),
overflowAriaLabel: index.default.string,
/**@ts-ignore */
pictogram: index.default.oneOfType([index.default.func, index.default.object]),
primaryButtonDisabled: index.default.bool,
primaryButtonHref: index.default.string,
/**@ts-ignore */
primaryButtonIcon: index.default.oneOfType([index.default.func, index.default.object]),
primaryButtonKind: index.default.oneOf(['primary', 'ghost']),
primaryButtonPlacement: index.default.oneOf(['top', 'bottom']),
primaryButtonText: index.default.string,
productive: index.default.bool,
secondaryButtonDisabled: index.default.bool,
secondaryButtonHref: index.default.string,
/**@ts-ignore */
secondaryButtonIcon: index.default.oneOfType([index.default.func, index.default.object]),
secondaryButtonKind: index.default.oneOf(['secondary', 'ghost']),
secondaryButtonPlacement: index.default.oneOf(['top', 'bottom']),
secondaryButtonText: index.default.string,
sequence: index.default.number,
/**
* **Experimental:** For all cases a `Slug` component can be provided.
* Clickable tiles only accept a boolean value of true and display a hollow slug.
* @deprecated please use the `decorator` prop
*/
slug: index.default.oneOfType([index.default.node, index.default.bool]),
status: index.default.oneOf(['complete', 'incomplete']),
title: index.default.oneOfType([index.default.string, index.default.node]),
titleSize: index.default.oneOf(['default', 'large'])
};
Card.displayName = componentName;
exports.Card = Card;