@carbon/react
Version:
React components for the Carbon Design System
204 lines (192 loc) • 7.91 kB
JavaScript
/**
* 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.
*/
;
Object.defineProperty(exports, '__esModule', { value: true });
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');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx);
const defaultSize = 'md';
const OverflowMenu = /*#__PURE__*/React__default["default"].forwardRef(function OverflowMenu({
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__default["default"](className, `${prefix}--overflow-menu__container`, {
[`${prefix}--autoalign`]: enableFloatingStyles
});
const menuClasses = cx__default["default"](`${prefix}--overflow-menu__${menuAlignment}`);
const triggerClasses = cx__default["default"](`${prefix}--overflow-menu`, {
[`${prefix}--overflow-menu--open`]: open
}, size !== defaultSize && `${prefix}--overflow-menu--${size}`);
const floatingRef = mergeRefs["default"](triggerRef, refs.setReference);
return /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({}, rest, {
className: containerClasses,
"aria-owns": open ? id : undefined,
ref: forwardRef
}), /*#__PURE__*/React__default["default"].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__default["default"].createElement(IconElement, {
className: `${prefix}--overflow-menu__icon`
})), /*#__PURE__*/React__default["default"].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.
*/
autoAlign: PropTypes__default["default"].bool,
/**
* A collection of MenuItems to be rendered within this OverflowMenu.
*/
children: PropTypes__default["default"].node,
/**
* Additional CSS class names for the trigger button.
*/
className: PropTypes__default["default"].string,
/**
* A label describing the options available. Is used in the trigger tooltip and as the menu's accessible label.
*/
label: PropTypes__default["default"].string,
/**
* Experimental property. Specify how the menu should align with the button element
*/
menuAlignment: PropTypes__default["default"].oneOf(['top-start', 'top-end', 'bottom-start', 'bottom-end']),
/**
* A component used to render an icon.
*/
renderIcon: PropTypes__default["default"].oneOfType([PropTypes__default["default"].func, PropTypes__default["default"].object]),
/**
* Specify the size of the menu, from a list of available sizes.
*/
size: PropTypes__default["default"].oneOf(['sm', 'md', 'lg']),
/**
* Specify how the trigger tooltip should be aligned.
*/
tooltipAlignment: deprecateValuesWithin["default"](PropTypes__default["default"].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__default["default"].instanceOf(typeof Element !== 'undefined' ? Element : Object)
};
exports.OverflowMenu = OverflowMenu;