@carbon/ibm-products
Version:
Carbon for IBM Products
313 lines (304 loc) • 10.1 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.
*/
'use strict';
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var carbonMotion = require('@carbon/motion');
var icons = require('@carbon/react/icons');
var react = require('@carbon/react');
var React = require('react');
var utilitiesReact = require('@carbon/utilities-react');
var index = require('../../_virtual/index.js');
var cx = require('classnames');
var devtools = require('../../global/js/utils/devtools.js');
var settings = require('../../settings.js');
var uuidv4 = require('../../global/js/utils/uuidv4.js');
var useControllableState = require('../../global/js/hooks/useControllableState.js');
var usePrefersReducedMotion = require('../../global/js/hooks/usePrefersReducedMotion.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var carbonMotion__namespace = /*#__PURE__*/_interopNamespaceDefault(carbonMotion);
var _Locked;
const blockClass = `${settings.pkg.prefix}--options-tile`;
const componentName = 'OptionsTile';
// Default values for props
const defaults = {
size: 'lg'
};
exports.OptionsTile = /*#__PURE__*/React.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] = React.useState(false);
const [open, setOpen] = useControllableState.useControllableState(userOpen ?? false, onChange);
const detailsRef = React.useRef(null);
const contentRef = React.useRef(null);
const headingRef = React.useRef(null);
utilitiesReact.useNoInteractiveChildren(headingRef);
const titleId = userDefinedTitleId ?? `${uuidv4.default()}-title`;
const isExpandable = children !== undefined;
const isWarn = !invalid && warn;
const isLocked = !invalid && !isWarn && locked;
const shouldReduceMotion = usePrefersReducedMotion.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__namespace.moderate01.replace('ms', '')),
easing: carbonMotion__namespace.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__namespace.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__namespace.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 = icons.WarningFilled;
text = invalidText;
} else if (warn) {
Icon = icons.WarningAltFilled;
text = warnText;
} else if (locked) {
Icon = icons.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.createElement("div", {
className: `${blockClass}__heading`
}, /*#__PURE__*/React.createElement(react.Heading, {
ref: headingRef,
id: titleId,
className: `${blockClass}__title`
}, title), text && /*#__PURE__*/React.createElement("span", {
className: summaryClasses,
"aria-hidden": summaryHidden
}, Icon && /*#__PURE__*/React.createElement(Icon, {
size: 16
}), /*#__PURE__*/React.createElement("span", {
className: `${blockClass}__summary-text`
}, text)));
};
return /*#__PURE__*/React.createElement(react.Section, _rollupPluginBabelHelpers.extends({}, rest, {
className: cx(blockClass, className, `${blockClass}--${size}`, {
[`${blockClass}--closing`]: closing
}),
ref: ref
}, devtools.getDevtoolsProps(componentName)), enabled !== undefined && /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__toggle-container`
}, /*#__PURE__*/React.createElement(react.Toggle, {
id: `${titleId}-toggle`,
className: `${blockClass}__toggle`,
toggled: enabled,
"aria-labelledby": titleId,
hideLabel: true,
onToggle: onToggle,
size: "sm",
disabled: isLocked
})), isExpandable ? /*#__PURE__*/React.createElement("details", {
className: `${blockClass}__details`,
open: open,
ref: detailsRef
}, /*#__PURE__*/React.createElement("summary", {
className: `${blockClass}__header`,
onClick: toggle
}, /*#__PURE__*/React.createElement(icons.ChevronDown, {
size: 16,
className: cx(`${blockClass}__chevron`, {
[`${blockClass}__chevron--open`]: open,
[`${blockClass}__chevron--closing`]: closing
})
}), renderTitle()), /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__content`,
ref: contentRef
}, /*#__PURE__*/React.createElement(react.Layer, null, isLocked && /*#__PURE__*/React.createElement("p", {
className: `${blockClass}__locked-text`
}, _Locked || (_Locked = /*#__PURE__*/React.createElement(icons.Locked, {
size: 16
})), lockedText), children))) : /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__static-content`
}, renderTitle()));
});
// Return a placeholder if not released and not enabled by feature flag
exports.OptionsTile = settings.pkg.checkComponentEnabled(exports.OptionsTile, componentName);
// The display name of the component, used by React. Note that displayName
// is used in preference to relying on function.name.
exports.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.
exports.OptionsTile.propTypes = {
/**
* Provide content to render as expandable OptionsTile. If no children
* are present, the OptionsTile will render as its variant.
*/
children: index.default.node,
/**
* Provide an optional class to be applied to the containing node.
*/
className: index.default.string,
/**
* Whether the toggle is enabled or disabled. If nothing is passed,
* no toggle will be rendered.
*/
enabled: index.default.bool,
/**
* Whether the OptionsTile is in invalid validation state.
*/
invalid: index.default.bool,
/**
* Provide a text explaining why the OptionsTile is in invalid state.
*/
invalidText: index.default.string,
/**
* Whether the OptionsTile is in locked validation state.
*/
locked: index.default.bool,
/**
* Provide a text explaining why the OptionsTile is in locked state.
*/
lockedText: index.default.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: index.default.func,
/**
* Provide a function which will be called each time the user
* interacts with the toggle.
*/
onToggle: index.default.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: index.default.bool,
/**
* Define the size of the OptionsTile.
*/
size: index.default.oneOf(['lg', 'xl']),
/**
* Optionally provide a text summarizing the current state of the content.
*/
summary: index.default.string,
/**
* Provide the title for this OptionsTile. Must not contain any interactive elements.
*/
title: index.default.oneOfType([index.default.string, index.default.node]).isRequired,
/**
* Optionally provide an id which should be used for the title.
*/
titleId: index.default.string,
/**
* Whether the OptionsTile is in warning validation state.
*/
warn: index.default.bool,
/**
* Provide a text explaining why the OptionsTile is in warning state.
*/
warnText: index.default.string
};