UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

355 lines (349 loc) 12.7 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 React__default, { forwardRef } from 'react'; import cx from 'classnames'; import { Section, Layer, unstable_FeatureFlags, OverflowMenu, MenuItem, Button, IconButton } from '@carbon/react'; import { Incomplete, CheckmarkOutline } from '@carbon/react/icons'; import PropTypes from '../../_virtual/index.js'; import { CardHeader } from './CardHeader.js'; import { CardFooter } from './CardFooter.js'; import { pkg } from '../../settings.js'; var _Incomplete, _CheckmarkOutline; const componentName = 'Card'; const Card = /*#__PURE__*/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 = `${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__default.createElement(Layer, { level: 2 }, /*#__PURE__*/React__default.createElement(unstable_FeatureFlags, { enableV12Overflowmenu: true }, /*#__PURE__*/React__default.createElement(OverflowMenu, { autoAlign: true, menuAlignment: pos, size: size, label: overflowAriaLabel || iconDescription }, overflowActions.map(_ref2 => { let { id, itemText, ...rest } = _ref2; return /*#__PURE__*/React__default.createElement(MenuItem, _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__default.createElement("span", { key: id, className: `${blockClass}__icon` }, Icon && /*#__PURE__*/React__default.createElement(Icon, { "aria-label": iconDescription }), iconDescription); } if (productive) { return /*#__PURE__*/React__default.createElement(Button, _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__default.createElement("a", _extends({ key: id, className: `${blockClass}__icon`, href: href, onClick: onClick }, linkProps), Icon && /*#__PURE__*/React__default.createElement(Icon, { "aria-label": iconDescription })); } return /*#__PURE__*/React__default.createElement(IconButton, { key: id, label: iconDescription, className: `${blockClass}__icon`, onClick: onClick, kind: "ghost", size: "sm" }, Icon && /*#__PURE__*/React__default.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__default.createElement(Section, _extends({ as: "div", "aria-disabled": disabled }, getCardProps()), !getStarted && media && /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__media` }, media), Pictogram && /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__pictogram` }, /*#__PURE__*/React__default.createElement(Pictogram, null)), getStarted && sequence && /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__sequence` }, sequence), getStarted && status && /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__status` }, status === 'incomplete' && (_Incomplete || (_Incomplete = /*#__PURE__*/React__default.createElement(Incomplete, null))), status === 'complete' && (_CheckmarkOutline || (_CheckmarkOutline = /*#__PURE__*/React__default.createElement(CheckmarkOutline, null)))), /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__content-container` }, /*#__PURE__*/React__default.createElement("div", getHeaderBodyProps(), /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__header-wrapper` }, /*#__PURE__*/React__default.createElement(CardHeader, getHeaderProps()), /*#__PURE__*/React__default.createElement("div", getBodyProps(), children)), getStarted && media && /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__media` }, media)), hasBottomBar && /*#__PURE__*/React__default.createElement(CardFooter, getFooterProps()))); }); Card.propTypes = { /**@ts-ignore */ actionIcons: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string, icon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), onKeyDown: PropTypes.func, onClick: PropTypes.func, iconDescription: PropTypes.string, /** * @deprecated please use the `link.href` instead */ href: PropTypes.string, link: PropTypes.shape({ href: PropTypes.string.isRequired }) })), actionsPlacement: PropTypes.oneOf(['top', 'bottom']), children: PropTypes.node, className: PropTypes.string, clickZone: PropTypes.oneOf(['one', 'two', 'three']), /** * Optional prop that allows you to pass any component. */ decorator: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), /**@ts-ignore */ description: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), disabled: PropTypes.bool, /**@ts-ignore */ footerActionIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), getStarted: PropTypes.bool, label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), media: PropTypes.node, mediaPosition: PropTypes.oneOf(['top', 'left']), /**@ts-ignore */ metadata: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string, icon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), iconDescription: PropTypes.string })), onClick: PropTypes.func, onKeyDown: PropTypes.func, onPrimaryButtonClick: PropTypes.func, onSecondaryButtonClick: PropTypes.func, /**@ts-ignore */ overflowActions: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string, itemText: PropTypes.string, onClick: PropTypes.func, onKeyDown: PropTypes.func })), overflowAriaLabel: PropTypes.string, /**@ts-ignore */ pictogram: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), primaryButtonDisabled: PropTypes.bool, primaryButtonHref: PropTypes.string, /**@ts-ignore */ primaryButtonIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), primaryButtonKind: PropTypes.oneOf(['primary', 'ghost']), primaryButtonPlacement: PropTypes.oneOf(['top', 'bottom']), primaryButtonText: PropTypes.string, productive: PropTypes.bool, secondaryButtonDisabled: PropTypes.bool, secondaryButtonHref: PropTypes.string, /**@ts-ignore */ secondaryButtonIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), secondaryButtonKind: PropTypes.oneOf(['secondary', 'ghost']), secondaryButtonPlacement: PropTypes.oneOf(['top', 'bottom']), secondaryButtonText: PropTypes.string, sequence: PropTypes.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: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), status: PropTypes.oneOf(['complete', 'incomplete']), title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), titleSize: PropTypes.oneOf(['default', 'large']) }; Card.displayName = componentName; export { Card };