UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

529 lines (523 loc) 18.1 kB
/** * MSKCC 2021, 2024 */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import React__default, { useState, useRef, useEffect } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { CheckboxCheckedFilled, Checkbox, ChevronDown, ArrowRight, Error } from '@carbon/icons-react'; import Link from '../Link/Link.js'; import deprecate from '../../prop-types/deprecate.js'; import { composeEventHandlers } from '../../tools/events.js'; import { usePrefix } from '../../internal/usePrefix.js'; import useIsomorphicEffect from '../../internal/useIsomorphicEffect.js'; import { getInteractiveContent } from '../../internal/useNoInteractiveChildren.js'; import { useMergedRefs } from '../../internal/useMergedRefs.js'; import { useFeatureFlag } from '../FeatureFlags/index.js'; import { matches } from '../../internal/keyboard/match.js'; import { Enter, Space } from '../../internal/keyboard/keys.js'; var _CheckboxCheckedFille, _Checkbox, _ChevronDown, _ChevronDown2; const Tile = /*#__PURE__*/React__default.forwardRef(function Tile(_ref, ref) { let { children, className, light = false, ...rest } = _ref; const prefix = usePrefix(); const tileClasses = cx(`${prefix}--tile`, light && `${prefix}--tile--light`, className); return /*#__PURE__*/React__default.createElement("div", _extends({ className: tileClasses, ref: ref }, rest), children); }); Tile.displayName = 'Tile'; Tile.propTypes = { /** * The child nodes. */ children: PropTypes.node, /** * The CSS class names. */ className: PropTypes.string, /** * `true` to use the light version. For use on $ui-01 backgrounds only. * Don't use this to make tile background color same as container background color. * * @deprecated */ light: deprecate(PropTypes.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.') }; const ClickableTile = /*#__PURE__*/React__default.forwardRef(function ClickableTile(_ref2, ref) { let { children, className, clicked = false, disabled, href, light, onClick = () => {}, onKeyDown = () => {}, renderIcon: Icon, ...rest } = _ref2; const prefix = usePrefix(); const classes = cx(`${prefix}--tile`, `${prefix}--tile--clickable`, clicked && `${prefix}--tile--is-clicked`, light && `${prefix}--tile--light`, className); const [isSelected, setIsSelected] = useState(clicked); function handleOnClick(evt) { evt.persist(); setIsSelected(!isSelected); onClick(evt); } function handleOnKeyDown(evt) { evt.persist(); if (matches(evt, [Enter, Space])) { evt.preventDefault(); setIsSelected(!isSelected); onKeyDown(evt); } onKeyDown(evt); } const v12DefaultIcons = useFeatureFlag('enable-v12-tile-default-icons'); if (v12DefaultIcons) { if (!Icon) { Icon = ArrowRight; } if (disabled) { Icon = Error; } } const iconClasses = cx({ [`${prefix}--tile--icon`]: !v12DefaultIcons || v12DefaultIcons && !disabled, [`${prefix}--tile--disabled-icon`]: v12DefaultIcons && disabled }); return /*#__PURE__*/React__default.createElement(Link, _extends({ className: classes, href: href, onClick: !disabled ? handleOnClick : undefined, onKeyDown: handleOnKeyDown, ref: ref, disabled: disabled }, rest), children, Icon && /*#__PURE__*/React__default.createElement(Icon, { className: iconClasses, "aria-hidden": "true" })); }); ClickableTile.displayName = 'ClickableTile'; ClickableTile.propTypes = { /** * The child nodes. */ children: PropTypes.node, /** * The CSS class names. */ className: PropTypes.string, /** * Boolean for whether a tile has been clicked. */ clicked: PropTypes.bool, /** * Specify whether the ClickableTile should be disabled */ disabled: PropTypes.bool, /** * The href for the link. */ href: PropTypes.string, /** * `true` to use the light version. For use on $ui-01 backgrounds only. * Don't use this to make tile background color same as container background color. */ light: deprecate(PropTypes.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.'), /** * Specify the function to run when the ClickableTile is clicked */ onClick: PropTypes.func, /** * Specify the function to run when the ClickableTile is interacted with via a keyboard */ onKeyDown: PropTypes.func, /** * The rel property for the link. */ rel: PropTypes.string, /** * Optional prop to allow overriding the icon rendering. * Can be a React component class */ // @ts-expect-error: Invalid derived prop type, seemingly no real solution. renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]) }; const SelectableTile = /*#__PURE__*/React__default.forwardRef(function SelectableTile(_ref3, ref) { let { children, className, disabled, id, light, onClick = () => {}, onChange = () => {}, onKeyDown = () => {}, selected = false, tabIndex = 0, title = 'title', ...rest } = _ref3; const prefix = usePrefix(); const clickHandler = onClick; const keyDownHandler = onKeyDown; const [isSelected, setIsSelected] = useState(selected); const [prevSelected, setPrevSelected] = useState(selected); const classes = cx(`${prefix}--tile`, `${prefix}--tile--selectable`, isSelected && `${prefix}--tile--is-selected`, light && `${prefix}--tile--light`, disabled && `${prefix}--tile--disabled`, className); // TODO: rename to handleClick when handleClick prop is deprecated function handleOnClick(evt) { evt.preventDefault(); evt.persist(); setIsSelected(!isSelected); clickHandler(evt); onChange(evt); } // TODO: rename to handleKeyDown when handleKeyDown prop is deprecated function handleOnKeyDown(evt) { evt.persist(); if (matches(evt, [Enter, Space])) { evt.preventDefault(); setIsSelected(!isSelected); onChange(evt); } keyDownHandler(evt); } function handleChange(event) { setIsSelected(event.target.checked); onChange(event); } if (selected !== prevSelected) { setIsSelected(selected); setPrevSelected(selected); } return ( /*#__PURE__*/ // eslint-disable-next-line jsx-a11y/interactive-supports-focus React__default.createElement("div", _extends({ className: classes, onClick: !disabled ? handleOnClick : undefined, role: "checkbox", "aria-checked": isSelected, onKeyDown: !disabled ? handleOnKeyDown : undefined // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex , tabIndex: !disabled ? tabIndex : undefined, ref: ref, id: id, onChange: !disabled ? handleChange : undefined, title: title }, rest), /*#__PURE__*/React__default.createElement("span", { className: `${prefix}--tile__checkmark ${prefix}--tile__checkmark--persistent` }, isSelected ? _CheckboxCheckedFille || (_CheckboxCheckedFille = /*#__PURE__*/React__default.createElement(CheckboxCheckedFilled, null)) : _Checkbox || (_Checkbox = /*#__PURE__*/React__default.createElement(Checkbox, null))), /*#__PURE__*/React__default.createElement("label", { htmlFor: id, className: `${prefix}--tile-content` }, children)) ); }); SelectableTile.displayName = 'SelectableTile'; SelectableTile.propTypes = { children: PropTypes.node, className: PropTypes.string, /** * Specify whether the SelectableTile should be disabled */ disabled: PropTypes.bool, /** * The ID of the `<input>`. */ id: PropTypes.string, /** * `true` to use the light version. For use on $ui-01 backgrounds only. * Don't use this to make tile background color same as container background color. */ light: deprecate(PropTypes.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.'), /** * The `name` of the `<input>`. * @deprecated */ name: PropTypes.string, /** * The empty handler of the `<input>`. */ onChange: PropTypes.func, /** * Specify the function to run when the SelectableTile is clicked */ onClick: PropTypes.func, /** * Specify the function to run when the SelectableTile is interacted with via a keyboard */ onKeyDown: PropTypes.func, /** * `true` to select this tile. */ selected: PropTypes.bool, /** * Specify the tab index of the wrapper element */ tabIndex: PropTypes.number, /** * The `title` of the `<input>`. */ title: PropTypes.string, /** * The value of the `<input>`. * @deprecated */ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired }; const ExpandableTile = /*#__PURE__*/React__default.forwardRef(function ExpandableTile(_ref4, forwardRef) { let { tabIndex = 0, className, children, expanded = false, tileMaxHeight = 0, // eslint-disable-line tilePadding = 0, // eslint-disable-line onClick, onKeyUp, tileCollapsedIconText = 'Interact to expand Tile', tileExpandedIconText = 'Interact to collapse Tile', tileCollapsedLabel, tileExpandedLabel, light, ...rest } = _ref4; const [isTileMaxHeight, setIsTileMaxHeight] = useState(tileMaxHeight); const [isTilePadding, setIsTilePadding] = useState(tilePadding); const [prevExpanded, setPrevExpanded] = useState(expanded); const [prevTileMaxHeight, setPrevTileMaxHeight] = useState(tileMaxHeight); const [prevTilePadding, setPrevTilePadding] = useState(tilePadding); const [isExpanded, setIsExpanded] = useState(expanded); const [interactive, setInteractive] = useState(true); const aboveTheFold = useRef(null); const belowTheFold = useRef(null); const tileContent = useRef(null); const tile = useRef(null); const ref = useMergedRefs([forwardRef, tile]); const prefix = usePrefix(); if (expanded !== prevExpanded) { setIsExpanded(expanded); setPrevExpanded(expanded); setMaxHeight(); } if (tileMaxHeight !== prevTileMaxHeight) { setIsTileMaxHeight(tileMaxHeight); setPrevTileMaxHeight(tileMaxHeight); } if (tilePadding !== prevTilePadding) { setIsTilePadding(tilePadding); setPrevTilePadding(tilePadding); } function setMaxHeight() { if (isExpanded && tileContent.current) { setIsTileMaxHeight(tileContent.current.getBoundingClientRect()?.height); } if (aboveTheFold.current) { setIsTileMaxHeight(aboveTheFold.current.getBoundingClientRect().height); } } function handleClick(evt) { evt.persist(); setIsExpanded(!isExpanded); setMaxHeight(); } function handleKeyUp(evt) { if (evt.target !== tile.current) { if (matches(evt, [Enter, Space])) { evt.preventDefault(); } } } function getChildren() { return React__default.Children.toArray(children); } const classNames = cx(`${prefix}--tile`, `${prefix}--tile--expandable`, { [`${prefix}--tile--is-expanded`]: isExpanded, [`${prefix}--tile--light`]: light }, className); const interactiveClassNames = cx(`${prefix}--tile`, `${prefix}--tile--expandable`, `${prefix}--tile--expandable--interactive`, isExpanded && `${prefix}--tile--is-expanded`, light && `${prefix}--tile--light`, className); const chevronInteractiveClassNames = cx(`${prefix}--tile__chevron`, `${prefix}--tile__chevron--interactive`); const childrenAsArray = getChildren(); useIsomorphicEffect(() => { if (!tile.current || !aboveTheFold.current) { return; } const getStyle = window.getComputedStyle(tile.current, null); const { current: node } = aboveTheFold; const { height } = node.getBoundingClientRect(); const paddingTop = parseInt(getStyle.getPropertyValue('padding-top'), 10); const paddingBottom = parseInt(getStyle.getPropertyValue('padding-bottom'), 10); setIsTileMaxHeight(height); setIsTilePadding(paddingTop + paddingBottom); }, [isTileMaxHeight]); useIsomorphicEffect(() => { if (!aboveTheFold.current || !belowTheFold.current) { return; } if (!getInteractiveContent(belowTheFold.current)) { setInteractive(false); return; } else if (!getInteractiveContent(aboveTheFold.current)) { setInteractive(false); } }, []); useIsomorphicEffect(() => { if (!tile.current) { return; } if (isExpanded) { tile.current.style.maxHeight = ''; } else { tile.current.style.maxHeight = isTileMaxHeight + isTilePadding + 'px'; } }, [isExpanded, isTileMaxHeight, isTilePadding]); useEffect(() => { if (!aboveTheFold.current) { return; } const resizeObserver = new ResizeObserver(entries => { const [aboveTheFold] = entries; setIsTileMaxHeight(aboveTheFold.contentRect.height); }); resizeObserver.observe(aboveTheFold.current); return () => resizeObserver.disconnect(); }, []); return interactive ? /*#__PURE__*/React__default.createElement("div", _extends({ // @ts-expect-error: Needlesly strict & deep typing for the element type ref: ref, className: interactiveClassNames, "aria-expanded": isExpanded }, rest), /*#__PURE__*/React__default.createElement("div", { ref: tileContent }, /*#__PURE__*/React__default.createElement("div", { ref: aboveTheFold, className: `${prefix}--tile-content` }, childrenAsArray[0]), /*#__PURE__*/React__default.createElement("button", { type: "button", "aria-expanded": isExpanded, onKeyUp: composeEventHandlers([onKeyUp, handleKeyUp]), onClick: composeEventHandlers([onClick, handleClick]), "aria-label": isExpanded ? tileExpandedIconText : tileCollapsedIconText, className: chevronInteractiveClassNames }, _ChevronDown || (_ChevronDown = /*#__PURE__*/React__default.createElement(ChevronDown, null))), /*#__PURE__*/React__default.createElement("div", { ref: belowTheFold, className: `${prefix}--tile-content` }, childrenAsArray[1]))) : /*#__PURE__*/React__default.createElement("button", _extends({ type: "button" // @ts-expect-error: Needlesly strict & deep typing for the element type , ref: ref, className: classNames, "aria-expanded": isExpanded, title: isExpanded ? tileExpandedIconText : tileCollapsedIconText }, rest, { onKeyUp: composeEventHandlers([onKeyUp, handleKeyUp]), onClick: composeEventHandlers([onClick, handleClick]), tabIndex: tabIndex }), /*#__PURE__*/React__default.createElement("div", { ref: tileContent }, /*#__PURE__*/React__default.createElement("div", { ref: aboveTheFold, className: `${prefix}--tile-content` }, childrenAsArray[0]), /*#__PURE__*/React__default.createElement("div", { className: `${prefix}--tile__chevron` }, /*#__PURE__*/React__default.createElement("span", null, isExpanded ? tileExpandedLabel : tileCollapsedLabel), _ChevronDown2 || (_ChevronDown2 = /*#__PURE__*/React__default.createElement(ChevronDown, null))), /*#__PURE__*/React__default.createElement("div", { ref: belowTheFold, className: `${prefix}--tile-content` }, childrenAsArray[1]))); }); ExpandableTile.propTypes = { children: PropTypes.node, className: PropTypes.string, /** * `true` if the tile is expanded. */ expanded: PropTypes.bool, /** * An ID that can be provided to aria-labelledby */ id: PropTypes.string, /** * `true` to use the light version. For use on $ui-01 backgrounds only. * Don't use this to make tile background color same as container background color. */ light: deprecate(PropTypes.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.'), /** * Specify the function to run when the ExpandableTile is clicked */ onClick: PropTypes.func, /** * optional handler to trigger a function when a key is pressed */ onKeyUp: PropTypes.func, /** * The `tabindex` attribute. */ tabIndex: PropTypes.number, /** * The description of the "collapsed" icon that can be read by screen readers. */ tileCollapsedIconText: PropTypes.string, /** * When "collapsed", a label to appear next to the chevron (e.g., "View more"). */ tileCollapsedLabel: PropTypes.string, /** * The description of the "expanded" icon that can be read by screen readers. */ tileExpandedIconText: PropTypes.string, /** * When "expanded", a label to appear next to the chevron (e.g., "View less"). */ tileExpandedLabel: PropTypes.string }; ExpandableTile.displayName = 'ExpandableTile'; const TileAboveTheFoldContent = /*#__PURE__*/React__default.forwardRef(function TilAboveTheFoldContent(_ref5, ref) { let { children } = _ref5; const prefix = usePrefix(); return /*#__PURE__*/React__default.createElement("span", { ref: ref, className: `${prefix}--tile-content__above-the-fold` }, children); }); TileAboveTheFoldContent.propTypes = { /** * The child nodes. */ children: PropTypes.node }; TileAboveTheFoldContent.displayName = 'TileAboveTheFoldContent'; const TileBelowTheFoldContent = /*#__PURE__*/React__default.forwardRef(function TileBelowTheFoldContent(_ref6, ref) { let { children } = _ref6; const prefix = usePrefix(); return /*#__PURE__*/React__default.createElement("span", { ref: ref, className: `${prefix}--tile-content__below-the-fold` }, children); }); TileBelowTheFoldContent.propTypes = { /** * The child nodes. */ children: PropTypes.node }; TileBelowTheFoldContent.displayName = 'TileBelowTheFoldContent'; export { ClickableTile, ExpandableTile, SelectableTile, Tile, TileAboveTheFoldContent, TileBelowTheFoldContent };