feat(Mobile Schedule): 유튜브 스타일 추천 검색어 리스트 추가

- 드롭다운 대신 컨텐츠 영역 전체에 추천 검색어 표시
- 입력값에 따른 실시간 필터링
- 키패드 검색 버튼(Enter) 클릭 시 키패드 닫기
- 검색 결과 모드에서는 autoFocus 비활성화
This commit is contained in:
caadiq 2026-01-11 18:59:38 +09:00
parent 09706e42e3
commit 043925dcb2

View file

@ -27,6 +27,13 @@ function MobileSchedule() {
const [calendarViewDate, setCalendarViewDate] = useState(() => new Date(selectedDate)); // const [calendarViewDate, setCalendarViewDate] = useState(() => new Date(selectedDate)); //
const [calendarShowYearMonth, setCalendarShowYearMonth] = useState(false); // const [calendarShowYearMonth, setCalendarShowYearMonth] = useState(false); //
const contentRef = useRef(null); // const contentRef = useRef(null); //
const searchContainerRef = useRef(null); // ( )
const searchInputRef = useRef(null); // ( )
//
const [showSuggestions, setShowSuggestions] = useState(false);
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
const [originalSearchQuery, setOriginalSearchQuery] = useState(''); //
// (history ) // (history )
const enterSearchMode = () => { const enterSearchMode = () => {
@ -38,7 +45,10 @@ function MobileSchedule() {
const exitSearchMode = () => { const exitSearchMode = () => {
setIsSearchMode(false); setIsSearchMode(false);
setSearchInput(''); setSearchInput('');
setOriginalSearchQuery('');
setSearchTerm(''); setSearchTerm('');
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
}; };
// //
@ -175,6 +185,26 @@ function MobileSchedule() {
}; };
}, [showCalendar]); }, [showCalendar]);
//
useEffect(() => {
const handleClickOutside = (event) => {
if (searchContainerRef.current && !searchContainerRef.current.contains(event.target)) {
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
}
};
if (showSuggestions) {
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchstart', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchstart', handleClickOutside);
};
}, [showSuggestions]);
// //
const getCategoryColor = (categoryId) => { const getCategoryColor = (categoryId) => {
const category = categories.find(c => c.id === categoryId); const category = categories.find(c => c.id === categoryId);
@ -257,27 +287,75 @@ function MobileSchedule() {
{/* 툴바 (헤더 + 날짜 선택기) */} {/* 툴바 (헤더 + 날짜 선택기) */}
<div className="mobile-toolbar-schedule shadow-sm z-50"> <div className="mobile-toolbar-schedule shadow-sm z-50">
{isSearchMode ? ( {isSearchMode ? (
<div className="flex items-center gap-3 px-4 py-3"> <div className="flex items-center gap-3 px-4 py-3 relative" ref={searchContainerRef}>
<div className="flex-1 flex items-center gap-2 bg-gray-100 rounded-full px-4 py-2 min-w-0"> <div className="flex-1 flex items-center gap-2 bg-gray-100 rounded-full px-4 py-2 min-w-0">
<Search size={18} className="text-gray-400 flex-shrink-0" /> <Search size={18} className="text-gray-400 flex-shrink-0" />
<input <input
ref={searchInputRef}
type="text" type="text"
inputMode="search" inputMode="search"
enterKeyHint="search" enterKeyHint="search"
placeholder="일정 검색..." placeholder="일정 검색..."
value={searchInput} value={searchInput}
onChange={(e) => setSearchInput(e.target.value)} onChange={(e) => {
setSearchInput(e.target.value);
setOriginalSearchQuery(e.target.value);
setShowSuggestions(true);
setSelectedSuggestionIndex(-1);
}}
onFocus={() => setShowSuggestions(true)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { //
const dummySuggestions = ['성수기', '성수기 이채영', '이채영 먹방', 'NOW TOMORROW', '하얀 그리움', '콘서트', '월드투어'].filter(s =>
s.toLowerCase().includes(originalSearchQuery.toLowerCase())
).slice(0, 7);
if (e.key === 'ArrowDown') {
e.preventDefault(); e.preventDefault();
const newIndex = selectedSuggestionIndex < dummySuggestions.length - 1
? selectedSuggestionIndex + 1
: 0;
setSelectedSuggestionIndex(newIndex);
if (dummySuggestions[newIndex]) {
setSearchInput(dummySuggestions[newIndex]);
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
const newIndex = selectedSuggestionIndex > 0
? selectedSuggestionIndex - 1
: dummySuggestions.length - 1;
setSelectedSuggestionIndex(newIndex);
if (dummySuggestions[newIndex]) {
setSearchInput(dummySuggestions[newIndex]);
}
} else if (e.key === 'Enter') {
e.preventDefault();
if (selectedSuggestionIndex >= 0 && dummySuggestions[selectedSuggestionIndex]) {
setSearchInput(dummySuggestions[selectedSuggestionIndex]);
setSearchTerm(dummySuggestions[selectedSuggestionIndex]);
} else if (searchInput.trim()) {
setSearchTerm(searchInput); setSearchTerm(searchInput);
} }
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
//
searchInputRef.current?.blur();
}
}} }}
className="flex-1 bg-transparent outline-none text-sm min-w-0 [&::-webkit-search-cancel-button]:hidden" className="flex-1 bg-transparent outline-none text-sm min-w-0 [&::-webkit-search-cancel-button]:hidden"
autoFocus autoFocus={!searchTerm}
/> />
{searchInput && ( {searchInput && (
<button onClick={() => { setSearchInput(''); setSearchTerm(''); }} className="flex-shrink-0"> <button
onClick={() => {
setSearchInput('');
setOriginalSearchQuery('');
setSearchTerm('');
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
}}
className="flex-shrink-0"
>
<X size={18} className="text-gray-400" /> <X size={18} className="text-gray-400" />
</button> </button>
)} )}
@ -494,13 +572,48 @@ function MobileSchedule() {
{/* 컨텐츠 영역 */} {/* 컨텐츠 영역 */}
<div className="mobile-content" ref={isSearchMode && searchTerm ? scrollContainerRef : contentRef}> <div className="mobile-content" ref={isSearchMode && searchTerm ? scrollContainerRef : contentRef}>
<div className="px-4 pt-4 pb-4"> <div className={`px-4 pb-4 ${isSearchMode && !searchTerm ? 'pt-0' : 'pt-4'}`}>
{isSearchMode ? ( {isSearchMode ? (
// //
!searchTerm ? ( !searchTerm ? (
// - // - ( )
<div className="space-y-0">
{(() => {
const dummySuggestions = ['성수기', '성수기 이채영', '이채영 먹방', 'NOW TOMORROW', '하얀 그리움', '콘서트', '월드투어'];
//
const filtered = originalSearchQuery.length > 0
? dummySuggestions.filter(s => s.toLowerCase().includes(originalSearchQuery.toLowerCase()))
: dummySuggestions;
if (filtered.length === 0) {
return (
<div className="text-center py-8 text-gray-400"> <div className="text-center py-8 text-gray-400">
검색어를 입력하세요 추천 검색어가 없습니다
</div>
);
}
return filtered.map((suggestion, index) => (
<button
key={suggestion}
onClick={() => {
setSearchInput(suggestion);
setSearchTerm(suggestion);
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
}}
className={`w-full px-0 py-3.5 text-left flex items-center gap-4 border-b border-gray-100 active:bg-gray-50 ${
index === selectedSuggestionIndex
? 'bg-primary/5'
: ''
}`}
>
<Search size={18} className="text-gray-400 shrink-0" />
<span className="text-[15px] text-gray-700 flex-1">{suggestion}</span>
</button>
));
})()}
</div> </div>
) : searchLoading ? ( ) : searchLoading ? (
<div className="flex justify-center py-8"> <div className="flex justify-center py-8">