๐ Task TODOLIST
- [x] ๊ฒ์์ฐฝ์ ์ด์ง ๋ง์ง ๊ฒฐ์ ํ๋ searchMode ์ํ ์์ฑ
- [x] ๊ฒ์๊ธฐ๋ฅ input์ฐฝ + ๊ฒ์ ๋ฒํผ + ์/์๋ ๋ฒํผ ์์ฑ
- [x] ์คํฌ๋กค ์ค์ธ์ง ์ฌ๋ถ์ ๋ฐ๋ผ์ absolute ๊ฐ ์ฃผ๊ธฐ
- [x] ํ์ฌ ๋ณด์ด๋ ๋ฉ์ธ์ง๋ค ์ค ์ํ๋ ๊ฒ์์ด ๊ฒ์ ์ ์ผ์นํ๋ ๊ฒฐ๊ณผ ๋ฐฐ์ด ์์ฑ
- [x] ์/์๋ ๋ฒํผ ๋๋ฅผ ์ ๊ฒ์ํ์ ๊ธฐ๋ฐ์ผ๋ก up/down count state ์์ฑ
- [x] ํด๋น์์ ์ฐพ์์ ์คํฌ๋กค
- [x] ์ ์น ํ๊ธฐ(๊ฐ์กฐ์ฒ๋ฆฌ)
- [x] ์ ํจ์ฑ ๊ฒ์ฌ
- [x] ๊ฒ์์ข ๋ฃ ์ ์ด์ ๊น์ง ๊ฒ์ํ๋ ๊ฑฐ ๋ชจ๋ clear
โจ ๊ฐ๋ฐ ๋ด์ฉ
1. ๊ฒ์์ฐฝ์ ์ด์ง ๋ง์ง ๊ฒฐ์ ํ๋ searchMode ์ํ ์์ฑ
2. ๊ฒ์๊ธฐ๋ฅ input์ฐฝ + ๊ฒ์ ๋ฒํผ + ์/์๋ ๋ฒํผ ์์ฑ
3. ์คํฌ๋กค ์ค์ธ์ง ์ฌ๋ถ์ ๋ฐ๋ผ์ absolute ๊ฐ ์ฃผ๊ธฐ
import { useMsgsQuery } from '@/hooks/useQueries/useChattingQuery';
import { chatStore } from '@/store/chatStore';
import { useEffect, useState } from 'react';
import { FaChevronDown, FaChevronUp } from 'react-icons/fa';
import { FaX } from 'react-icons/fa6';
import { IoIosSearch } from 'react-icons/io';
const ChatSearch = ({ isScrollTop }: { isScrollTop: boolean }) => {
const { searchMode, chatRoomId, setSearchMode } = chatStore((state) => state);
const [searchWord, setSearchWord] = useState('');
return (
<>
{searchMode ? (
<div className={`${isScrollTop ? 'w-full' : 'absolute z-50 w-[90%]'} flex items-center gap-2`}>
<form
onSubmit={(e) => {
e.preventDefault();
handleSearch();
}}
className="flex w-full gap-2 relative items-center"
>
<div className="absolute ml-[10px] flex items-center gap-[6px] text-[#A1A1AA]">
{searchWord ? null : (
<>
<IoIosSearch />
</>
)}
</div>
<input
value={searchWord}
onChange={(e) => setSearchWord(e.target.value)}
className="h-[40px] w-full p-[8px] border-2 border-[#D4D4D8] rounded-md focus:outline-none"
placeholder=" ์ฑํ
๋ด์ฉ ๊ฒ์ํ๊ธฐ"
autoFocus
></input>
<div className="absolute right-3">{doneSearchDivs?.length ? `(${doneSearchDivs?.length})` : ''}</div>
</form>
<div className="flex gap-2">
<div className="flex flex-col justify-center items-center">
<button onClick={handleSearchUp}>
<FaChevronUp />
</button>
<button onClick={handleSearchDown}>
<FaChevronDown />
</button>
</div>
<button onClick={stopSearch}>
<FaX />
</button>
</div>
</div>
) : null}
</>
);
};
export default ChatSearch;
4. ํ์ฌ ๋ณด์ด๋ ๋ฉ์ธ์ง๋ค ์ค ์ํ๋ ๊ฒ์์ด ๊ฒ์ ์ ์ผ์นํ๋ ๊ฒฐ๊ณผ ๋ฐฐ์ด ์์ฑ
5. ์/์๋ ๋ฒํผ ๋๋ฅผ ์ ๊ฒ์ํ์ ๊ธฐ๋ฐ์ผ๋ก up/down count state ์์ฑ
6. ํด๋น์์ ์ฐพ์์ ์คํฌ๋กค
7. ์ ์น ํ๊ธฐ(๊ฐ์กฐ์ฒ๋ฆฌ)
8. ์ ํจ์ฑ ๊ฒ์ฌ
const [doneSearchDivs, setDoneSearchdivs] = useState<(HTMLElement | null)[]>();
const [searchCount, setSearchCount] = useState(0);
const [upDownCount, setUpDownCount] = useState(0);
const handleSearch = () => {
if (searchWord) {
const filteredIds =
messages &&
messages
.filter((m) => m.message?.includes(searchWord))
.map((messages) => messages.message_id)
.reverse();
const idsDivs = filteredIds?.map((id) => {
return document.getElementById(`${messages?.find((m) => m.message_id === id)?.message_id}`);
});
setDoneSearchdivs(idsDivs);
if (idsDivs && searchCount < idsDivs.length) {
if (searchCount > 0) idsDivs[searchCount - 1]!.style.backgroundColor = '';
const theDiv = idsDivs[searchCount];
if (theDiv) {
theDiv.style.backgroundColor! = '#E4D4F4';
theDiv.style.borderRadius = '5px';
theDiv.scrollIntoView({ block: 'center' });
setSearchCount((prev) => prev + 1);
setUpDownCount((prev) => prev + 1);
// upDownCount๋ ์ง๊ธ๊น์ง searchํ ๊ฒ์ ๊ธฐ๋ฐ์ผ๋ก ๋์ด์ผ ํ๋๊น ์ฌ๊ธฐ์ ์ข
์๋์ด์ +
}
} else {
alert('๋ ์ด์ ์ฐพ์ ๋ด์ฉ์ด ์์ต๋๋ค.');
}
} else {
alert('๊ฒ์์ด๋ฅผ ์
๋ ฅํด์ฃผ์ธ์');
}
};
const handleSearchUp = () => {
if (doneSearchDivs && upDownCount < doneSearchDivs.length) {
if (upDownCount > 0) doneSearchDivs[upDownCount - 1]!.style.backgroundColor = '';
const theDiv = doneSearchDivs[upDownCount];
if (theDiv) {
theDiv.style.backgroundColor! = '#E4D4F4';
theDiv.style.borderRadius = '5px';
theDiv.scrollIntoView({ block: 'center' });
}
setUpDownCount((prev) => prev + 1);
}
};
const handleSearchDown = () => {
if (doneSearchDivs && 1 < upDownCount) {
if (upDownCount <= doneSearchDivs.length) doneSearchDivs[upDownCount - 1]!.style.backgroundColor = '';
const theDiv = doneSearchDivs[upDownCount - 2];
if (theDiv) {
theDiv.style.backgroundColor! = '#E4D4F4';
theDiv.style.borderRadius = '5px';
theDiv.scrollIntoView({ block: 'center' });
}
setUpDownCount((prev) => prev - 1);
}
};
const clearColor = () => {
doneSearchDivs?.forEach((div) => {
if (div) div.style.backgroundColor = '';
});
};
9. ๊ฒ์์ข ๋ฃ ์ ์ด์ ๊น์ง ๊ฒ์ํ๋ ๊ฑฐ ๋ชจ๋ clear
const stopSearch = () => {
setSearchMode();
setSearchCount(0);
setSearchWord('');
clearColor();
setDoneSearchdivs([]);
setUpDownCount(0);
};
useEffect(() => {
if (!searchWord) {
setSearchCount(0);
setUpDownCount(0);
clearColor();
setDoneSearchdivs([]);
}
}, [searchWord]);
๐จ TroubleShotting
1. ๋ถ๋ช ํ ๊ฐ์ด ์๋์ง ํ์ธํ์์๋ null์ธ ๊ฒ ๊ฐ๋ค๋ ์ค๋ฅ๊ฐ ๋์๋ค.
=> ๋ณ์์ ํ ๋นํด์ ํ์ธํด์คฌ๋๋ ํด๊ฒฐ๋์๋ค.
2. ์ฑํ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ up & down ํ ๋, ๋ค์ ๊ฒฐ๊ณผ๋ฅผ focus ํ๋ฉด ์ด์ ๊ฒฐ๊ณผ ๊ฐ์กฐ์ฒ๋ฆฌ๊ฐ ๊ณ์ ๋จ์์์๋ค.
=> ๋ค์ ๊ฒฐ๊ณผ๋ก focus ๋๋ฉด ์ด์ ๊ฒฐ๊ณผ์ backgroundColor ์คํ์ผ์ "" ๋น ๊ฐ์ผ๋ก ๋ณ๊ฒฝํด์ฃผ์๋ค.
3. upDownCount๊ฐ ๋๋ ์์ด ์ฌ๋ผ๊ฐ๊ณ , ๋ด๋ ค๊ฐ๋ ์ด์ ๋ฐ์
=> upDownCount๋ doneSearchDivs(= ๊ฒ์๊ฒฐ๊ณผ)์ length์ ๋ฐ๋ผ ์ ์ ๋๋ง up, 1๋ณด๋ค ํด ๋๋ง downํ ์ ์๋๋ก ์กฐ๊ฑด์ ์ถ๊ฐํจ
๐ธ ์คํฌ๋ฆฐ์ท
๊ฒ์ํ๊ธฐ(๊ฐ์ฅ ์ต์ ์ "3" ๊ฐ์กฐ์ฒ๋ฆฌ) | ์๋ก ์ฌ๋ฆฌ๋ฉด, ๊ทธ ์ ์ "3" ๊ฐ์กฐ์ฒ๋ฆฌ |
๐ ๋ ํผ๋ฐ์ค
scrollIntoView()
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
https://mosei.tistory.com/entry/Javascript-div-%EC%9A%94%EC%86%8C%EC%9D%98-top%EC%8B%9C%EC%9E%91-%EC%9C%84%EC%B9%98-%EC%95%8C%EC%95%84%EB%82%B4%EA%B8%B0
https://github.com/Team-MeetGo/MeetGO/pull/105
https://github.com/Team-MeetGo/MeetGO/pull/205