UNPKG

@carbon/react

Version:

React components for the Carbon Design System

222 lines (212 loc) 7.42 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. */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import React, { useRef, useLayoutEffect } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { ChevronDown } from '@carbon/icons-react'; import { IconButton } from '../IconButton/index.js'; import Button from '../Button/Button.js'; import '../Button/Button.Skeleton.js'; import { Menu } from '../Menu/Menu.js'; import '../Menu/MenuItem.js'; import { useAttachedMenu } from '../../internal/useAttachedMenu.js'; import { useId } from '../../internal/useId.js'; import { usePrefix } from '../../internal/usePrefix.js'; import { flip, hide, size, useFloating, autoUpdate } from '@floating-ui/react'; import { useFeatureFlag } from '../FeatureFlags/index.js'; import mergeRefs from '../../tools/mergeRefs.js'; import deprecateValuesWithin from '../../prop-types/deprecateValuesWithin.js'; import { mapPopoverAlign } from '../../tools/mapPopoverAlign.js'; var _ChevronDown; const defaultTranslations = { 'carbon.combo-button.additional-actions': 'Additional actions' }; /** * Message ids that will be passed to translateWithId(). */ function defaultTranslateWithId(messageId) { return defaultTranslations[messageId]; } const ComboButton = /*#__PURE__*/React.forwardRef(function ComboButton({ children, className, disabled, label, onClick, size: size$1 = 'lg', menuAlignment = 'bottom', tooltipAlignment, translateWithId: t = defaultTranslateWithId, ...rest }, forwardRef) { // feature flag utilized to separate out only the dynamic styles from @floating-ui // flag is turned on when collision detection (ie. flip, hide) logic is not desired const enableOnlyFloatingStyles = useFeatureFlag('enable-v12-dynamic-floating-styles'); const id = useId('combobutton'); const prefix = usePrefix(); const containerRef = useRef(null); let middlewares = []; if (!enableOnlyFloatingStyles) { middlewares = [flip({ crossAxis: false }), hide()]; } if (menuAlignment === 'bottom' || menuAlignment === 'top') { middlewares.push(size({ apply({ rects, elements }) { Object.assign(elements.floating.style, { width: `${rects.reference.width}px` }); } })); } const { refs, floatingStyles, placement, middlewareData } = useFloating({ 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 order matters, arrow should be last middleware: middlewares, whileElementsMounted: autoUpdate }); const ref = mergeRefs(forwardRef, containerRef, refs.setReference); const { open, handleClick: hookOnClick, handleMousedown: handleTriggerMousedown, handleClose } = useAttachedMenu(containerRef); useLayoutEffect(() => { const updatedFloatingStyles = { ...floatingStyles, visibility: middlewareData.hide?.referenceHidden ? 'hidden' : 'visible' }; Object.keys(updatedFloatingStyles).forEach(style => { if (refs.floating.current) { refs.floating.current.style[style] = updatedFloatingStyles[style]; } }); }, [floatingStyles, refs.floating, middlewareData, placement, open]); function handleTriggerClick() { if (containerRef.current) { hookOnClick(); } } function handlePrimaryActionClick(e) { if (onClick) { onClick(e); } } const containerClasses = cx(`${prefix}--combo-button__container`, `${prefix}--combo-button__container--${size$1}`, { [`${prefix}--combo-button__container--open`]: open }, className); const menuClasses = cx(`${prefix}--combo-button__${menuAlignment}`); const primaryActionClasses = cx(`${prefix}--combo-button__primary-action`); const triggerClasses = cx(`${prefix}--combo-button__trigger`); return /*#__PURE__*/React.createElement("div", _extends({}, rest, { className: containerClasses, ref: ref, "aria-owns": open ? id : undefined }), /*#__PURE__*/React.createElement("div", { className: primaryActionClasses }, /*#__PURE__*/React.createElement(Button, { title: label, size: size$1, disabled: disabled, onClick: handlePrimaryActionClick }, label)), /*#__PURE__*/React.createElement(IconButton, { ref: refs.setReference, className: triggerClasses, label: t('carbon.combo-button.additional-actions'), size: size$1, disabled: disabled, align: tooltipAlignment, "aria-haspopup": true, "aria-expanded": open, onClick: handleTriggerClick, onMouseDown: handleTriggerMousedown, "aria-controls": open ? id : undefined }, _ChevronDown || (_ChevronDown = /*#__PURE__*/React.createElement(ChevronDown, null))), /*#__PURE__*/React.createElement(Menu, { containerRef: containerRef, menuAlignment: menuAlignment, className: menuClasses, ref: refs.setFloating, id: id, label: t('carbon.combo-button.additional-actions'), size: size$1, open: open, onClose: handleClose }, children)); }); ComboButton.propTypes = { /** * A collection of MenuItems to be rendered as additional actions for this ComboButton. */ children: PropTypes.node.isRequired, /** * Additional CSS class names. */ className: PropTypes.string, /** * Specify whether the ComboButton should be disabled, or not. */ disabled: PropTypes.bool, /** * Provide the label to be rendered on the primary action button. */ label: PropTypes.string.isRequired, /** * Experimental property. Specify how the menu should align with the button element */ menuAlignment: PropTypes.oneOf(['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end']), /** * Provide an optional function to be called when the primary action element is clicked. */ onClick: PropTypes.func, /** * Specify the size of the buttons and menu. */ size: PropTypes.oneOf(['sm', 'md', 'lg']), /** * Specify how the trigger tooltip should be aligned. */ tooltipAlignment: 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), /** * Optional method that takes in a message id and returns an * internationalized string. */ translateWithId: PropTypes.func }; export { ComboButton };