feat(Mobile Schedule): 유튜브 스타일 추천 검색어 리스트 추가
- 드롭다운 대신 컨텐츠 영역 전체에 추천 검색어 표시 - 입력값에 따른 실시간 필터링 - 키패드 검색 버튼(Enter) 클릭 시 키패드 닫기 - 검색 결과 모드에서는 autoFocus 비활성화
This commit is contained in:
parent
09706e42e3
commit
043925dcb2
1 changed files with 123 additions and 10 deletions
|
|
@ -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();
|
||||||
setSearchTerm(searchInput);
|
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);
|
||||||
|
}
|
||||||
|
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="text-center py-8 text-gray-400">
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue