diff --git a/src/components/common/CategoryTabBar/index.tsx b/src/components/common/CategoryTabBar/index.tsx index a90d075f..074a7b48 100644 --- a/src/components/common/CategoryTabBar/index.tsx +++ b/src/components/common/CategoryTabBar/index.tsx @@ -2,6 +2,8 @@ import { Z_INDEX } from '@/styles/constants'; import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; +import { HEIGHTS } from '@/styles/constants'; + type TapWrapperProps = { isActive: boolean; }; @@ -41,13 +43,12 @@ export default CategoryTabBar; const Wrapper = styled.div` z-index: ${Z_INDEX.Header}; - position: fixed; + position: sticky; + top: ${HEIGHTS.HEADER}; width: 100%; height: 41px; display: flex; flex-direction: row; - justify-content: space-between; - align-items: center; border-bottom: 1px solid var(--color-gray-md); background: var(--color-white); font-size: var(--font-size-sm); @@ -57,9 +58,11 @@ const Wrapper = styled.div` const TabWrapper = styled.div` width: 100%; height: 100%; - padding: 11px; cursor: pointer; text-align: center; + display: flex; + justify-content: center; + align-items: center; color: ${({ isActive }) => (isActive ? 'var(--color-black)' : 'var(--color-gray-dk)')}; border-bottom: ${({ isActive }) => (isActive ? '2px solid var(--color-black)' : 'none')}; diff --git a/src/components/common/FakeSearchBar/index.tsx b/src/components/common/FakeSearchBar/index.tsx index 5a270939..c9af125d 100644 --- a/src/components/common/FakeSearchBar/index.tsx +++ b/src/components/common/FakeSearchBar/index.tsx @@ -2,18 +2,17 @@ import styled from '@emotion/styled'; import SearchIcon from '@/assets/icons/search.svg?react'; import IconButton from '@/components/common/IconButton'; +import useSearchModalStore from '@/store/useSearchModalStore'; import { HEIGHTS } from '@/styles/constants'; const SEARCH_PLACEHOLDER = '작품/작가 외 검색은 #을 붙여주세요'; -interface FakeSearchBarProps { - modalOpen: () => void; -} +const FakeSearchBar = () => { + const { isModalOpen, setIsModalOpen } = useSearchModalStore(); -const FakeSearchBar = ({ modalOpen }: FakeSearchBarProps) => { return ( - + setIsModalOpen(!isModalOpen)}> diff --git a/src/components/common/SearchModal/index.tsx b/src/components/common/SearchModal/index.tsx index 59291ff9..18763099 100644 --- a/src/components/common/SearchModal/index.tsx +++ b/src/components/common/SearchModal/index.tsx @@ -1,30 +1,31 @@ import styled from '@emotion/styled'; import SearchBar from '@/components/layouts/SearchBar'; +import useSearchModalStore from '@/store/useSearchModalStore'; import { HEIGHTS, Z_INDEX } from '@/styles/constants'; import Ad from './Ad'; import PopularSearch from './PopularSearch'; import RecentSearch from './RecentSearch'; -interface SearchModalProps { - modalClose: () => void; -} - -const SearchModal = ({ modalClose }: SearchModalProps) => { +const SearchModal = () => { + const { isModalOpen } = useSearchModalStore(); const searchSectionList: React.ReactNode[] = [, , ]; // 각 섹션을 리스트로 관리 // todo: 검색어 존재 시 자동 완성 모달 뜨게 - // x 버튼 클릭 시 검색 모달 뜨게 return ( - - - - {searchSectionList.map((section, index) => ( -
{section}
- ))} -
-
+ <> + {isModalOpen && ( + + + + {searchSectionList.map((section, index) => ( +
{section}
+ ))} +
+
+ )} + ); }; diff --git a/src/components/layouts/Header/index.tsx b/src/components/layouts/Header/index.tsx index 1d57e557..1a448c95 100644 --- a/src/components/layouts/Header/index.tsx +++ b/src/components/layouts/Header/index.tsx @@ -5,18 +5,19 @@ import Logo from '@/assets/logo.svg?react'; import IconButton from '@/components/common/IconButton'; import { RouterPath } from '@/routes/path'; import useModeStore from '@/store/useModeStore'; +import useSearchModalStore from '@/store/useSearchModalStore'; import { HEIGHTS, Z_INDEX } from '@/styles/constants'; interface HeaderProps { title?: string; leftSideChildren?: React.ReactNode; rightSideChildren?: React.ReactNode; - modalOpen?: () => void; } -const Header = ({ title, leftSideChildren, rightSideChildren, modalOpen }: HeaderProps) => { +const Header = ({ title, leftSideChildren, rightSideChildren }: HeaderProps) => { const { pathname } = useLocation(); const { mode } = useModeStore(); + const { isModalOpen, setIsModalOpen } = useSearchModalStore(); const renderElements = () => { if (pathname === RouterPath.home) { @@ -24,7 +25,7 @@ const Header = ({ title, leftSideChildren, rightSideChildren, modalOpen }: Heade <> - + setIsModalOpen(!isModalOpen)} /> {mode === 'user' ? ( ) : ( diff --git a/src/components/layouts/SearchBar/index.tsx b/src/components/layouts/SearchBar/index.tsx index 0fc82821..7ed75eac 100644 --- a/src/components/layouts/SearchBar/index.tsx +++ b/src/components/layouts/SearchBar/index.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled'; +import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate, useSearchParams } from 'react-router-dom'; @@ -6,20 +7,24 @@ import CancelIcon from '@/assets/icons/cancel-filled.svg?react'; import SearchIcon from '@/assets/icons/search.svg?react'; import IconButton from '@/components/common/IconButton'; import { SEARCH_ARRAY_KEY } from '@/constants/search'; +import { RouterPath } from '@/routes/path'; +import useSearchModalStore from '@/store/useSearchModalStore'; import { HEIGHTS, Z_INDEX } from '@/styles/constants'; const SEARCH_PLACEHOLDER = '작품/작가 외 검색은 #을 붙여주세요'; const MAX_RECENT_SEARCHES = 10; interface SearchBarProps { + includeBack?: boolean; includeFavorite?: boolean; - goBack?: () => void; + goBack?: () => void; // SearchResult에서만 전달됨 } -const SearchBar = ({ includeFavorite = false, goBack }: SearchBarProps) => { +const SearchBar = ({ includeBack = true, includeFavorite = false, goBack }: SearchBarProps) => { const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const initialSearchWord = searchParams.get('query') || ''; + const { isModalOpen, setIsModalOpen } = useSearchModalStore(); const { register, handleSubmit, watch, setValue, formState } = useForm<{ searchWord: string }>({ defaultValues: { @@ -28,13 +33,25 @@ const SearchBar = ({ includeFavorite = false, goBack }: SearchBarProps) => { mode: 'onSubmit', }); + // test + useEffect(() => { + console.log('isModalOpen: ', isModalOpen); + }, [isModalOpen]); + const generateRandomKey = () => { return Math.random().toString(36).substr(2, 9); }; + const handleClickBack = () => { + if (goBack) goBack(); // SearchResult에서만 전달됨 // pathname 추출해서 해도 된다 생각했는데 안 됨 + setIsModalOpen(false); + }; + const handleRemoveSearchWord = (e: React.MouseEvent) => { + console.log('called'); e.preventDefault(); setValue('searchWord', ''); + setIsModalOpen(true); }; const activeEnter = (data: { searchWord: string }) => { @@ -57,14 +74,14 @@ const SearchBar = ({ includeFavorite = false, goBack }: SearchBarProps) => { localStorage.setItem(SEARCH_ARRAY_KEY, JSON.stringify(searchArray)); setSearchParams({ query: searchWord }); - navigate(`/results?query=${searchWord}`); + navigate(`/${RouterPath.results}?query=${searchWord}`); }; const nowSearchWord = watch('searchWord'); return ( - + {includeBack && } { {...register('searchWord', { validate: (value) => value.trim() !== '' || '공백만 입력할 수 없습니다.', })} + onClick={() => setIsModalOpen(true)} /> {nowSearchWord.trim().length > 0 && } diff --git a/src/pages/Categories/index.tsx b/src/pages/Categories/index.tsx index d6098e2e..b3aec02d 100644 --- a/src/pages/Categories/index.tsx +++ b/src/pages/Categories/index.tsx @@ -1,5 +1,4 @@ import styled from '@emotion/styled'; -import { useState } from 'react'; import FakeSearchBar from '@/components/common/FakeSearchBar'; import SearchModal from '@/components/common/SearchModal'; @@ -9,16 +8,10 @@ import CategoryItem from './components/CategoryItem'; import CurationItem from './components/CurationItem'; const Categories = () => { - const [isModalOpen, setIsModalOpen] = useState(false); - - const handleModalOpen = () => { - setIsModalOpen(true); - }; - return ( - - {isModalOpen && setIsModalOpen(false)} />} + + {CATEGORY_LIST.map((category) => ( diff --git a/src/pages/Discover/index.tsx b/src/pages/Discover/index.tsx index f483bd21..ae395b79 100644 --- a/src/pages/Discover/index.tsx +++ b/src/pages/Discover/index.tsx @@ -4,12 +4,14 @@ import { ErrorBoundary } from 'react-error-boundary'; import useGetFeed, { type Product } from '@/apis/products/useGetFeed'; import Loader from '@/components/common/Loader'; +import SearchModal from '@/components/common/SearchModal'; import SearchBar from '@/components/layouts/SearchBar'; import { HEIGHTS } from '@/styles/constants'; const Discover = () => ( - + + {/* todo: 폴백 UI 만들기 */} Error}> diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 06f18500..8a049304 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,5 +1,4 @@ import styled from '@emotion/styled'; -import { useState } from 'react'; import SearchModal from '@/components/common/SearchModal'; import Footer from '@/components/layouts/Footer'; @@ -10,16 +9,10 @@ import AdBanner from './components/AdBanner'; import ArticleBanner from './components/ArticleBanner'; const Home = () => { - const [isModalOpen, setIsModalOpen] = useState(false); - - const handleModalOpen = () => { - setIsModalOpen(true); - }; - return ( - {isModalOpen && setIsModalOpen(false)} />} -
+
+ {ARTICLE_LIST.map((item) => ( { const searchArtistLen = artistsData.length; const categoryList = ['전체', '작품', '작가']; + const { isModalOpen, setIsModalOpen } = useSearchModalStore(); + + // 검색 결과로 이동 직후 검색 모달 닫음 + useEffect(() => { + setIsModalOpen(false); + }, []); + const goBack = () => { - navigate(RouterPath.categories); + navigate(-1); }; const handleTabClick = (tab: string) => { @@ -44,11 +51,9 @@ const SearchResultsContent = () => { return ( - - - + + {isModalOpen && } - {selectedTab === '전체' && ( @@ -105,12 +110,6 @@ const PageContainer = styled.div` width: 100%; `; -const HeaderSection = styled.div` - position: sticky; - height: 41px; - z-index: ${Z_INDEX.SearchHeader}; -`; - const ContentSection = styled.div` flex: 1; overflow-y: auto; diff --git a/src/store/useSearchModalStore.ts b/src/store/useSearchModalStore.ts new file mode 100644 index 00000000..b547599d --- /dev/null +++ b/src/store/useSearchModalStore.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand'; + +type SearchModalState = { + isModalOpen: boolean; + setIsModalOpen: (isModalOpen: boolean) => void; +}; + +const useSearchModalStore = create((set) => ({ + isModalOpen: false, + setIsModalOpen: (isModalOpen) => set({ isModalOpen }), +})); + +export default useSearchModalStore;