UNPKG

@carbon/react

Version:

React components for the Carbon Design System

421 lines (419 loc) 19.3 kB
/** * 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;