/* eslint-disable react-hooks/rules-of-hooks */ import { consoleWarn, getClassNames } from '@websolutespa/bom-core'; import { useClickAnyWhere, useDomObserver, usePortal, useResize } from '@websolutespa/bom-mixer-hooks'; import React, { MutableRefObject, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import styled from 'styled-components'; import { Transition } from '../transition/transition'; import { getRefRect } from './dropdown-layout'; type Props = { parent?: MutableRefObject | undefined; visible: boolean; disableMatchWidth?: boolean; getPopupContainer?: () => HTMLElement | null; }; type ReactiveDomReact = { top: number; left: number; right: number; width: number; }; const defaultRect: ReactiveDomReact = { top: -1000, left: -1000, right: -1000, width: 0, }; const StyledDropdown = styled.div<{ rect: ReactiveDomReact }>` position: absolute; top: ${props => props.rect.top + 2}px; left: ${props => props.rect.left}px; z-index: 1100; &.width-match { width: ${props => props.rect.width}px; } &.disable-match { min-width: ${props => props.rect.width}px; } `; export const Dropdown: React.FC> = React.memo(({ children, parent, visible, disableMatchWidth, getPopupContainer, }) => { const portal = usePortal('dropdown', getPopupContainer); const [rect, setRect] = useState(defaultRect); if (!parent) { return null; } /* istanbul ignore next */ if (process.env.NODE_ENV !== 'production') { if (getPopupContainer && getPopupContainer()) { const element = getPopupContainer(); const style = window.getComputedStyle(element as HTMLDivElement); if (style.position === 'static') { consoleWarn('The element specified by "getPopupContainer" must have "position" set.'); } } } const updateRect = () => { const { top, left, right, width: nativeWidth } = getRefRect(parent, getPopupContainer); setRect({ top, left, right, width: nativeWidth }); }; useResize(updateRect); useClickAnyWhere(() => { const { top, left } = getRefRect(parent, getPopupContainer); const shouldUpdatePosition = top !== rect.top || left !== rect.left; if (!shouldUpdatePosition) { return; } updateRect(); }); useDomObserver(parent, () => { updateRect(); }); useEffect(() => { if (!parent || !parent.current) { return; } const element = parent.current; element.addEventListener('mouseenter', updateRect); return () => { element.removeEventListener('mouseenter', updateRect); }; }, [parent]); const clickHandler = (event: React.MouseEvent) => { event.stopPropagation(); event.nativeEvent.stopImmediatePropagation(); event.preventDefault(); }; const mouseDownHandler = (event: React.MouseEvent) => { event.preventDefault(); }; if (!portal) { return null; } const classNames = getClassNames('dropdown', disableMatchWidth ? 'disable-match' : 'width-match'); return createPortal( {children} , portal ); }); Dropdown.displayName = 'Dropdown';