diff options
| -rw-r--r-- | src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx | 26 | ||||
| -rw-r--r-- | src/components/SearchForm/SearchForm.tsx | 4 | ||||
| -rw-r--r-- | src/components/Toolbar/Toolbar.tsx | 80 |
3 files changed, 94 insertions, 16 deletions
diff --git a/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx b/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx index 22da133..246ad80 100644 --- a/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx +++ b/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx @@ -1,19 +1,22 @@ import { CloseIcon, CogIcon, SearchIcon } from '@components/Icons'; import { t } from '@lingui/macro'; -import { SetStateAction } from 'react'; +import { ForwardedRef, forwardRef, SetStateAction } from 'react'; import styles from '../Buttons.module.scss'; type ButtonType = 'search' | 'settings'; -const ButtonToolbar = ({ - type, - isActivated, - setIsActivated, -}: { - type: ButtonType; - isActivated: boolean; - setIsActivated: (value: SetStateAction<boolean>) => void; -}) => { +const ButtonToolbar = ( + { + type, + isActivated, + setIsActivated, + }: { + type: ButtonType; + isActivated: boolean; + setIsActivated: (value: SetStateAction<boolean>) => void; + }, + ref: ForwardedRef<HTMLButtonElement> +) => { const ButtonIcon = () => (type === 'search' ? <SearchIcon /> : <CogIcon />); const btnClasses = isActivated ? `${styles.toolbar} ${styles['toolbar--activated']}` @@ -21,6 +24,7 @@ const ButtonToolbar = ({ return ( <button + ref={ref} className={btnClasses} type="button" onClick={() => setIsActivated(!isActivated)} @@ -42,4 +46,4 @@ const ButtonToolbar = ({ ); }; -export default ButtonToolbar; +export default forwardRef(ButtonToolbar); diff --git a/src/components/SearchForm/SearchForm.tsx b/src/components/SearchForm/SearchForm.tsx index c1a7ca7..cefda85 100644 --- a/src/components/SearchForm/SearchForm.tsx +++ b/src/components/SearchForm/SearchForm.tsx @@ -13,10 +13,10 @@ const SearchForm = ({ isOpened }: { isOpened: boolean }) => { useEffect(() => { setTimeout(() => { - if (inputRef.current) { + if (isOpened && inputRef.current) { inputRef.current.focus(); } - }, 800); + }, 400); }, [isOpened]); const launchSearch = (e: FormEvent) => { diff --git a/src/components/Toolbar/Toolbar.tsx b/src/components/Toolbar/Toolbar.tsx index 771efa1..25418b1 100644 --- a/src/components/Toolbar/Toolbar.tsx +++ b/src/components/Toolbar/Toolbar.tsx @@ -2,13 +2,17 @@ import { ButtonToolbar } from '@components/Buttons'; import MainNav from '@components/MainNav/MainNav'; import SearchForm from '@components/SearchForm/SearchForm'; import Settings from '@components/Settings/Settings'; -import { useEffect, useState } from 'react'; +import { RefObject, useCallback, useEffect, useRef, useState } from 'react'; import styles from './Toolbar.module.scss'; const Toolbar = () => { const [isNavOpened, setIsNavOpened] = useState<boolean>(false); const [isSearchOpened, setIsSearchOpened] = useState<boolean>(false); const [isSettingsOpened, setIsSettingsOpened] = useState<boolean>(false); + const searchBtnRef = useRef<HTMLButtonElement>(null); + const searchModalRef = useRef<HTMLDivElement>(null); + const settingsBtnRef = useRef<HTMLButtonElement>(null); + const settingsModalRef = useRef<HTMLDivElement>(null); useEffect(() => { if (isNavOpened) { @@ -31,6 +35,70 @@ const Toolbar = () => { } }, [isSettingsOpened]); + const isClickOutside = ( + ref: RefObject<HTMLDivElement>, + target: EventTarget + ) => { + const currentRef = ref.current; + return currentRef && !currentRef.contains(target as Node); + }; + + const isToggleBtn = (ref: RefObject<HTMLDivElement>, target: EventTarget) => { + const currentRef = ref.current; + return ( + currentRef && + currentRef.previousElementSibling && + currentRef.previousElementSibling.contains(target as Node) + ); + }; + + const isSearchBtn = useCallback((target: HTMLElement) => { + return ( + target === searchBtnRef.current || searchBtnRef.current?.contains(target) + ); + }, []); + + const isSettingsBtn = useCallback((target: HTMLElement) => { + return ( + target === settingsBtnRef.current || + settingsBtnRef.current?.contains(target) + ); + }, []); + + const handleVisibility = useCallback( + (e: MouseEvent | FocusEvent) => { + let ref: RefObject<HTMLDivElement> | null = null; + if (isSearchOpened) ref = searchModalRef; + if (isSettingsOpened) ref = settingsModalRef; + + if (!ref || !ref.current || !ref.current.id) return; + if (!isClickOutside(ref, e.target as Node)) return; + if (isToggleBtn(ref, e.target as Node)) return; + + if ( + ref.current.id === 'search-modal' && + !isSettingsBtn(e.target as HTMLElement) + ) + setIsSearchOpened(false); + if ( + ref.current.id === 'settings-modal' && + !isSearchBtn(e.target as HTMLElement) + ) + setIsSettingsOpened(false); + }, + [isSearchOpened, isSettingsOpened, isSearchBtn, isSettingsBtn] + ); + + useEffect(() => { + document.addEventListener('mousedown', handleVisibility); + document.addEventListener('focusin', handleVisibility); + + return () => { + document.removeEventListener('mousedown', handleVisibility); + document.removeEventListener('focusin', handleVisibility); + }; + }, [handleVisibility]); + const searchClasses = `${styles.menu} ${ isSearchOpened ? styles['menu--opened'] : styles['menu--closed'] }`; @@ -43,19 +111,25 @@ const Toolbar = () => { <div className={styles.wrapper}> <MainNav isOpened={isNavOpened} setIsOpened={setIsNavOpened} /> <ButtonToolbar + ref={searchBtnRef} type="search" isActivated={isSearchOpened} setIsActivated={setIsSearchOpened} /> - <div className={searchClasses}> + <div id="search-modal" className={searchClasses} ref={searchModalRef}> <SearchForm isOpened={isSearchOpened} /> </div> <ButtonToolbar + ref={settingsBtnRef} type="settings" isActivated={isSettingsOpened} setIsActivated={setIsSettingsOpened} /> - <div className={settingsClasses}> + <div + id="settings-modal" + className={settingsClasses} + ref={settingsModalRef} + > <Settings /> </div> </div> |
