Skip to content

Commit

Permalink
Merge pull request #101 from sprint-military-9team/feat#100-search/au…
Browse files Browse the repository at this point in the history
…tosuggestion

Feat#100 search/autosuggestion
  • Loading branch information
zoonyoung authored Apr 28, 2024
2 parents 3cca204 + da52a78 commit 31a926e
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 8 deletions.
32 changes: 32 additions & 0 deletions src/components/common/Header/SearchBox.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,35 @@
width: 100%;
}
}

.itemBox {
position: absolute;
display: flex;
flex-direction: column;
top: 5rem;
left: 0;
z-index: 1;
width: 100%;
overflow: hidden;
background: $white;
border: 2px solid $red-200;

@include rounded-lg;
}

.item {
padding: 0.6rem 1.5rem;
font-size: 1.6rem;
border: none;
background: none;
cursor: pointer;
border: 1px solid $red-600;

span {
@include font-bold;
}

&:hover {
background: $red-600;
}
}
90 changes: 82 additions & 8 deletions src/components/common/Header/SearchBox.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,106 @@
'use client';

import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import Image from 'next/image';
import { SEARCH_ICON } from '@/utils/constants';
import BASE_URL from '@/constants/BASEURL';
import useOutsideClick from '@/hooks/useOutsideClick';
import styles from './SearchBox.module.scss';

export default function SearchBox() {
const router = useRouter();
const [input, setInput] = useState('');
const [keyword, setKeyword] = useState('');
const [showAutoSuggestion, setShowAutoSuggestion] = useState(false);
const [suggestionList, setSuggestionList] = useState([]);
const autoSuggestionRef = useRef(null);
const inputRef = useRef(null);
useOutsideClick(autoSuggestionRef, () => setShowAutoSuggestion(false));

const handleSuggestionClick = (event) => {
setKeyword(event.target.id);
inputRef.current.focus();
setShowAutoSuggestion(false);
};

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInput(event.target.value);
setKeyword(event.target.value);
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' && input) {
router.push(`/?keyword=${input}`);
if (event.key === 'Enter' && keyword) {
router.push(`/?keyword=${keyword}`);
}
};

useEffect(() => {
let debounce;
if (keyword) {
debounce = setTimeout(async () => {
try {
const response = await fetch(
`${BASE_URL}/notices?limit=10&keyword=${encodeURIComponent(keyword.trim())}&sort=time`,
);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const searchSet = new Set<string[]>();
const data = await response.json();
data.items.filter((item) => searchSet.add(item.item.shop.item.name));
setSuggestionList([...searchSet]);
} catch (error) {
console.error('There was a problem with your fetch operation:', error);
}
}, 200);
} else {
setSuggestionList([]);
}

return () => {
clearTimeout(debounce);
};
}, [keyword]);

return (
<form className={styles.searchBarWrapper} onSubmit={(e) => e.preventDefault()}>
<Image src={SEARCH_ICON} alt="search" width={20} height={20} className={styles.searchButton} priority />
<form className={styles.searchBarWrapper} onSubmit={(e) => e.preventDefault()} ref={autoSuggestionRef}>
<Image src={SEARCH_ICON} alt="search" width={21} height={20} className={styles.searchButton} priority />
<input
className={styles.searchBar}
value={input}
value={keyword}
ref={inputRef}
onFocus={() => setShowAutoSuggestion(true)}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="가게 이름으로 찾아보세요"
/>
{showAutoSuggestion && (
<div className={styles.itemBox}>
{suggestionList.map((data) => (
<button
type="button"
className={styles.item}
key={data}
id={data}
onClick={handleSuggestionClick}
aria-label={`${data}을(를) 선택하려면 클릭하세요.`}
dangerouslySetInnerHTML={{
__html: data.replace(
new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'),
'<strong>$1</strong>',
),
}}
/>

// <button type="button" className={styles.item} key={data} id={data} onClick={handleSuggestionClick}>
// {data}
// </button>
))}
{suggestionList.length === 0 && (
<button type="button" className={styles.item}>
검색된 결과가 없습니다.
</button>
)}
</div>
)}
</form>
);
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"downlevelIteration": true,
"plugins": [
{
"name": "next"
Expand Down

0 comments on commit 31a926e

Please sign in to comment.