UNPKG

@zendeskgarden/react-dropdowns

Version:

Components related to dropdowns in the Garden Design System

171 lines (168 loc) 5.64 kB
/** * Copyright Zendesk, Inc. * * Use of this source code is governed under the Apache License, Version 2.0 * found at http://www.apache.org/licenses/LICENSE-2.0. */ import React__default, { forwardRef, useRef, useState, useContext, useEffect } from 'react'; import PropTypes from 'prop-types'; import { ThemeContext } from 'styled-components'; import { DEFAULT_THEME, getFloatingPlacements, getMenuPosition, getArrowPosition } from '@zendeskgarden/react-theming'; import { useFloating, platform, offset, autoPlacement, flip, size, autoUpdate } from '@floating-ui/react-dom'; import { PLACEMENT } from '../../types/index.js'; import '../../views/combobox/StyledCombobox.js'; import '../../views/combobox/StyledContainer.js'; import '../../views/combobox/StyledField.js'; import '../../views/combobox/StyledFloatingListbox.js'; import '../../views/combobox/StyledHint.js'; import '../../views/combobox/StyledInput.js'; import '../../views/combobox/StyledInputGroup.js'; import '../../views/combobox/StyledInputIcon.js'; import '../../views/combobox/StyledLabel.js'; import '../../views/combobox/StyledListbox.js'; import '../../views/combobox/StyledListboxSeparator.js'; import '../../views/combobox/StyledMessage.js'; import '../../views/combobox/StyledOptGroup.js'; import '../../views/combobox/StyledOption.js'; import '../../views/combobox/StyledOptionContent.js'; import '../../views/combobox/StyledOptionIcon.js'; import '../../views/combobox/StyledOptionMeta.js'; import '../../views/combobox/StyledOptionSelectionIcon.js'; import '../../views/combobox/StyledOptionTypeIcon.js'; import '../../views/combobox/StyledTag.js'; import '../../views/combobox/StyledTagsButton.js'; import '../../views/combobox/StyledTrigger.js'; import '../../views/combobox/StyledValue.js'; import { StyledMenu } from '../../views/menu/StyledMenu.js'; import { StyledFloatingMenu } from '../../views/menu/StyledFloatingMenu.js'; import '../../views/menu/StyledItem.js'; import '../../views/menu/StyledItemContent.js'; import '../../views/menu/StyledItemGroup.js'; import '../../views/menu/StyledItemIcon.js'; import '../../views/menu/StyledItemMeta.js'; import '../../views/menu/StyledItemTypeIcon.js'; import '../../views/menu/StyledSeparator.js'; import { createPortal } from 'react-dom'; const PLACEMENT_DEFAULT = 'bottom-start'; const MenuList = forwardRef((_ref, ref) => { let { appendToNode, hasArrow, isCompact, isExpanded, fallbackPlacements: _fallbackPlacements, maxHeight, minHeight, placement: _placement, triggerRef, zIndex, children, ...props } = _ref; const floatingRef = useRef(null); const [isVisible, setIsVisible] = useState(isExpanded); const [height, setHeight] = useState(); const theme = useContext(ThemeContext) || DEFAULT_THEME; const [floatingPlacement, fallbackPlacements] = getFloatingPlacements(theme, _placement === 'auto' ? PLACEMENT_DEFAULT : _placement, _fallbackPlacements); const { refs, placement, update, floatingStyles: { transform } } = useFloating({ platform: { ...platform, isRTL: () => theme.rtl }, elements: { reference: triggerRef?.current, floating: floatingRef?.current }, placement: floatingPlacement, middleware: [offset(theme.space.base * (hasArrow ? 2 : 1)), _placement === 'auto' ? autoPlacement() : flip({ fallbackPlacements }), size({ apply: _ref2 => { let { rects, availableHeight } = _ref2; if (!(minHeight === null || minHeight === 'fit-content')) { if (rects.floating.height > availableHeight) { setHeight(availableHeight); } } } })] }); useEffect(() => { let cleanup; if (isExpanded && refs.reference.current && refs.floating.current) { cleanup = autoUpdate(refs.reference.current, refs.floating.current, update, { elementResize: typeof ResizeObserver === 'function' }); } return () => cleanup && cleanup(); }, [isExpanded, refs.reference, refs.floating, update]); useEffect(() => { let timeout; if (isExpanded) { setIsVisible(true); } else { timeout = setTimeout(() => { setIsVisible(false); setHeight(undefined); }, 200 ); } return () => clearTimeout(timeout); }, [isExpanded]); useEffect(() => { if (height) { setHeight(undefined); update(); } }, [ children, update]); const Node = React__default.createElement(StyledFloatingMenu, { "data-garden-animate": isVisible, $isHidden: !isExpanded || !isVisible , $position: getMenuPosition(placement), $zIndex: zIndex, style: { transform }, ref: floatingRef }, React__default.createElement(StyledMenu, Object.assign({ "data-garden-animate-arrow": isVisible, $arrowPosition: hasArrow ? getArrowPosition(theme, placement) : undefined, $isCompact: isCompact, $minHeight: minHeight, $maxHeight: maxHeight, style: { height } }, props, { ref: ref }), !!isVisible && children)); return appendToNode ? createPortal(Node, appendToNode) : Node; }); MenuList.displayName = 'MenuList'; MenuList.propTypes = { appendToNode: PropTypes.any, hasArrow: PropTypes.bool, isCompact: PropTypes.bool, isExpanded: PropTypes.bool, maxHeight: PropTypes.string, minHeight: PropTypes.string, placement: PropTypes.oneOf(PLACEMENT), triggerRef: PropTypes.any, zIndex: PropTypes.number }; MenuList.defaultProps = { maxHeight: '400px', placement: PLACEMENT_DEFAULT, zIndex: 1000 }; export { MenuList };