@carbon/ibm-products
Version:
Carbon for IBM Products
256 lines (254 loc) • 11.2 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 { pkg } from "../../settings.js";
import { CardHeader } from "./CardHeader.js";
import { CardFooter } from "./CardFooter.js";
import React, { forwardRef } from "react";
import PropTypes from "prop-types";
import { Button, IconButton, Layer, MenuItem, OverflowMenu, Section, unstable_FeatureFlags } from "@carbon/react";
import { CheckmarkOutline, Incomplete } from "@carbon/react/icons";
//#region src/components/Card/Card.tsx
var import_classnames = /* @__PURE__ */ __toESM(require_classnames());
const componentName = "Card";
const Card = forwardRef(({ 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", ...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
};
const getActions = () => {
if (overflowActions.length > 0) {
const pos = actionsPlacement === "top" ? "bottom-end" : "top-end";
const size = actionsPlacement === "top" ? "sm" : "md";
return /* @__PURE__ */ React.createElement(Layer, { level: 2 }, /* @__PURE__ */ React.createElement(unstable_FeatureFlags, { enableV12Overflowmenu: true }, /* @__PURE__ */ React.createElement(OverflowMenu, {
autoAlign: true,
menuAlignment: pos,
size,
label: overflowAriaLabel || iconDescription
}, overflowActions.map(({ id, itemText, onKeyDown, onClick, ...rest }) => /* @__PURE__ */ React.createElement(MenuItem, {
...rest,
key: id,
label: itemText ?? "",
onKeyDown,
onClick
})))));
}
return getIcons().map(({ id, icon: Icon, onClick, iconDescription, href: deprecatedHref, link, ...rest }) => {
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(Button, {
...rest,
key: id,
renderIcon: Icon,
hasIconOnly: true,
onClick,
size: actionsPlacement === "top" ? "sm" : "md",
iconDescription,
kind: "ghost",
href,
...linkProps
});
if (href) return /* @__PURE__ */ React.createElement("a", {
key: id,
className: `${blockClass}__icon`,
href,
onClick,
...linkProps
}, Icon && /* @__PURE__ */ React.createElement(Icon, { "aria-label": iconDescription }));
return /* @__PURE__ */ React.createElement(IconButton, {
key: id,
label: iconDescription,
className: `${blockClass}__icon`,
onClick,
kind: "ghost",
size: "sm"
}, Icon && /* @__PURE__ */ React.createElement(Icon, { "aria-label": iconDescription }));
});
};
const getCardProps = () => {
const clickable = hasClickEvent && !productive || hasClickEvent && productive && clickZone === "one";
return {
...rest,
ref,
className: (0, import_classnames.default)(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
};
};
const getHeaderBodyProps = () => {
const clickable = hasClickEvent && clickZone === "two";
return {
className: (0, import_classnames.default)(`${blockClass}__header-body-container`, { [`${blockClass}__clickable`]: clickable }),
...clickable && clickableProps
};
};
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";
return {
className: (0, import_classnames.default)(`${blockClass}__body`, { [`${blockClass}__clickable`]: clickable }),
...clickable && clickableProps
};
};
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(Section, {
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" && /* @__PURE__ */ React.createElement(Incomplete, null), status === "complete" && /* @__PURE__ */ React.createElement(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, getHeaderProps()), /* @__PURE__ */ React.createElement("div", getBodyProps(), children)), getStarted && media && /* @__PURE__ */ React.createElement("div", { className: `${blockClass}__media` }, media)), hasBottomBar && /* @__PURE__ */ React.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;
//#endregion
export { Card };