Adding Click Outside
This commit is contained in:
		@@ -1,9 +1,10 @@
 | 
			
		||||
import cn from 'classnames'
 | 
			
		||||
import Link from 'next/link'
 | 
			
		||||
import { FC, useState } from 'react'
 | 
			
		||||
import { useRef, FC, useState } from 'react'
 | 
			
		||||
import { useRouter } from 'next/router'
 | 
			
		||||
import s from './I18nWidget.module.css'
 | 
			
		||||
import { Cross, ChevronUp } from '@components/icons'
 | 
			
		||||
import ClickOutside from '@lib/click-outside'
 | 
			
		||||
interface LOCALE_DATA {
 | 
			
		||||
  name: string
 | 
			
		||||
  img: {
 | 
			
		||||
@@ -37,55 +38,62 @@ const I18nWidget: FC = () => {
 | 
			
		||||
    defaultLocale = 'en-US',
 | 
			
		||||
    asPath: currentPath,
 | 
			
		||||
  } = useRouter()
 | 
			
		||||
 | 
			
		||||
  const options = locales?.filter((val) => val !== locale)
 | 
			
		||||
  const currentLocale = locale || defaultLocale
 | 
			
		||||
  const ref = useRef<HTMLDivElement | null>(null)
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <nav className={s.root}>
 | 
			
		||||
      <div className="flex items-center relative">
 | 
			
		||||
        <button className={s.button} aria-label="Language selector">
 | 
			
		||||
          <img
 | 
			
		||||
            className="block mr-2 w-5"
 | 
			
		||||
            src={`/${LOCALES_MAP[currentLocale].img.filename}`}
 | 
			
		||||
            alt={LOCALES_MAP[currentLocale].img.alt}
 | 
			
		||||
          />
 | 
			
		||||
          {options && (
 | 
			
		||||
            <span
 | 
			
		||||
              className="cursor-pointer"
 | 
			
		||||
              onClick={() => setDisplay(!display)}
 | 
			
		||||
            >
 | 
			
		||||
              <ChevronUp className={cn({ [s.icon]: display })} />
 | 
			
		||||
            </span>
 | 
			
		||||
          )}
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="absolute top-0 right-0">
 | 
			
		||||
        {options?.length && display ? (
 | 
			
		||||
          <div className={s.dropdownMenu}>
 | 
			
		||||
            <div className="flex flex-row justify-end px-6">
 | 
			
		||||
              <button
 | 
			
		||||
                onClick={() => setDisplay(false)}
 | 
			
		||||
                aria-label="Close panel"
 | 
			
		||||
                className={s.closeButton}
 | 
			
		||||
              >
 | 
			
		||||
                <Cross className="h-6 w-6" />
 | 
			
		||||
              </button>
 | 
			
		||||
    <ClickOutside active={display} onClick={() => setDisplay(false)} ref={ref}>
 | 
			
		||||
      <nav className={s.root}>
 | 
			
		||||
        <div
 | 
			
		||||
          className="flex items-center relative"
 | 
			
		||||
          onClick={() => setDisplay(!display)}
 | 
			
		||||
        >
 | 
			
		||||
          <button className={s.button} aria-label="Language selector">
 | 
			
		||||
            <img
 | 
			
		||||
              className="block mr-2 w-5"
 | 
			
		||||
              src={`/${LOCALES_MAP[currentLocale].img.filename}`}
 | 
			
		||||
              alt={LOCALES_MAP[currentLocale].img.alt}
 | 
			
		||||
            />
 | 
			
		||||
            {options && (
 | 
			
		||||
              <span className="cursor-pointer">
 | 
			
		||||
                <ChevronUp className={cn({ [s.icon]: display })} />
 | 
			
		||||
              </span>
 | 
			
		||||
            )}
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="absolute top-0 right-0">
 | 
			
		||||
          {options?.length && display ? (
 | 
			
		||||
            <div className={s.dropdownMenu}>
 | 
			
		||||
              <div className="flex flex-row justify-end px-6">
 | 
			
		||||
                <button
 | 
			
		||||
                  onClick={() => setDisplay(false)}
 | 
			
		||||
                  aria-label="Close panel"
 | 
			
		||||
                  className={s.closeButton}
 | 
			
		||||
                >
 | 
			
		||||
                  <Cross className="h-6 w-6" />
 | 
			
		||||
                </button>
 | 
			
		||||
              </div>
 | 
			
		||||
              <ul>
 | 
			
		||||
                {options.map((locale) => (
 | 
			
		||||
                  <li key={locale}>
 | 
			
		||||
                    <Link href={currentPath} locale={locale}>
 | 
			
		||||
                      <a
 | 
			
		||||
                        className={cn(s.item)}
 | 
			
		||||
                        onClick={() => setDisplay(false)}
 | 
			
		||||
                      >
 | 
			
		||||
                        {LOCALES_MAP[locale].name}
 | 
			
		||||
                      </a>
 | 
			
		||||
                    </Link>
 | 
			
		||||
                  </li>
 | 
			
		||||
                ))}
 | 
			
		||||
              </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
            <ul>
 | 
			
		||||
              {options.map((locale) => (
 | 
			
		||||
                <li key={locale}>
 | 
			
		||||
                  <Link href={currentPath} locale={locale}>
 | 
			
		||||
                    <a className={cn(s.item)} onClick={() => setDisplay(false)}>
 | 
			
		||||
                      {LOCALES_MAP[locale].name}
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </Link>
 | 
			
		||||
                </li>
 | 
			
		||||
              ))}
 | 
			
		||||
            </ul>
 | 
			
		||||
          </div>
 | 
			
		||||
        ) : null}
 | 
			
		||||
      </div>
 | 
			
		||||
    </nav>
 | 
			
		||||
          ) : null}
 | 
			
		||||
        </div>
 | 
			
		||||
      </nav>
 | 
			
		||||
    </ClickOutside>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								lib/click-outside/click-outside.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/click-outside/click-outside.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import React, { forwardRef, useEffect, Ref, MouseEvent } from 'react'
 | 
			
		||||
import hasParent from './has-parent'
 | 
			
		||||
 | 
			
		||||
interface ClickOutsideProps {
 | 
			
		||||
  active: boolean
 | 
			
		||||
  onClick: (e?: MouseEvent) => void
 | 
			
		||||
  children: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ClickOutside = (
 | 
			
		||||
  { active = true, onClick, children }: ClickOutsideProps,
 | 
			
		||||
  ref: Ref<HTMLDivElement> | null | any = {}
 | 
			
		||||
) => {
 | 
			
		||||
  console.log('--------', active, '-----------')
 | 
			
		||||
  const innerRef = ref?.current
 | 
			
		||||
 | 
			
		||||
  const handleClick = (event: any) => {
 | 
			
		||||
    console.log(innerRef, event.target)
 | 
			
		||||
    if (!hasParent(event.target, innerRef)) {
 | 
			
		||||
      if (typeof onClick === 'function') {
 | 
			
		||||
        event.preventDefault()
 | 
			
		||||
        event.stopImmediatePropagation()
 | 
			
		||||
        onClick(event)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (active) {
 | 
			
		||||
      document.addEventListener('mousedown', handleClick)
 | 
			
		||||
      document.addEventListener('touchstart', handleClick)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      if (active) {
 | 
			
		||||
        document.removeEventListener('mousedown', handleClick)
 | 
			
		||||
        document.removeEventListener('touchstart', handleClick)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return React.cloneElement(children, { ref })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default forwardRef(ClickOutside)
 | 
			
		||||
							
								
								
									
										5
									
								
								lib/click-outside/has-parent.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								lib/click-outside/has-parent.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
import isInDOM from './is-in-dom'
 | 
			
		||||
 | 
			
		||||
export default function hasParent(element, root) {
 | 
			
		||||
  return root && root.contains(element) && isInDOM(element)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/click-outside/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lib/click-outside/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export { default } from './click-outside'
 | 
			
		||||
							
								
								
									
										3
									
								
								lib/click-outside/is-in-dom.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								lib/click-outside/is-in-dom.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
export default function isInDom(obj) {
 | 
			
		||||
  return Boolean(obj.closest('body'))
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user