Skip to content

Commit

Permalink
refactor(SearchModal): 검색 모달 상태 관리
Browse files Browse the repository at this point in the history
  • Loading branch information
joojjang committed Nov 20, 2024
1 parent 5aeb9b1 commit 054b8a4
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 63 deletions.
11 changes: 7 additions & 4 deletions src/components/common/CategoryTabBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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);
Expand All @@ -57,9 +58,11 @@ const Wrapper = styled.div`
const TabWrapper = styled.div<TapWrapperProps>`
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')};
Expand Down
9 changes: 4 additions & 5 deletions src/components/common/FakeSearchBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<SearchBarWrapper>
<InputBox onClick={modalOpen}>
<InputBox onClick={() => setIsModalOpen(!isModalOpen)}>
<StyledSearchIcon />
<Input type="text" placeholder={SEARCH_PLACEHOLDER} />
</InputBox>
Expand Down
29 changes: 15 additions & 14 deletions src/components/common/SearchModal/index.tsx
Original file line number Diff line number Diff line change
@@ -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[] = [<RecentSearch />, <PopularSearch />, <Ad />]; // 각 섹션을 리스트로 관리

// todo: 검색어 존재 시 자동 완성 모달 뜨게
// x 버튼 클릭 시 검색 모달 뜨게

return (
<ModalLayout>
<SearchBar goBack={modalClose} />
<SectionsWrapper>
{searchSectionList.map((section, index) => (
<div key={index}>{section}</div>
))}
</SectionsWrapper>
</ModalLayout>
<>
{isModalOpen && (
<ModalLayout>
<SearchBar />
<SectionsWrapper>
{searchSectionList.map((section, index) => (
<div key={index}>{section}</div>
))}
</SectionsWrapper>
</ModalLayout>
)}
</>
);
};

Expand Down
7 changes: 4 additions & 3 deletions src/components/layouts/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@ 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) {
return (
<>
<Logo />
<IconBox>
<IconButton icon="search" onClick={modalOpen} />
<IconButton icon="search" onClick={() => setIsModalOpen(!isModalOpen)} />
{mode === 'user' ? (
<IconButton icon="favorite-default" />
) : (
Expand Down
26 changes: 22 additions & 4 deletions src/components/layouts/SearchBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import styled from '@emotion/styled';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate, useSearchParams } from 'react-router-dom';

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: {
Expand All @@ -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 }) => {
Expand All @@ -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 (
<SearchBarWrapper>
<IconButton icon="arrow-back" onClick={goBack} />
{includeBack && <IconButton icon="arrow-back" onClick={handleClickBack} />}
<InputBox onSubmit={handleSubmit(activeEnter)}>
<StyledSearchIcon />
<Input
Expand All @@ -73,6 +90,7 @@ const SearchBar = ({ includeFavorite = false, goBack }: SearchBarProps) => {
{...register('searchWord', {
validate: (value) => value.trim() !== '' || '공백만 입력할 수 없습니다.',
})}
onClick={() => setIsModalOpen(true)}
/>
{nowSearchWord.trim().length > 0 && <CancelIconButton onClick={handleRemoveSearchWord} />}
</InputBox>
Expand Down
11 changes: 2 additions & 9 deletions src/pages/Categories/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
<Wrapper>
<FakeSearchBar modalOpen={handleModalOpen} />
{isModalOpen && <SearchModal modalClose={() => setIsModalOpen(false)} />}
<FakeSearchBar />
<SearchModal />
<CategoryGrid>
{CATEGORY_LIST.map((category) => (
<CategoryItem key={category.title} title={category.title} src={category.src} />
Expand Down
4 changes: 3 additions & 1 deletion src/pages/Discover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => (
<Wrapper>
<SearchBar />
<SearchBar includeBack={false} includeFavorite={true} />
<SearchModal />
<ContentWrapper>
{/* todo: 폴백 UI 만들기 */}
<ErrorBoundary fallback={<>Error</>}>
Expand Down
11 changes: 2 additions & 9 deletions src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
<Wrapper>
{isModalOpen && <SearchModal modalClose={() => setIsModalOpen(false)} />}
<Header modalOpen={handleModalOpen} />
<Header />
<SearchModal />
<AdBanner adList={AD_LIST} />
{ARTICLE_LIST.map((item) => (
<ArticleBanner
Expand Down
27 changes: 13 additions & 14 deletions src/pages/SearchResults/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Z_INDEX } from '@/styles/constants';
import styled from '@emotion/styled';
import { Suspense, useState } from 'react';
import { Suspense, useEffect, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { useNavigate, useSearchParams } from 'react-router-dom';

import useSearchArtists from '@/apis/search/useSearchArtists';
import useSearchProducts from '@/apis/search/useSearchProducts';
import CategoryTabBar from '@/components/common/CategoryTabBar';
import Loader from '@/components/common/Loader';
import SearchModal from '@/components/common/SearchModal';
import SearchBar from '@/components/layouts/SearchBar';
import { RouterPath } from '@/routes/path';
import useSearchModalStore from '@/store/useSearchModalStore';
import * as G from '@/styles/globalStyles';
import ArtWorkContents from './components/ArtWorkContents';
import ArtistContents from './components/ArtistContents';
Expand All @@ -34,8 +34,15 @@ const SearchResultsContent = () => {
const searchArtistLen = artistsData.length;
const categoryList = ['전체', '작품', '작가'];

const { isModalOpen, setIsModalOpen } = useSearchModalStore();

// 검색 결과로 이동 직후 검색 모달 닫음
useEffect(() => {
setIsModalOpen(false);
}, []);

const goBack = () => {
navigate(RouterPath.categories);
navigate(-1);
};

const handleTabClick = (tab: string) => {
Expand All @@ -44,11 +51,9 @@ const SearchResultsContent = () => {

return (
<PageContainer>
<HeaderSection>
<SearchBar goBack={goBack} />
</HeaderSection>
<SearchBar goBack={goBack} />
{isModalOpen && <SearchModal />}
<CategoryTabBar tabClick={handleTabClick} tabState={selectedTab} tabList={categoryList} />

<ContentSection>
{selectedTab === '전체' && (
<AllContentWrapper>
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions src/store/useSearchModalStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { create } from 'zustand';

type SearchModalState = {
isModalOpen: boolean;
setIsModalOpen: (isModalOpen: boolean) => void;
};

const useSearchModalStore = create<SearchModalState>((set) => ({
isModalOpen: false,
setIsModalOpen: (isModalOpen) => set({ isModalOpen }),
}));

export default useSearchModalStore;

0 comments on commit 054b8a4

Please sign in to comment.