@carbon/ibm-products
Version:
Carbon for IBM Products
294 lines (287 loc) • 9.55 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.
*/
import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
import * as carbonMotion from '@carbon/motion';
import { ChevronDown, Locked, WarningFilled, WarningAltFilled } from '@carbon/react/icons';
import { Section, Toggle, Layer, Heading } from '@carbon/react';
import React__default, { useState, useRef } from 'react';
import { useNoInteractiveChildren } from '@carbon/utilities-react';
import PropTypes from '../../_virtual/index.js';
import cx from 'classnames';
import { getDevtoolsProps } from '../../global/js/utils/devtools.js';
import { pkg } from '../../settings.js';
import uuidv4 from '../../global/js/utils/uuidv4.js';
import { useControllableState } from '../../global/js/hooks/useControllableState.js';
import { usePrefersReducedMotion } from '../../global/js/hooks/usePrefersReducedMotion.js';
var _Locked;
const blockClass = `${pkg.prefix}--options-tile`;
const componentName = 'OptionsTile';
// Default values for props
const defaults = {
size: 'lg'
};
let OptionsTile = /*#__PURE__*/React__default.forwardRef((props, ref) => {
const {
children,
className,
enabled,
invalid,
invalidText,
locked,
lockedText,
onChange,
onToggle,
open: userOpen,
size = defaults.size,
summary,
title,
titleId: userDefinedTitleId,
warn,
warnText,
...rest
} = props;
const [closing, setClosing] = useState(false);
const [open, setOpen] = useControllableState(userOpen ?? false, onChange);
const detailsRef = useRef(null);
const contentRef = useRef(null);
const headingRef = useRef(null);
useNoInteractiveChildren(headingRef);
const titleId = userDefinedTitleId ?? `${uuidv4()}-title`;
const isExpandable = children !== undefined;
const isWarn = !invalid && warn;
const isLocked = !invalid && !isWarn && locked;
const shouldReduceMotion = usePrefersReducedMotion();
const expand = () => {
if (detailsRef.current && contentRef.current && !shouldReduceMotion) {
setOpen(true);
detailsRef.current.open = true;
const {
paddingTop,
paddingBottom,
height
} = getComputedStyle(contentRef.current);
contentRef.current.animate([{
paddingTop: 0,
paddingBottom: 0,
height: 0,
opacity: 0,
overflow: 'hidden'
}, {
paddingTop,
paddingBottom,
height,
opacity: 1,
overflow: 'hidden'
}], {
duration: Number(carbonMotion.moderate01.replace('ms', '')),
easing: carbonMotion.easings.entrance.productive
});
} else {
setOpen(true);
}
};
const collapse = () => {
if (contentRef.current && !shouldReduceMotion) {
setClosing(true);
const {
paddingTop,
paddingBottom,
height
} = getComputedStyle(contentRef.current);
const animationDuration = Number(carbonMotion.moderate01.replace('ms', ''));
const animation = contentRef.current.animate([{
paddingTop,
paddingBottom,
height,
opacity: 1
}, {
paddingTop: 0,
paddingBottom: 0,
height: 0,
opacity: 0
}], {
duration: animationDuration,
easing: carbonMotion.easings.entrance.productive
});
const callback = () => {
setOpen(false);
setClosing(false);
};
setTimeout(() => {
callback();
}, animationDuration * 0.9);
animation.oncancel = callback;
} else {
setOpen(false);
}
};
const toggle = evt => {
evt.preventDefault();
if (open) {
collapse();
} else {
expand();
}
};
const renderTitle = () => {
let Icon = null;
let text = summary;
if (invalid) {
Icon = WarningFilled;
text = invalidText;
} else if (warn) {
Icon = WarningAltFilled;
text = warnText;
} else if (locked) {
Icon = Locked;
if (!text) {
text = lockedText;
}
}
const hasValidationState = invalid || warn || locked;
const summaryHidden = !hasValidationState && enabled === false;
const summaryClasses = cx(`${blockClass}__summary`, {
[`${blockClass}__summary--closing`]: closing,
[`${blockClass}__summary--open`]: open,
[`${blockClass}__summary--invalid`]: invalid,
[`${blockClass}__summary--warn`]: warn,
[`${blockClass}__summary--locked`]: locked,
[`${blockClass}__summary--hidden`]: summaryHidden
});
return /*#__PURE__*/React__default.createElement("div", {
className: `${blockClass}__heading`
}, /*#__PURE__*/React__default.createElement(Heading, {
ref: headingRef,
id: titleId,
className: `${blockClass}__title`
}, title), text && /*#__PURE__*/React__default.createElement("span", {
className: summaryClasses,
"aria-hidden": summaryHidden
}, Icon && /*#__PURE__*/React__default.createElement(Icon, {
size: 16
}), /*#__PURE__*/React__default.createElement("span", {
className: `${blockClass}__summary-text`
}, text)));
};
return /*#__PURE__*/React__default.createElement(Section, _extends({}, rest, {
className: cx(blockClass, className, `${blockClass}--${size}`, {
[`${blockClass}--closing`]: closing
}),
ref: ref
}, getDevtoolsProps(componentName)), enabled !== undefined && /*#__PURE__*/React__default.createElement("div", {
className: `${blockClass}__toggle-container`
}, /*#__PURE__*/React__default.createElement(Toggle, {
id: `${titleId}-toggle`,
className: `${blockClass}__toggle`,
toggled: enabled,
"aria-labelledby": titleId,
hideLabel: true,
onToggle: onToggle,
size: "sm",
disabled: isLocked
})), isExpandable ? /*#__PURE__*/React__default.createElement("details", {
className: `${blockClass}__details`,
open: open,
ref: detailsRef
}, /*#__PURE__*/React__default.createElement("summary", {
className: `${blockClass}__header`,
onClick: toggle
}, /*#__PURE__*/React__default.createElement(ChevronDown, {
size: 16,
className: cx(`${blockClass}__chevron`, {
[`${blockClass}__chevron--open`]: open,
[`${blockClass}__chevron--closing`]: closing
})
}), renderTitle()), /*#__PURE__*/React__default.createElement("div", {
className: `${blockClass}__content`,
ref: contentRef
}, /*#__PURE__*/React__default.createElement(Layer, null, isLocked && /*#__PURE__*/React__default.createElement("p", {
className: `${blockClass}__locked-text`
}, _Locked || (_Locked = /*#__PURE__*/React__default.createElement(Locked, {
size: 16
})), lockedText), children))) : /*#__PURE__*/React__default.createElement("div", {
className: `${blockClass}__static-content`
}, renderTitle()));
});
// Return a placeholder if not released and not enabled by feature flag
OptionsTile = pkg.checkComponentEnabled(OptionsTile, componentName);
// The display name of the component, used by React. Note that displayName
// is used in preference to relying on function.name.
OptionsTile.displayName = componentName;
// The types and DocGen commentary for the component props,
// in alphabetical order (for consistency).
// See https://www.npmjs.com/package/prop-types#usage.
OptionsTile.propTypes = {
/**
* Provide content to render as expandable OptionsTile. If no children
* are present, the OptionsTile will render as its variant.
*/
children: PropTypes.node,
/**
* Provide an optional class to be applied to the containing node.
*/
className: PropTypes.string,
/**
* Whether the toggle is enabled or disabled. If nothing is passed,
* no toggle will be rendered.
*/
enabled: PropTypes.bool,
/**
* Whether the OptionsTile is in invalid validation state.
*/
invalid: PropTypes.bool,
/**
* Provide a text explaining why the OptionsTile is in invalid state.
*/
invalidText: PropTypes.string,
/**
* Whether the OptionsTile is in locked validation state.
*/
locked: PropTypes.bool,
/**
* Provide a text explaining why the OptionsTile is in locked state.
*/
lockedText: PropTypes.string,
/**
* A handler for managing the controlled state of open prop. If not passed the open prop will not be honored and an uncontrolled state will be used.
*/
onChange: PropTypes.func,
/**
* Provide a function which will be called each time the user
* interacts with the toggle.
*/
onToggle: PropTypes.func,
/**
* For controlled usage of the tile open state. This prop only works when an onChange prop is also passed, otherwise an uncontrolled state is used.
*/
open: PropTypes.bool,
/**
* Define the size of the OptionsTile.
*/
size: PropTypes.oneOf(['lg', 'xl']),
/**
* Optionally provide a text summarizing the current state of the content.
*/
summary: PropTypes.string,
/**
* Provide the title for this OptionsTile. Must not contain any interactive elements.
*/
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
/**
* Optionally provide an id which should be used for the title.
*/
titleId: PropTypes.string,
/**
* Whether the OptionsTile is in warning validation state.
*/
warn: PropTypes.bool,
/**
* Provide a text explaining why the OptionsTile is in warning state.
*/
warnText: PropTypes.string
};
export { OptionsTile };