refactor: Add hideOnClickOutside feature to reuse code (#240)

* remove console log

* refactor: Add hideOnClickOutside feature to reuse code

This change abstracts the code repetition in three different files and promote the DRY principle by adding the hideOnClickOutside feature to the handleClickOutside function. The feature is designed to hide an element when clicking outside of its area.

* refactor: hooks

---------

Co-authored-by: Jing Hua <tohjinghua123@gmail.com>
This commit is contained in:
Tindo Arsel 2023-04-20 15:35:51 +01:00 committed by GitHub
parent 226944b62a
commit b3f421cde9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 70 deletions

View file

@ -1,9 +1,12 @@
import React, { useEffect, useRef, useState } from 'react';
import useStore from '@store/store';
import { useTranslation } from 'react-i18next';
import { matchSorter } from 'match-sorter';
import { Prompt } from '@type/prompt';
import useHideOnOutsideClick from '@hooks/useHideOnOutsideClick';
const CommandPrompt = ({
_setContent,
}: {
@ -11,10 +14,10 @@ const CommandPrompt = ({
}) => {
const { t } = useTranslation();
const prompts = useStore((state) => state.prompts);
const [dropDown, setDropDown] = useState<boolean>(false);
const [_prompts, _setPrompts] = useState<Prompt[]>(prompts);
const [input, setInput] = useState<string>('');
const dropdownRef = useRef<HTMLDivElement>(null);
const [dropDown, setDropDown, dropDownRef] = useHideOnOutsideClick();
useEffect(() => {
const filteredPrompts = matchSorter(useStore.getState().prompts, input, {
@ -28,29 +31,8 @@ const CommandPrompt = ({
setInput('');
}, [prompts]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setDropDown(false);
}
};
if (dropDown) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [dropdownRef, dropDown]);
return (
<div className='relative max-wd-sm' ref={dropdownRef}>
<div className='relative max-wd-sm' ref={dropDownRef}>
<button
className='btn btn-neutral btn-small'
onClick={() => setDropDown(!dropDown)}

View file

@ -5,6 +5,8 @@ import useStore from '@store/store';
import DownChevronArrow from '@icon/DownChevronArrow';
import { ChatInterface, Role, roles } from '@type/chat';
import useHideOnOutsideClick from '@hooks/useHideOnOutsideClick';
const RoleSelector = React.memo(
({
role,
@ -20,29 +22,7 @@ const RoleSelector = React.memo(
const setChats = useStore((state) => state.setChats);
const currentChatIndex = useStore((state) => state.currentChatIndex);
const [dropDown, setDropDown] = useState<boolean>(false);
const dropDownRef = useRef<HTMLDivElement>(null);
const handleClickOutside = useCallback(
(event: any) => {
if (dropDownRef.current && !dropDownRef.current.contains(event.target))
setDropDown(false);
},
[dropDownRef, setDropDown, messageIndex]
);
useEffect(() => {
// Bind the event listener only if the dropdown is open.
if (dropDown) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [dropDownRef, dropDown]);
const [dropDown, setDropDown, dropDownRef] = useHideOnOutsideClick();
return (
<div className='prose dark:prose-invert relative'>

View file

@ -20,6 +20,8 @@ import RefreshIcon from '@icon/RefreshIcon';
import { folderColorOptions } from '@constants/color';
import useHideOnOutsideClick from '@hooks/useHideOnOutsideClick';
const ChatFolder = ({
folderChats,
folderId,
@ -37,13 +39,13 @@ const ChatFolder = ({
const inputRef = useRef<HTMLInputElement>(null);
const folderRef = useRef<HTMLDivElement>(null);
const gradientRef = useRef<HTMLDivElement>(null);
const paletteRef = useRef<HTMLDivElement>(null);
const [_folderName, _setFolderName] = useState<string>(folderName);
const [isEdit, setIsEdit] = useState<boolean>(false);
const [isDelete, setIsDelete] = useState<boolean>(false);
const [isHover, setIsHover] = useState<boolean>(false);
const [showPalette, setShowPalette] = useState<boolean>(false);
const [showPalette, setShowPalette, paletteRef] = useHideOnOutsideClick();
const editTitle = () => {
const updatedFolders: FolderCollection = JSON.parse(
@ -145,27 +147,6 @@ const ChatFolder = ({
if (inputRef && inputRef.current) inputRef.current.focus();
}, [isEdit]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
paletteRef.current &&
!paletteRef.current.contains(event.target as Node)
) {
setShowPalette(false);
}
};
if (showPalette) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [paletteRef, showPalette]);
return (
<div
className={`w-full transition-colors group/folder ${

View file

@ -0,0 +1,36 @@
import React, { useEffect, useRef, useState } from 'react';
const useHideOnOutsideClick = (): [
boolean,
React.Dispatch<React.SetStateAction<boolean>>,
React.RefObject<HTMLDivElement>
] => {
const elementRef = useRef<HTMLDivElement>(null);
const [showElement, setShowElement] = useState<boolean>(false);
const handleClickOutside = (event: MouseEvent) => {
if (
elementRef.current &&
!elementRef.current.contains(event.target as Node)
) {
setShowElement(false);
}
};
useEffect(() => {
// Bind the event listener only if the element is show.
if (showElement) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [showElement, elementRef]);
return [showElement, setShowElement, elementRef];
};
export default useHideOnOutsideClick;