|  | 
|  | 1 | +import clsx from 'clsx'; | 
|  | 2 | +import React, { useEffect, useCallback, useState, useMemo } from 'react'; | 
|  | 3 | +import type { DropdownProps } from './types'; | 
|  | 4 | +import { DropdownContext } from './context'; | 
|  | 5 | +import { usePopper } from 'react-popper'; | 
|  | 6 | + | 
|  | 7 | +const MDBDropdown: React.FC<DropdownProps> = ({ | 
|  | 8 | +  className, | 
|  | 9 | +  tag: Tag, | 
|  | 10 | +  group, | 
|  | 11 | +  isOpen, | 
|  | 12 | +  children, | 
|  | 13 | +  dropup, | 
|  | 14 | +  dropright, | 
|  | 15 | +  dropleft, | 
|  | 16 | +  options, | 
|  | 17 | +  animation, | 
|  | 18 | +  placement, | 
|  | 19 | +  ...props | 
|  | 20 | +}): JSX.Element => { | 
|  | 21 | +  const [isOpenState, setIsOpenState] = useState<undefined | boolean | any>(isOpen); | 
|  | 22 | +  const [isFadeIn, setIsFadeIn] = useState(false); | 
|  | 23 | +  const [isFadeOut, setIsFadeOut] = useState(false); | 
|  | 24 | +  const [referenceElement, setReferenceElement] = useState<HTMLElement>(); | 
|  | 25 | +  const [popperElement, setPopperElement] = useState<HTMLElement>(); | 
|  | 26 | +  const [isPlacement, setIsPlacement] = useState(placement); | 
|  | 27 | +  // eslint-disable-next-line prefer-const | 
|  | 28 | +  let [activeIndex, setActiveIndex] = useState<number>(-1); | 
|  | 29 | +  const [countLength, setCountLength] = useState<number>(-1); | 
|  | 30 | + | 
|  | 31 | +  useEffect(() => { | 
|  | 32 | +    if (dropup) { | 
|  | 33 | +      setIsPlacement('top-start'); | 
|  | 34 | +    } else if (dropright) { | 
|  | 35 | +      setIsPlacement('right-start'); | 
|  | 36 | +    } else if (dropleft) { | 
|  | 37 | +      setIsPlacement('left-start'); | 
|  | 38 | +    } else { | 
|  | 39 | +      setIsPlacement('bottom-start'); | 
|  | 40 | +    } | 
|  | 41 | +  }, [dropleft, dropright, dropup]); | 
|  | 42 | + | 
|  | 43 | +  const { styles, attributes } = usePopper(referenceElement, popperElement, { | 
|  | 44 | +    placement: isPlacement, | 
|  | 45 | +    ...options, | 
|  | 46 | +  }); | 
|  | 47 | + | 
|  | 48 | +  const classes = clsx( | 
|  | 49 | +    group ? 'btn-group' : 'dropdown', | 
|  | 50 | +    dropup && 'dropup', | 
|  | 51 | +    dropright && 'dropend', | 
|  | 52 | +    dropleft && 'dropstart', | 
|  | 53 | +    className | 
|  | 54 | +  ); | 
|  | 55 | + | 
|  | 56 | +  const handleOpenClose = (): void => setIsOpenState(!isOpenState); | 
|  | 57 | +  const handleClose = (): void => setIsOpenState(false); | 
|  | 58 | +  const getCounter = (el: any): void => setCountLength(el); | 
|  | 59 | + | 
|  | 60 | +  const handleClickOutside = useCallback( | 
|  | 61 | +    (event: MouseEvent) => { | 
|  | 62 | +      if (popperElement && popperElement !== null && isOpenState && referenceElement && referenceElement !== null) { | 
|  | 63 | +        if (!popperElement.contains(event.target as Node) && !referenceElement.contains(event.target as Node)) { | 
|  | 64 | +          setIsOpenState(false); | 
|  | 65 | +        } | 
|  | 66 | +      } | 
|  | 67 | +    }, | 
|  | 68 | +    [isOpenState, popperElement, referenceElement] | 
|  | 69 | +  ); | 
|  | 70 | + | 
|  | 71 | +  useEffect(() => { | 
|  | 72 | +    document.addEventListener('mousedown', handleClickOutside); | 
|  | 73 | +    return () => { | 
|  | 74 | +      document.removeEventListener('mousedown', handleClickOutside); | 
|  | 75 | +    }; | 
|  | 76 | +  }, [handleClickOutside]); | 
|  | 77 | + | 
|  | 78 | +  useEffect(() => { | 
|  | 79 | +    if (isOpenState) { | 
|  | 80 | +      setActiveIndex(countLength); | 
|  | 81 | +    } | 
|  | 82 | +  }, [countLength, isOpenState]); | 
|  | 83 | + | 
|  | 84 | +  useMemo(() => { | 
|  | 85 | +    let timer: ReturnType<typeof setTimeout>; | 
|  | 86 | +    let secondTimer: ReturnType<typeof setTimeout>; | 
|  | 87 | + | 
|  | 88 | +    if (isOpenState) { | 
|  | 89 | +      setIsFadeIn(true); | 
|  | 90 | + | 
|  | 91 | +      timer = setTimeout(() => { | 
|  | 92 | +        setIsFadeIn(false); | 
|  | 93 | +      }, 300); | 
|  | 94 | +    } else { | 
|  | 95 | +      setIsFadeOut(true); | 
|  | 96 | + | 
|  | 97 | +      secondTimer = setTimeout(() => { | 
|  | 98 | +        setIsFadeOut(false); | 
|  | 99 | +      }, 300); | 
|  | 100 | +    } | 
|  | 101 | +    return () => { | 
|  | 102 | +      clearTimeout(timer); | 
|  | 103 | +      clearTimeout(secondTimer); | 
|  | 104 | +    }; | 
|  | 105 | +  }, [isOpenState]); | 
|  | 106 | + | 
|  | 107 | +  return ( | 
|  | 108 | +    <DropdownContext.Provider | 
|  | 109 | +      value={{ | 
|  | 110 | +        animation, | 
|  | 111 | +        activeIndex, | 
|  | 112 | +        handleClose, | 
|  | 113 | +        handleOpenClose, | 
|  | 114 | +        isOpenState, | 
|  | 115 | +        setReferenceElement, | 
|  | 116 | +        setPopperElement, | 
|  | 117 | +        styles, | 
|  | 118 | +        attributes, | 
|  | 119 | +        animatedFadeIn: isFadeIn, | 
|  | 120 | +        animatedFadeOut: isFadeOut, | 
|  | 121 | +        getCount: (el) => getCounter(el), | 
|  | 122 | +      }} | 
|  | 123 | +    > | 
|  | 124 | +      <Tag className={classes} {...props}> | 
|  | 125 | +        {children} | 
|  | 126 | +      </Tag> | 
|  | 127 | +    </DropdownContext.Provider> | 
|  | 128 | +  ); | 
|  | 129 | +}; | 
|  | 130 | + | 
|  | 131 | +MDBDropdown.defaultProps = { | 
|  | 132 | +  tag: 'div', | 
|  | 133 | +  animation: true, | 
|  | 134 | +}; | 
|  | 135 | + | 
|  | 136 | +export default MDBDropdown; | 
0 commit comments