diff --git a/src/assets/icons/cancel-filled-gray.svg b/src/assets/icons/cancel-filled-gray.svg deleted file mode 100644 index bc49a1f5..00000000 --- a/src/assets/icons/cancel-filled-gray.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/assets/icons/cancel-filled.svg b/src/assets/icons/cancel-filled.svg index c2853515..9b295918 100644 --- a/src/assets/icons/cancel-filled.svg +++ b/src/assets/icons/cancel-filled.svg @@ -1,8 +1,8 @@ - + - + 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/Chip/index.tsx b/src/components/common/Chip/index.tsx index 4f9c1a93..25974664 100644 --- a/src/components/common/Chip/index.tsx +++ b/src/components/common/Chip/index.tsx @@ -1,21 +1,23 @@ import styled from '@emotion/styled'; - -import CancelDefault from '@/assets/icons/cancel-default.svg?react'; import { useNavigate } from 'react-router-dom'; +import CancelIcon from '@/assets/icons/cancel-filled.svg?react'; +import { RouterPath } from '@/routes/path'; + interface ChipProps { tag: string; - onClick: () => void; + onDeleteClick: () => void; } -const Chip = ({ tag, onClick }: ChipProps) => { +const Chip = ({ tag, onDeleteClick }: ChipProps) => { const navigate = useNavigate(); + return ( - - - - + navigate(`/${RouterPath.results}?query=${tag}`)}>{tag} + + + ); }; @@ -26,28 +28,21 @@ const Wrapper = styled.div` border: 1px solid var(--color-gray-md); border-radius: var(--border-radius); font-size: var(--font-size-sm); - padding: 0.6rem; - gap: 0.6rem; - height: 2.3rem; + background-color: var(--color-white); + padding: 4px 8px; + gap: 6px; display: inline-flex; align-items: center; - justify-content: space-between; `; -const CancelIconButton = styled.button` - cursor: pointer; - width: 15px; - height: 15px; - background-color: var(--color-white); +const DeleteButton = styled.button` + width: 14px; + height: 14px; transition: fill 0.3s ease; - & svg { + svg { width: 100%; height: 100%; - } - - &:hover { - stroke: var(--color-black); - fill: var(--color-black); + color: var(--color-black); } `; 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/PopularSearchItem/index.tsx b/src/components/common/PopularSearchItem/index.tsx index 958ae8a0..1163f795 100644 --- a/src/components/common/PopularSearchItem/index.tsx +++ b/src/components/common/PopularSearchItem/index.tsx @@ -2,15 +2,15 @@ import styled from '@emotion/styled'; import { Text } from '@chakra-ui/react'; interface PopularSearchItemProps { - text: string; + name: string; rank: number; } -const PopularSearchItem = ({ text, rank }: PopularSearchItemProps) => { +const PopularSearchItem = ({ name, rank }: PopularSearchItemProps) => { return ( {rank} - #{text} + {name} ); }; diff --git a/src/components/common/ProfileImage/index.tsx b/src/components/common/ProfileImage/index.tsx index 55fec96a..6b02ecbc 100644 --- a/src/components/common/ProfileImage/index.tsx +++ b/src/components/common/ProfileImage/index.tsx @@ -24,7 +24,9 @@ const StyledProfileImage = styled.div<{ width: number }>` border: 1px solid var(--color-gray-md); background-color: var(--color-gray-lt); - .img { + img { object-fit: cover; + width: 100%; + height: 100%; } `; diff --git a/src/components/common/SearchModal/Ad.tsx b/src/components/common/SearchModal/Ad.tsx new file mode 100644 index 00000000..00ae6028 --- /dev/null +++ b/src/components/common/SearchModal/Ad.tsx @@ -0,0 +1,53 @@ +import styled from '@emotion/styled'; + +import { AD_LIST } from '@/constants/search'; +import * as S from './styles'; + +const SearchAd = () => { + return ( + + + 요즘 뜨는 작품 + + 광고 + + {AD_LIST.map((ad, index) => ( + + Ad image + + ))} + + + ); +}; + +export default SearchAd; + +const AdWrapper = styled.div` + display: flex; + gap: 12px; +`; + +const AdTag = styled.p` + background: var(--color-gray-md); + border-radius: 50px; + padding: 3px 7px; + font-size: var(--font-size-xs); + color: var(--color-white); + font-weight: 500; + position: absolute; + top: 16px; + right: 16px; +`; + +const AdImage = styled.div` + width: 100%; + aspect-ratio: 1 / 1; + background-color: var(--color-gray-lt); + + img { + object-fit: cover; + width: 100%; + height: 100%; + } +`; diff --git a/src/components/common/SearchModal/PopularSearch.tsx b/src/components/common/SearchModal/PopularSearch.tsx index 0eacad97..1a6bf82e 100644 --- a/src/components/common/SearchModal/PopularSearch.tsx +++ b/src/components/common/SearchModal/PopularSearch.tsx @@ -1,63 +1,31 @@ -import { Text } from '@chakra-ui/react'; import styled from '@emotion/styled'; import { POPULAR_SEARCH_LIST } from '@/constants/search'; -import * as G from '@/styles/globalStyles'; import PopularSearchItem from '../PopularSearchItem'; +import * as S from './styles'; const PopularSearch = () => { - const midPoint = Math.ceil(POPULAR_SEARCH_LIST.length / 2); - return ( - - - - 인기 검색어 - - - - - {POPULAR_SEARCH_LIST.slice(0, midPoint).map((item, index) => ( - - ))} - - - {POPULAR_SEARCH_LIST.slice(midPoint).map((item, index) => ( - - ))} - - - + + + 인기 검색어 + + + {POPULAR_SEARCH_LIST.map((item, index) => ( + + ))} + + ); }; export default PopularSearch; -const Wrapper = styled.div` - display: flex; - flex-direction: column; - padding: 16px; -`; - -const TitleWrapper = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; -`; - -const TitleText = styled(Text)` - color: var(--color-black, #020715); - font-size: var(--font-size-md); - font-weight: 700; - line-height: normal; -`; - -const Column = styled.div` - display: flex; - flex-direction: column; - gap: 5px; -`; - -const RedText = styled.span` - color: var(--color-red); +const PopularSearchGrid = styled.div` + display: grid; + grid-auto-flow: column; // 세로 방향 배치 + grid-template-rows: repeat(5, 1fr); + justify-items: flex-start; + gap: 8px 12px; + font-size: var(--font-size-sm); `; diff --git a/src/components/common/SearchModal/RecentSearch.tsx b/src/components/common/SearchModal/RecentSearch.tsx index c6a12a75..048b05ec 100644 --- a/src/components/common/SearchModal/RecentSearch.tsx +++ b/src/components/common/SearchModal/RecentSearch.tsx @@ -1,80 +1,58 @@ -import { Text } from '@chakra-ui/react'; import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; -import Chip from '../Chip'; -export const SEARCH_ARRAY_KEY = 'searchArray'; +import { SEARCH_ARRAY_KEY } from '@/constants/search'; +import Chip from '../Chip'; +import * as S from './styles'; const RecentSearch = () => { const [searchArray, setSearchArray] = useState>([]); - const allDelete = () => { + const deleteAll = () => { setSearchArray([]); localStorage.removeItem(SEARCH_ARRAY_KEY); }; - const handleStoredData = (key: string) => { + const handleStoredKey = (key: string) => { const updatedArray = searchArray.filter((item) => item.key !== key); setSearchArray(updatedArray); localStorage.setItem(SEARCH_ARRAY_KEY, JSON.stringify(updatedArray)); }; useEffect(() => { - const storedData = localStorage.getItem(SEARCH_ARRAY_KEY); - if (storedData) { - setSearchArray(JSON.parse(storedData)); + const storedSearchArray = localStorage.getItem(SEARCH_ARRAY_KEY); + if (storedSearchArray) { + setSearchArray(JSON.parse(storedSearchArray)); } }, []); return ( - - - 최근 검색어 - {searchArray.length > 0 && 모두 삭제하기} - - + + 최근 검색어 + {searchArray.length > 0 && 모두 삭제} + {searchArray.map((item) => ( - handleStoredData(item.key)} /> + handleStoredKey(item.key)} /> ))} - - + + ); }; export default RecentSearch; -const Wrapper = styled.div` - display: flex; - flex-direction: column; - padding: 16px; - margin-top: 41px; -`; - -const ChipWrapper = styled.div` +const RecentSearchWrapper = styled.div` display: flex; flex-wrap: wrap; - gap: 8px; + gap: 6px; `; -const TitleWrapper = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; - margin-bottom: 10px; -`; - -const DelText = styled(Text)` - color: var(--color-gray-deep, #909090); - text-align: right; - font-size: var(--font-size-sm); +const DeleteAllButton = styled.button` + color: var(--color-gray-dk); + font-size: var(--font-size-xs); font-weight: 400; line-height: normal; - cursor: pointer; -`; - -const TitleText = styled(Text)` - color: var(--color-black, #020715); - font-size: var(--font-size-md); - font-weight: 700; - line-height: normal; + position: absolute; + top: 18px; + right: 16px; `; diff --git a/src/components/common/SearchModal/SearchAd.tsx b/src/components/common/SearchModal/SearchAd.tsx deleted file mode 100644 index 8d686d8d..00000000 --- a/src/components/common/SearchModal/SearchAd.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Image } from '@chakra-ui/react'; -import styled from '@emotion/styled'; - -import { AD_LIST } from '@/constants/search'; -import * as G from '@/styles/globalStyles'; - -const SearchAd = () => { - return ( - - -
- 요즘 뜨는 작품 -
- 광고 -
- - {AD_LIST.map((ad) => ( - - ))} - -
- ); -}; - -export default SearchAd; - -const Wrapper = styled.div` - display: flex; - flex-direction: column; - padding: 16px; -`; - -const TitleWrapper = styled.div` - width: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - margin-bottom: 16px; - color: var(--color-black, #020715); - font-size: var(--font-size-md); - font-weight: 700; - line-height: normal; -`; - -const RedText = styled.span` - color: var(--color-red); -`; - -const AdImage = styled(Image)` - width: 100%; - height: 100%; - aspect-ratio: 1 / 1; - max-width: 200px; - max-height: 200px; - background-color: var(--color-gray-lt); -`; - -const Tab = styled.p` - background: var(--color-gray-lt); - border-radius: 50px; - padding: 3px 7px; - justify-content: center; - align-items: center; - gap: 10px; - width: 36px; - height: 20px; - font-size: var(--font-size-xs); - color: var(--white, #fff); - font-weight: 700; -`; diff --git a/src/components/common/SearchModal/index.tsx b/src/components/common/SearchModal/index.tsx index 3fea75d8..18763099 100644 --- a/src/components/common/SearchModal/index.tsx +++ b/src/components/common/SearchModal/index.tsx @@ -1,34 +1,37 @@ import styled from '@emotion/styled'; import SearchBar from '@/components/layouts/SearchBar'; -import { Z_INDEX } from '@/styles/constants'; -import * as G from '@/styles/globalStyles'; +import useSearchModalStore from '@/store/useSearchModalStore'; +import { HEIGHTS, Z_INDEX } from '@/styles/constants'; +import Ad from './Ad'; import PopularSearch from './PopularSearch'; import RecentSearch from './RecentSearch'; -import Ad from './SearchAd'; -interface SearchModalProps { - modalClose: () => void; -} +const SearchModal = () => { + const { isModalOpen } = useSearchModalStore(); + const searchSectionList: React.ReactNode[] = [, , ]; // 각 섹션을 리스트로 관리 + + // todo: 검색어 존재 시 자동 완성 모달 뜨게 -const SearchModal = ({ modalClose }: SearchModalProps) => { return ( - - - - - - - - - - + <> + {isModalOpen && ( + + + + {searchSectionList.map((section, index) => ( +
{section}
+ ))} +
+
+ )} + ); }; export default SearchModal; -const ModalWrapper = styled.div` +const ModalLayout = styled.div` position: fixed; top: 0; left: 0; @@ -40,8 +43,17 @@ const ModalWrapper = styled.div` z-index: ${Z_INDEX.Modal}; `; -const SearchWrapper = styled.div` - flex: 1; +const SectionsWrapper = styled.div` + margin-top: ${HEIGHTS.HEADER}; + width: 100%; display: flex; flex-direction: column; + + & > * { + border-bottom: 1px solid var(--color-gray-lt); + } + + & > *:last-child { + border-bottom: none; + } `; diff --git a/src/components/common/SearchModal/styles.ts b/src/components/common/SearchModal/styles.ts new file mode 100644 index 00000000..b2a4ab40 --- /dev/null +++ b/src/components/common/SearchModal/styles.ts @@ -0,0 +1,21 @@ +import styled from '@emotion/styled'; + +export const SectionWrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + padding: 16px; + position: relative; +`; + +export const SectionTitle = styled.p` + flex-direction: row; + margin-bottom: 12px; + font-size: var(--font-size-md); + font-weight: 700; + + .section-title-highlight { + font-size: inherit; + color: var(--color-red); + } +`; 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 8650a5f8..f065bf0a 100644 --- a/src/components/layouts/SearchBar/index.tsx +++ b/src/components/layouts/SearchBar/index.tsx @@ -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-gray.svg?react'; +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 '@/components/common/SearchModal/RecentSearch'; +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,17 +33,31 @@ 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 }) => { const { searchWord } = data; + + // 검색 기록 업데이트 const storedData = localStorage.getItem(SEARCH_ARRAY_KEY); let searchArray = storedData ? JSON.parse(storedData) : []; const existingIndex = searchArray.findIndex( @@ -56,15 +75,17 @@ 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 && } @@ -138,6 +160,7 @@ const CancelIconButton = styled(CancelIcon)` position: absolute; right: 8px; cursor: pointer; + color: var(--color-gray-dk); `; const ErrorMessage = styled.div` diff --git a/src/constants/categories.ts b/src/constants/categories.ts index 3d50e373..ec948c19 100644 --- a/src/constants/categories.ts +++ b/src/constants/categories.ts @@ -1,53 +1,56 @@ type Category = { - id: number; src: string; - des: string; + title: string; }; export const CATEGORY_LIST: Category[] = [ { - id: 1, src: 'https://images.unsplash.com/photo-1580136608079-72029d0de130?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NHx8JUVCJThGJTk5JUVDJTk2JTkxJUVBJUI3JUI4JUVCJUE2JUJDfGVufDB8fDB8fHww', - des: '동양화/ 한국화', + title: '동양화/한국화', }, { - id: 2, src: 'https://images.unsplash.com/photo-1579783928621-7a13d66a62d1?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8JUVDJTg0JTlDJUVDJTk2JTkxJUVEJTk5JTk0fGVufDB8fDB8fHww', - des: '서양화', + title: '서양화', }, { - id: 3, src: 'https://plus.unsplash.com/premium_photo-1672287578309-2a2115000688?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8JUVDJUExJUIwJUVBJUIwJTgxfGVufDB8fDB8fHww', - des: '조각', + title: '조각', }, { - id: 4, src: 'https://images.unsplash.com/photo-1590605105526-5c08f63f89aa?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8OHx8JUVCJThGJTg0JUVDJTk4JTg4fGVufDB8fDB8fHww', - des: '도예/공예', + title: '도예/공예', }, { - id: 5, src: 'https://images.unsplash.com/photo-1682159672286-40790338349b?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8JUVCJTg5JUI0JUVCJUFGJUI4JUVCJTk0JTk0JUVDJTk2JUI0fGVufDB8fDB8fHww', - des: '뉴미디어', + title: '뉴미디어', }, { - id: 6, src: 'https://plus.unsplash.com/premium_photo-1663100678842-d89cb7b084ee?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTd8fCVFQiU5NCU5NCVFQyU5RSU5MCVFQyU5RCVCOHxlbnwwfHwwfHx8MA%3D%3D', - des: '디자인', + title: '디자인', }, { - id: 7, src: 'https://plus.unsplash.com/premium_photo-1723075214781-ea091374d81c?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8JUVEJThDJTkwJUVEJTk5JTk0fGVufDB8fDB8fHww', - des: '드로잉/판화', + title: '드로잉/판화', }, { - id: 8, src: 'https://images.unsplash.com/photo-1506434304575-afbb92660c28?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Nnx8JUVDJTgyJUFDJUVDJUE3JTg0JUVDJTgyJUFDfGVufDB8fDB8fHww', - des: '사진', + title: '사진', }, { - id: 9, src: 'https://images.unsplash.com/photo-1546638008-efbe0b62c730?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8OHx8JUVCJThGJTk5JUVDJTk2JTkxJUVBJUI3JUI4JUVCJUE2JUJDfGVufDB8fDB8fHww', - des: '서예/캘리그라피', + title: '서예/캘리그라피', + }, +]; + +type Curation = { title: string; des: string }; + +export const CURATION_LIST: Curation[] = [ + { + title: '매거진', + des: '숨겨진 무한의 가치를 발견하고 싶다면', + }, + { + title: '아티스트 그라운드', + des: '내 취향대로 작가 골라보기', }, ]; diff --git a/src/constants/search.ts b/src/constants/search.ts index 55d114e7..b2b597e5 100644 --- a/src/constants/search.ts +++ b/src/constants/search.ts @@ -1,35 +1,40 @@ -type PopularSearch = { - id: number; - text: string; -}; +export const SEARCH_ARRAY_KEY = 'searchArray'; -export const POPULAR_SEARCH_LIST: PopularSearch[] = [ - { id: 1, text: '모던 아트' }, - { id: 2, text: '추상화' }, - { id: 3, text: '인상파' }, - { id: 4, text: '인물화' }, - { id: 5, text: '풍경화' }, - { id: 6, text: '큐비즘' }, - { id: 7, text: '디지털 아트' }, - { id: 8, text: '팝 아트' }, - { id: 9, text: '아크릴화' }, - { id: 10, text: '수채화' }, +export const POPULAR_SEARCH_LIST: string[] = [ + '#모던 아트', + '#추상화', + '#인상파', + '#인물화', + '#풍경화', + '#큐비즘', + '#디지털 아트', + '#팝 아트', + '#아크릴화', + '#수채화', ]; // -type SearchAd = { - id: number; - src: string; +type Ad = { + title: string; + imageUrl: string; + artist: string; + des: string; }; -export const AD_LIST: SearchAd[] = [ +export const AD_LIST: Ad[] = [ { - id: 1, - src: ' https://images.unsplash.com/photo-1579273166152-d725a4e2b755?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTl8fCVFQSVCNyVCOCVFQiVBNiVCQ3xlbnwwfHwwfHx8MA%3D%3D', + title: '', + imageUrl: + 'https://images.unsplash.com/photo-1579273166152-d725a4e2b755?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTl8fCVFQSVCNyVCOCVFQiVBNiVCQ3xlbnwwfHwwfHx8MA%3D%3D', + artist: '', + des: '', }, { - id: 2, - src: 'https://images.unsplash.com/photo-1577083862054-7324cd025fa6?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTR8fCVFQyU4NCU5QyVFQyU5NiU5MSVFRCU5OSU5NHxlbnwwfHwwfHx8MA%3D%3D', + title: '', + imageUrl: + 'https://images.unsplash.com/photo-1577083862054-7324cd025fa6?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTR8fCVFQyU4NCU5QyVFQyU5NiU5MSVFRCU5OSU5NHxlbnwwfHwwfHx8MA%3D%3D', + artist: '', + des: '', }, ]; diff --git a/src/pages/Categories/components/CategoryItem/index.tsx b/src/pages/Categories/components/CategoryItem/index.tsx index 07172d9c..29e80c4e 100644 --- a/src/pages/Categories/components/CategoryItem/index.tsx +++ b/src/pages/Categories/components/CategoryItem/index.tsx @@ -1,41 +1,44 @@ -import { Image } from '@chakra-ui/react'; import styled from '@emotion/styled'; interface CategoryItemProps { - des: string; + title: string; src: string; } -const CategoryItem = ({ des, src }: CategoryItemProps) => { - des = des.replace('/', '/\n'); - +const CategoryItem = ({ title, src }: CategoryItemProps) => { return ( - - {des} + {src && Category thumbnail} + {title} ); }; export default CategoryItem; -const Wrapper = styled.div` +const Wrapper = styled.li` display: flex; flex-direction: column; align-items: center; - cursor: pointer; gap: 8px; + cursor: pointer; `; -const RoundImage = styled(Image)` - aspect-ratio: 1/1; - width: 80%; +const CategoryThumbnail = styled.div` + width: 100%; + aspect-ratio: 1 / 1; border-radius: 100%; - object-fit: cover; + overflow: hidden; background-color: var(--color-gray-lt); + + img { + object-fit: cover; + width: 100%; + height: 100%; + } `; -const DesWrapper = styled.p` +const Title = styled.p` font-size: var(--font-size-sm); display: inline; white-space: pre-wrap; diff --git a/src/pages/Categories/components/CurationItem/index.tsx b/src/pages/Categories/components/CurationItem/index.tsx new file mode 100644 index 00000000..4be4021e --- /dev/null +++ b/src/pages/Categories/components/CurationItem/index.tsx @@ -0,0 +1,43 @@ +import styled from '@emotion/styled'; + +interface CurationItemProps { + title: string; + des: string; +} + +const CurationItem = ({ title, des }: CurationItemProps) => { + return ( + + {title} + {des} + + ); +}; + +export default CurationItem; + +const Wrapper = styled.li` + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + padding: 16px; + min-height: 54px; + gap: 8px; + cursor: pointer; + + &:not(:last-child) { + border-bottom: 1px solid var(--color-gray-lt); + } +`; + +const Title = styled.span` + font-size: var(--font-size-md); + font-weight: 600; + line-height: 1.2; +`; + +const Des = styled.span` + font-size: var(--font-size-sm); + line-height: 1.2; +`; diff --git a/src/pages/Categories/index.tsx b/src/pages/Categories/index.tsx index 83c3737a..b3aec02d 100644 --- a/src/pages/Categories/index.tsx +++ b/src/pages/Categories/index.tsx @@ -1,40 +1,27 @@ -import { Text } from '@chakra-ui/react'; import styled from '@emotion/styled'; -import { useState } from 'react'; import FakeSearchBar from '@/components/common/FakeSearchBar'; import SearchModal from '@/components/common/SearchModal'; -import { CATEGORY_LIST } from '@/constants/categories'; +import { CATEGORY_LIST, CURATION_LIST } from '@/constants/categories'; import * as G from '@/styles/globalStyles'; -import Category from './components/CategoryItem'; +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) => ( - + ))} - - + + - - 매거진 - 숨겨진 무한의 가치를 발견하고 싶다면 - - - - 아티스트 그라운드 - 내 취향대로 작가 골라보기 - + {CURATION_LIST.map((curation) => ( + + ))} ); @@ -46,28 +33,22 @@ const Wrapper = styled.div` width: 100%; `; -const CurationItem = styled.div` - display: flex; - flex-direction: row; - align-items: center; - width: 100%; - padding: 16px; - min-height: 54px; - gap: 8px; -`; - -const Title = styled(Text)` - font-size: var(--font-size-md); - font-weight: 600; - line-height: 1.2; -`; - -const Des = styled(Text)` - font-size: var(--font-size-sm); - line-height: 1.2; +const CategoryGrid = styled.ul` + display: grid; + grid-template-columns: repeat(4, 1fr); + justify-items: center; + padding: 16px 16px 32px 16px; + gap: 24px; + + @media (min-width: 480px) { + grid-template-columns: repeat(5, 1fr); + } + @media (min-width: 600px) { + grid-template-columns: repeat(6, 1fr); + } `; -const CurationWrapper = styled.div` +const CurationWrapper = styled.ul` height: auto; width: 100%; margin-bottom: 54px; 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) => ( { + return ( + Error Status}> + }> + + + + ); +}; + const SearchResultsContent = () => { const [selectedTab, setSelectedTab] = useState('전체'); const [searchParams] = useSearchParams(); @@ -34,8 +44,15 @@ const SearchResultsContent = () => { const searchArtistLen = artistsData.length; const categoryList = ['전체', '작품', '작가']; + const { isModalOpen, setIsModalOpen } = useSearchModalStore(); + + // 검색어 바꿔 새로 검색 시 검색 모달 닫음 + useEffect(() => { + setIsModalOpen(false); + }, [searchQuery]); + const goBack = () => { - navigate(RouterPath.categories); + navigate(-1); }; const handleTabClick = (tab: string) => { @@ -44,11 +61,9 @@ const SearchResultsContent = () => { return ( - - - + + {isModalOpen && } - {selectedTab === '전체' && ( @@ -89,28 +104,12 @@ const SearchResultsContent = () => { ); }; -const SearchResults = () => { - return ( - Error Status}> - }> - - - - ); -}; - export default SearchResults; 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; diff --git a/src/styles/globalStyles.ts b/src/styles/globalStyles.ts index 889c2063..96df705c 100644 --- a/src/styles/globalStyles.ts +++ b/src/styles/globalStyles.ts @@ -13,8 +13,8 @@ export const HorizontalLine = styled.hr` export const Grid = styled.div<{ col: number; justifyItems?: string }>` display: grid; - grid-template-columns: ${({ col }) => (col === 2 ? 'repeat(2, 1fr)' : 'repeat(4, 1fr)')}; + grid-template-columns: ${({ col }) => `repeat(${col}, 1fr)`}; justify-items: ${({ justifyItems }) => justifyItems || 'center'}; - padding: ${({ col }) => (col === 2 ? '16px' : '24px 16px 32px 16px;')}; - gap: ${({ col }) => (col === 2 ? '8px' : '40px 12px')}; + padding: ${({ col }) => (col === 2 ? '16px' : '16px 16px 32px 16px;')}; + gap: ${({ col }) => (col === 2 ? '8px' : '24px')}; `;