import React , { useState , useContext , useEffect , useCallback , useRef , } from "react" ; import Flex from "./Flex" ; import ReactDOM from "react-dom" ; import styled , { css } from "styled-components/macro" ; const mql = window . matchMedia ( `(min-width: 800px)` ) ; type TReactDispatch = React . DispatchWithoutAction ; interface IDropdownContext { toggle : TReactDispatch ; close : TReactDispatch ; isDropdownOpen : boolean ; } const DropdownContext = React . createContext < IDropdownContext > ( { toggle : ( ) => { } , close : ( ) => { } , isDropdownOpen : false , } ) ; interface DropdownProps { onChange ? : ( toggle : boolean ) => void ; shouldCloseOnClick ? : boolean ; } type ToggleFuncProps = ( toggle : TReactDispatch , close : TReactDispatch ) => { } ; interface DropdownToggleProps extends React . HTMLAttributes < HTMLDivElement > { } interface DropdownContentProps extends React . HTMLAttributes < HTMLDivElement > { } type StaticComponents = { Toggle : React . FC < DropdownToggleProps > ; Content : React . FC < DropdownContentProps > ; Item : React . FC ; } ; export const Dropdown = ( { children , onChange , shouldCloseOnClick , ... props } ) => { const id = useRef < HTMLDivElement | null > ( null ) ; const [ isDropdownOpen , setDropdown ] = useState < boolean > ( false ) ; const toggle = ( ) => { setDropdown ( ! isDropdownOpen ) ; } ; const close = ( ) => { setDropdown ( false ) ; } ; const closeDropdown = useCallback ( e => { if ( e . target . closest ( ".dropdown--content" ) && shouldCloseOnClick ) { close ( ) ; return ; } if ( e . target . closest ( ".dropdown" ) !== id . current ) { close ( ) ; return ; } } , [ shouldCloseOnClick ] ) ; useEffect ( ( ) => { onChange && onChange ( isDropdownOpen ) ; } , [ isDropdownOpen , onChange ] ) ; useEffect ( ( ) => { window . addEventListener ( "click" , closeDropdown ) ; return ( ) => window . removeEventListener ( "click" , closeDropdown ) ; } , [ closeDropdown ] ) ; const value = React . useMemo ( ( ) => ( { isDropdownOpen , toggle , close } ) , [ isDropdownOpen , toggle , close , ] ) ; return ( < div className = "dropdown__container" { ... props } > < DropdownContext . Provider value = { value } > < div ref = { id } className = { `dropdown` } style = { { position : "relative" } } > { children } < /div> < /DropdownContext.Provider> < /div>); } ; const Toggle = ( { children } ) => { const { toggle , close } = useContext ( DropdownContext ) ; return ( < div data - testid = "dropdown-toggle" > { React . isValidElement ( children ) ? React . cloneElement ( children , { onClick : toggle } ) : ( children as ToggleFuncProps ) ( toggle , close ) } < /div>); } ; Dropdown . Toggle = React . memo ( Toggle ) ; const Content = ( { children , ... props } ) => { const { isDropdownOpen } = useContext ( DropdownContext ) ; const isMobile = ! mql . matches ; useEffect ( ( ) => { if ( isMobile && isDropdownOpen ) { document . body . classList . add ( "bodyOverlayBlur" ) ; } return ( ) => document . body . classList . remove ( "bodyOverlayBlur" ) ; } , [ isDropdownOpen ] ) ; const RenderContent = ( < StyledDropdownContent data - testid = "dropdown-content" className = "dropdown--content" isOpen = { isDropdownOpen } { ... props } > < Flex direction = "column" > { isDropdownOpen && children } < /Flex> < /StyledDropdownContent>); return isMobile ? ReactDOM . createPortal ( RenderContent , document . body ) : RenderContent ; } ; Dropdown . Content = React . memo ( Content ) ; const Item = ( { children } ) => { return < div className = "dropdown__item" > { children } < /div>; } ; Dropdown . Item = Item ; const StyledDropdownContent = styled . div < { isOpen ? : boolean ; } > `
  width: max-content;
  padding: 15px;
  border-radius: 5px;
  position: absolute;
  right: 0;
  margin-top: 5px;
  background-color: ${ p => p . theme . colors . dark1 };
  opacity: 0;
  pointer-events: none;
  transform: translateY(-25px);
  transition: 0.2s;
  z-index: 1;
  ${ p => p . isOpen && css `
      opacity: 1;
      pointer-events: all;
      transform: translateY(5px);
    ` }

  .dropdown__item {
    width: 100%;
    button {
      width: 100%;
    }
  }
  @media screen and (max-width: 400px) {
    .dropdown__item {
      button {
        padding: 15px 10px;
      }
    }
  }

  @media (${ p => p . theme . media . tablet }) {
    position: fixed;
    left: 0;
    top: var(--app-height);
    width: 100vw;
    height: auto;
    margin: 0;
    padding: 20px;
    border-radius: 0;
    transform: ${ p => ( p . isOpen ? "translateY(-100%)" : "translateY(0%)" ) };
  }
` ;