UNPKG

@carbon/react

Version:

React components for the Carbon Design System

200 lines (191 loc) 7.36 kB
/** * Copyright IBM Corp. 2016, 2023 * * 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 React = require('react'); var PropTypes = require('prop-types'); var cx = require('classnames'); var iconsReact = require('@carbon/icons-react'); var react = require('@floating-ui/react'); var index = require('../../FeatureFlags/index.js'); var index$1 = require('../../IconButton/index.js'); var Menu = require('../../Menu/Menu.js'); require('../../Menu/MenuItem.js'); var mergeRefs = require('../../../tools/mergeRefs.js'); var useId = require('../../../internal/useId.js'); var usePrefix = require('../../../internal/usePrefix.js'); var useAttachedMenu = require('../../../internal/useAttachedMenu.js'); var deprecateValuesWithin = require('../../../prop-types/deprecateValuesWithin.js'); var mapPopoverAlign = require('../../../tools/mapPopoverAlign.js'); const defaultSize = 'md'; // eslint-disable-next-line react/display-name -- https://github.com/carbon-design-system/carbon/issues/20452 const OverflowMenu = /*#__PURE__*/React.forwardRef(({ autoAlign = false, children, className, label = 'Options', renderIcon: IconElement = iconsReact.OverflowMenuVertical, size = defaultSize, menuAlignment = 'bottom-start', tooltipAlignment, menuTarget, ...rest }, forwardRef) => { const enableFloatingStyles = index.useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign; const { refs, floatingStyles, placement, middlewareData } = react.useFloating(enableFloatingStyles ? { // Computing the position starts with initial positioning // via `placement`. placement: menuAlignment, // The floating element is positioned relative to its nearest // containing block (usually the viewport). It will in many cases // also “break” the floating element out of a clipping ancestor. // https://floating-ui.com/docs/misc#clipping strategy: 'fixed', // Middleware are executed as an in-between “middle” step of the // initial `placement` computation and eventual return of data for // rendering. Each middleware is executed in order. middleware: [autoAlign && react.flip({ // An explicit array of placements to try if the initial // `placement` doesn’t fit on the axes in which overflow // is checked. fallbackPlacements: menuAlignment.includes('bottom') ? ['bottom-start', 'bottom-end', 'top-start', 'top-end'] : ['top-start', 'top-end', 'bottom-start', 'bottom-end'] })], whileElementsMounted: react.autoUpdate } : {} // When autoAlign is turned off & the `enable-v12-dynamic-floating-styles` feature flag is not // enabled, floating-ui will not be used ); const id = useId.useId('overflowmenu'); const prefix = usePrefix.usePrefix(); const triggerRef = React.useRef(null); const { open, x, y, handleClick: hookOnClick, handleMousedown, handleClose } = useAttachedMenu.useAttachedMenu(triggerRef); React.useEffect(() => { if (enableFloatingStyles) { Object.keys(floatingStyles).forEach(style => { if (refs.floating.current) { refs.floating.current.style[style] = floatingStyles[style]; } }); } }, [floatingStyles, enableFloatingStyles, refs.floating, open, placement, middlewareData]); function handleTriggerClick() { if (triggerRef.current) { hookOnClick(); } } const containerClasses = cx(className, `${prefix}--overflow-menu__container`, { [`${prefix}--autoalign`]: enableFloatingStyles }); const menuClasses = cx(`${prefix}--overflow-menu__${menuAlignment}`); const triggerClasses = cx(`${prefix}--overflow-menu`, { [`${prefix}--overflow-menu--open`]: open }, size !== defaultSize && `${prefix}--overflow-menu--${size}`); const floatingRef = mergeRefs.mergeRefs(triggerRef, refs.setReference); return /*#__PURE__*/React.createElement("div", _rollupPluginBabelHelpers.extends({}, rest, { className: containerClasses, "aria-owns": open ? id : undefined, ref: forwardRef }), /*#__PURE__*/React.createElement(index$1.IconButton, { "aria-controls": open ? id : undefined, "aria-haspopup": true, "aria-expanded": open, className: triggerClasses, onClick: handleTriggerClick, onMouseDown: handleMousedown, ref: floatingRef, label: label, align: tooltipAlignment, kind: "ghost" }, /*#__PURE__*/React.createElement(IconElement, { className: `${prefix}--overflow-menu__icon` })), /*#__PURE__*/React.createElement(Menu.Menu, { containerRef: triggerRef, ref: refs.setFloating, menuAlignment: menuAlignment, className: menuClasses, id: id, size: size, legacyAutoalign: !enableFloatingStyles, open: open, onClose: handleClose, x: x, y: y, label: label, target: menuTarget }, children)); }); OverflowMenu.propTypes = { /** * **Experimental**: Will attempt to automatically align the floating element * to avoid collisions with the viewport and being clipped by ancestor * elements. Requires React v17+ * @see https://github.com/carbon-design-system/carbon/issues/18714 */ autoAlign: PropTypes.bool, /** * A collection of MenuItems to be rendered within this OverflowMenu. */ children: PropTypes.node, /** * Additional CSS class names for the trigger button. */ className: PropTypes.string, /** * A label describing the options available. Is used in the trigger tooltip and as the menu's accessible label. */ label: PropTypes.string, /** * Experimental property. Specify how the menu should align with the button element */ menuAlignment: PropTypes.oneOf(['top-start', 'top-end', 'bottom-start', 'bottom-end']), /** * A component used to render an icon. */ renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), /** * Specify the size of the menu, from a list of available sizes. */ size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']), /** * Specify how the trigger tooltip should be aligned. */ tooltipAlignment: deprecateValuesWithin.deprecateValuesWithin(PropTypes.oneOf(['top', 'top-left', // deprecated use top-start instead 'top-right', // deprecated use top-end instead 'bottom', 'bottom-left', // deprecated use bottom-start instead 'bottom-right', // deprecated use bottom-end instead 'left', 'left-bottom', // deprecated use left-end instead 'left-top', // deprecated use left-start instead 'right', 'right-bottom', // deprecated use right-end instead 'right-top', // deprecated use right-start instead // new values to match floating-ui 'top-start', 'top-end', 'bottom-start', 'bottom-end', 'left-end', 'left-start', 'right-end', 'right-start']), ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end'], mapPopoverAlign.mapPopoverAlign), /** * Specify a DOM node where the Menu should be rendered in. Defaults to document.body. */ menuTarget: PropTypes.instanceOf(typeof Element !== 'undefined' ? Element : Object) }; exports.OverflowMenu = OverflowMenu;