@carbon/react
Version:
React components for the Carbon Design System
421 lines (419 loc) • 19.3 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 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.
*/
const require_runtime = require("../../_virtual/_rolldown/runtime.js");
const require_usePrefix = require("../../internal/usePrefix.js");
const require_Text = require("../Text/Text.js");
const require_keys = require("../../internal/keyboard/keys.js");
const require_match = require("../../internal/keyboard/match.js");
const require_useIsomorphicEffect = require("../../internal/useIsomorphicEffect.js");
const require_useId = require("../../internal/useId.js");
const require_deprecate = require("../../prop-types/deprecate.js");
const require_index = require("../Link/index.js");
const require_utils = require("../../internal/utils.js");
const require_useMergedRefs = require("../../internal/useMergedRefs.js");
const require_index$1 = require("../FeatureFlags/index.js");
const require_useNoInteractiveChildren = require("../../internal/useNoInteractiveChildren.js");
const require_index$2 = require("../AILabel/index.js");
const require_events = require("../../tools/events.js");
let classnames = require("classnames");
classnames = require_runtime.__toESM(classnames);
let react = require("react");
react = require_runtime.__toESM(react);
let prop_types = require("prop-types");
prop_types = require_runtime.__toESM(prop_types);
let react_jsx_runtime = require("react/jsx-runtime");
let _carbon_icons_react = require("@carbon/icons-react");
//#region src/components/Tile/Tile.tsx
/**
* Copyright IBM Corp. 2019, 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.
*/
const Tile = react.default.forwardRef(({ children, className, decorator, light = false, slug, hasRoundedCorners = false, ...rest }, ref) => {
const prefix = require_usePrefix.usePrefix();
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
className: (0, classnames.default)(`${prefix}--tile`, {
[`${prefix}--tile--light`]: light,
[`${prefix}--tile--slug`]: slug,
[`${prefix}--tile--slug-rounded`]: slug && hasRoundedCorners,
[`${prefix}--tile--decorator`]: decorator,
[`${prefix}--tile--decorator-rounded`]: decorator && hasRoundedCorners
}, className),
ref,
...rest,
children: [
children,
slug,
decorator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: `${prefix}--tile--inner-decorator`,
children: decorator
})
]
});
});
Tile.displayName = "Tile";
Tile.propTypes = {
children: prop_types.default.node,
className: prop_types.default.string,
decorator: prop_types.default.node,
hasRoundedCorners: prop_types.default.bool,
light: require_deprecate.deprecate(prop_types.default.bool, "The `light` prop for `Tile` is no longer needed and has been deprecated. It will be removed in the next major release. Use the Layer component instead."),
slug: require_deprecate.deprecate(prop_types.default.node, "The `slug` prop for `Tile` has been deprecated in favor of the new `decorator` prop. It will be removed in the next major release.")
};
const ClickableTile = react.default.forwardRef(({ children, className, clicked = false, decorator, disabled, href, light, onClick = () => {}, onKeyDown = () => {}, renderIcon: Icon, hasRoundedCorners, slug, ...rest }, ref) => {
const prefix = require_usePrefix.usePrefix();
const classes = (0, classnames.default)(`${prefix}--tile`, `${prefix}--tile--clickable`, {
[`${prefix}--tile--is-clicked`]: clicked,
[`${prefix}--tile--light`]: light,
[`${prefix}--tile--slug`]: slug,
[`${prefix}--tile--slug-rounded`]: slug && hasRoundedCorners,
[`${prefix}--tile--decorator`]: decorator,
[`${prefix}--tile--decorator-rounded`]: decorator && hasRoundedCorners
}, className);
function handleOnClick(evt) {
evt?.persist?.();
onClick(evt);
}
function handleOnKeyDown(evt) {
evt?.persist?.();
if (!href && require_match.matches(evt, [require_keys.Enter, require_keys.Space])) {
evt.preventDefault();
onClick(evt);
}
onKeyDown(evt);
}
const v12DefaultIcons = require_index$1.useFeatureFlag("enable-v12-tile-default-icons");
if (v12DefaultIcons) {
if (!Icon) Icon = _carbon_icons_react.ArrowRight;
if (disabled) Icon = _carbon_icons_react.Error;
}
const iconClasses = (0, classnames.default)({
[`${prefix}--tile--icon`]: !v12DefaultIcons || v12DefaultIcons && !disabled,
[`${prefix}--tile--disabled-icon`]: v12DefaultIcons && disabled
});
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_index.default, {
className: classes,
href,
tabIndex: !href && !disabled ? 0 : void 0,
onClick: !disabled ? handleOnClick : void 0,
onKeyDown: handleOnKeyDown,
ref,
disabled,
...rest,
children: [
slug || decorator ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: `${prefix}--tile-content`,
children
}) : children,
(slug === true || decorator === true) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.AiLabel, {
size: "24",
className: `${prefix}--tile--ai-label-icon`
}),
react.default.isValidElement(decorator) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: `${prefix}--tile--inner-decorator`,
children: decorator
}),
Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
className: iconClasses,
"aria-hidden": "true"
})
]
});
});
ClickableTile.displayName = "ClickableTile";
ClickableTile.propTypes = {
children: prop_types.default.node,
className: prop_types.default.string,
clicked: prop_types.default.bool,
decorator: prop_types.default.oneOfType([prop_types.default.bool, prop_types.default.node]),
disabled: prop_types.default.bool,
hasRoundedCorners: prop_types.default.bool,
href: prop_types.default.string,
light: require_deprecate.deprecate(prop_types.default.bool, "The `light` prop for `ClickableTile` is no longer needed and has been deprecated. It will be removed in the next major release. Use the Layer component instead."),
onClick: prop_types.default.func,
onKeyDown: prop_types.default.func,
rel: prop_types.default.string,
renderIcon: prop_types.default.oneOfType([prop_types.default.func, prop_types.default.object])
};
const SelectableTile = react.default.forwardRef(({ children, className, decorator, disabled, id, light, onClick = () => {}, onChange = () => {}, onKeyDown = () => {}, selected = false, tabIndex = 0, title = "title", slug, hasRoundedCorners, ...rest }, ref) => {
const prefix = require_usePrefix.usePrefix();
const clickHandler = onClick;
const keyDownHandler = onKeyDown;
const [isSelected, setIsSelected] = (0, react.useState)(selected);
(0, react.useEffect)(() => {
setIsSelected(selected);
}, [selected]);
const classes = (0, classnames.default)(`${prefix}--tile`, `${prefix}--tile--selectable`, {
[`${prefix}--tile--is-selected`]: isSelected,
[`${prefix}--tile--light`]: light,
[`${prefix}--tile--disabled`]: disabled,
[`${prefix}--tile--slug`]: slug,
[`${prefix}--tile--slug-rounded`]: slug && hasRoundedCorners,
[`${prefix}--tile--decorator`]: decorator,
[`${prefix}--tile--decorator-rounded`]: decorator && hasRoundedCorners
}, className);
const handleSelectionChange = (0, react.useCallback)((evt, newSelected) => {
setIsSelected(newSelected);
onChange(evt, newSelected, id);
}, [onChange, id]);
function handleClick(evt) {
evt.preventDefault();
evt?.persist?.();
if (normalizedDecorator && decoratorRef.current && evt.target instanceof Node && decoratorRef.current.contains(evt.target)) return;
handleSelectionChange(evt, !isSelected);
clickHandler(evt);
}
function handleKeyDown(evt) {
evt?.persist?.();
if (require_match.matches(evt, [require_keys.Enter, require_keys.Space])) {
evt.preventDefault();
handleSelectionChange(evt, !isSelected);
}
keyDownHandler(evt);
}
const decoratorRef = (0, react.useRef)(null);
const candidate = slug ?? decorator;
const normalizedDecorator = require_utils.isComponentElement(candidate, require_index$2.AILabel) ? (0, react.cloneElement)(candidate, {
size: "xs",
ref: decoratorRef
}) : candidate;
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
className: classes,
onClick: !disabled ? handleClick : void 0,
role: "checkbox",
"aria-checked": isSelected,
onKeyDown: !disabled ? handleKeyDown : void 0,
tabIndex: !disabled ? tabIndex : void 0,
ref,
id,
title,
...rest,
children: [
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
className: `${prefix}--tile__checkmark ${prefix}--tile__checkmark--persistent`,
children: isSelected ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.CheckboxCheckedFilled, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.Checkbox, {})
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, {
as: "label",
htmlFor: id,
className: `${prefix}--tile-content`,
children
}),
slug ? normalizedDecorator : decorator ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: `${prefix}--tile--inner-decorator`,
children: normalizedDecorator
}) : ""
]
});
});
SelectableTile.propTypes = {
children: prop_types.default.node,
className: prop_types.default.string,
decorator: prop_types.default.node,
disabled: prop_types.default.bool,
hasRoundedCorners: prop_types.default.bool,
id: prop_types.default.string,
light: require_deprecate.deprecate(prop_types.default.bool, "The `light` prop for `SelectableTile` is no longer needed and has been deprecated. It will be removed in the next major release. Use the Layer component instead."),
name: require_deprecate.deprecate(prop_types.default.string, "The `name` property is no longer used. It will be removed in the next major release."),
onChange: prop_types.default.func,
onClick: prop_types.default.func,
onKeyDown: prop_types.default.func,
selected: prop_types.default.bool,
slug: require_deprecate.deprecate(prop_types.default.node, "The `slug` prop for `SelectableTile` has been deprecated in favor of the new `decorator` prop. It will be removed in the next major release."),
tabIndex: prop_types.default.number,
title: prop_types.default.string,
value: require_deprecate.deprecate(prop_types.default.oneOfType([prop_types.default.string, prop_types.default.number]), "The `value` property is no longer used. It will be removed in the next major release.`")
};
const ExpandableTile = (0, react.forwardRef)(({ tabIndex = 0, className, children, decorator, expanded = false, tileMaxHeight = 0, tilePadding = 0, onClick, onKeyUp, tileCollapsedIconText = "Interact to expand Tile", tileExpandedIconText = "Interact to collapse Tile", tileCollapsedLabel, tileExpandedLabel, light, slug, hasRoundedCorners, ...rest }, forwardRef) => {
const [measuredAboveHeight, setMeasuredAboveHeight] = (0, react.useState)(0);
const [measuredPadding, setMeasuredPadding] = (0, react.useState)(0);
const [isExpanded, setIsExpanded] = (0, react.useState)(expanded);
const [interactive, setInteractive] = (0, react.useState)(true);
const aboveTheFold = (0, react.useRef)(null);
const belowTheFold = (0, react.useRef)(null);
const chevronInteractiveRef = (0, react.useRef)(null);
const tileContent = (0, react.useRef)(null);
const tile = (0, react.useRef)(null);
const ref = require_useMergedRefs.useMergedRefs([forwardRef, tile]);
const prefix = require_usePrefix.usePrefix();
(0, react.useEffect)(() => {
setIsExpanded(expanded);
}, [expanded]);
const handleClick = () => {
setIsExpanded((prev) => !prev);
};
const handleKeyUp = (evt) => {
if (evt.target !== tile.current && evt.target !== chevronInteractiveRef.current) {
if (require_match.matches(evt, [require_keys.Enter, require_keys.Space])) evt.preventDefault();
}
};
const classNames = (0, classnames.default)(`${prefix}--tile`, `${prefix}--tile--expandable`, {
[`${prefix}--tile--is-expanded`]: isExpanded,
[`${prefix}--tile--light`]: light
}, className);
const interactiveClassNames = (0, classnames.default)(classNames, `${prefix}--tile--expandable--interactive`, {
[`${prefix}--tile--slug`]: slug,
[`${prefix}--tile--slug-rounded`]: slug && hasRoundedCorners,
[`${prefix}--tile--decorator`]: decorator,
[`${prefix}--tile--decorator-rounded`]: decorator && hasRoundedCorners
});
const chevronInteractiveClassNames = (0, classnames.default)(`${prefix}--tile__chevron`, `${prefix}--tile__chevron--interactive`);
const childrenAsArray = react.Children.toArray(children);
require_useIsomorphicEffect.default(() => {
if (!tile.current || !aboveTheFold.current) return;
const style = window.getComputedStyle(tile.current);
setMeasuredPadding((parseInt(style.getPropertyValue("padding-top"), 10) || 0) + (parseInt(style.getPropertyValue("padding-bottom"), 10) || 0));
setMeasuredAboveHeight(aboveTheFold.current.scrollHeight);
}, []);
require_useIsomorphicEffect.default(() => {
if (!aboveTheFold.current || !belowTheFold.current) return;
setInteractive(Boolean(require_useNoInteractiveChildren.getInteractiveContent(aboveTheFold.current)) || Boolean(require_useNoInteractiveChildren.getRoleContent(aboveTheFold.current)) || Boolean(require_useNoInteractiveChildren.getInteractiveContent(belowTheFold.current)) || Boolean(require_useNoInteractiveChildren.getRoleContent(belowTheFold.current)) || Boolean(slug || decorator));
}, [
slug,
decorator,
children
]);
require_useIsomorphicEffect.default(() => {
if (!tile.current) return;
if (isExpanded) {
tile.current.style.maxHeight = "";
return;
}
const measured = measuredAboveHeight || aboveTheFold.current?.scrollHeight || 0;
const baseHeight = tileMaxHeight > 0 ? tileMaxHeight : measured;
const pad = tilePadding > 0 ? tilePadding : measuredPadding;
tile.current.style.maxHeight = `${baseHeight + pad}px`;
}, [
isExpanded,
tileMaxHeight,
tilePadding,
measuredAboveHeight,
measuredPadding
]);
(0, react.useEffect)(() => {
if (!aboveTheFold.current) return;
const resizeObserver = new ResizeObserver(() => {
if (aboveTheFold.current) setMeasuredAboveHeight(aboveTheFold.current.scrollHeight);
});
resizeObserver.observe(aboveTheFold.current);
return () => resizeObserver.disconnect();
}, []);
const belowTheFoldId = require_useId.useId(interactive ? "expandable-tile-interactive" : "expandable-tile");
const candidate = slug ?? decorator;
const normalizedDecorator = require_utils.isComponentElement(candidate, require_index$2.AILabel) ? (0, react.cloneElement)(candidate, { size: "xs" }) : candidate;
return interactive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
ref,
className: interactiveClassNames,
...rest,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
ref: tileContent,
children: [
slug ? normalizedDecorator : decorator ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: `${prefix}--tile--inner-decorator`,
children: normalizedDecorator
}) : "",
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
ref: aboveTheFold,
className: `${prefix}--tile-content`,
children: childrenAsArray[0]
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
type: "button",
"aria-expanded": isExpanded,
"aria-controls": belowTheFoldId,
onKeyUp: require_events.composeEventHandlers([onKeyUp, handleKeyUp]),
onClick: require_events.composeEventHandlers([onClick, handleClick]),
"aria-label": isExpanded ? tileExpandedIconText : tileCollapsedIconText,
ref: chevronInteractiveRef,
className: chevronInteractiveClassNames,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.ChevronDown, {})
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
ref: belowTheFold,
className: `${prefix}--tile-content`,
id: belowTheFoldId,
children: childrenAsArray[1]
})
]
})
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
type: "button",
ref,
className: classNames,
"aria-controls": belowTheFoldId,
"aria-expanded": isExpanded,
title: isExpanded ? tileExpandedIconText : tileCollapsedIconText,
...rest,
onKeyUp: require_events.composeEventHandlers([onKeyUp, handleKeyUp]),
onClick: require_events.composeEventHandlers([onClick, handleClick]),
tabIndex,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
ref: tileContent,
children: [
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
ref: aboveTheFold,
className: `${prefix}--tile-content`,
children: childrenAsArray[0]
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
className: `${prefix}--tile__chevron`,
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: isExpanded ? tileExpandedLabel : tileCollapsedLabel }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.ChevronDown, {})]
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
ref: belowTheFold,
id: belowTheFoldId,
className: `${prefix}--tile-content`,
children: childrenAsArray[1]
})
]
})
});
});
ExpandableTile.propTypes = {
children: prop_types.default.node,
className: prop_types.default.string,
decorator: prop_types.default.node,
expanded: prop_types.default.bool,
hasRoundedCorners: prop_types.default.bool,
id: prop_types.default.string,
light: require_deprecate.deprecate(prop_types.default.bool, "The `light` prop for `ExpandableTile` is no longer needed and has been deprecated. It will be removed in the next major release. Use the Layer component instead."),
onClick: prop_types.default.func,
onKeyUp: prop_types.default.func,
slug: require_deprecate.deprecate(prop_types.default.node, "The `slug` prop for `ExpandableTile` has been deprecated in favor of the new `decorator` prop. It will be removed in the next major release."),
tabIndex: prop_types.default.number,
tileCollapsedIconText: prop_types.default.string,
tileCollapsedLabel: prop_types.default.string,
tileExpandedIconText: prop_types.default.string,
tileExpandedLabel: prop_types.default.string
};
ExpandableTile.displayName = "ExpandableTile";
const TileAboveTheFoldContent = react.default.forwardRef(({ children }, ref) => {
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
ref,
className: `${require_usePrefix.usePrefix()}--tile-content__above-the-fold`,
children
});
});
TileAboveTheFoldContent.propTypes = { children: prop_types.default.node };
TileAboveTheFoldContent.displayName = "TileAboveTheFoldContent";
const TileBelowTheFoldContent = react.default.forwardRef(({ children }, ref) => {
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
ref,
className: `${require_usePrefix.usePrefix()}--tile-content__below-the-fold`,
children
});
});
TileBelowTheFoldContent.propTypes = { children: prop_types.default.node };
TileBelowTheFoldContent.displayName = "TileBelowTheFoldContent";
//#endregion
exports.ClickableTile = ClickableTile;
exports.ExpandableTile = ExpandableTile;
exports.SelectableTile = SelectableTile;
exports.Tile = Tile;
exports.TileAboveTheFoldContent = TileAboveTheFoldContent;
exports.TileBelowTheFoldContent = TileBelowTheFoldContent;