diff --git a/src/assets/filter/filterDown.svg b/src/assets/filter/filterDown.svg new file mode 100644 index 00000000..d586d99e --- /dev/null +++ b/src/assets/filter/filterDown.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/filter/filterUp.svg b/src/assets/filter/filterUp.svg new file mode 100644 index 00000000..1f06b9c4 --- /dev/null +++ b/src/assets/filter/filterUp.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/searchBar/delete.svg b/src/assets/searchBar/delete.svg new file mode 100644 index 00000000..5838fa55 --- /dev/null +++ b/src/assets/searchBar/delete.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/searchBar/tabDelete.svg b/src/assets/searchBar/tabDelete.svg new file mode 100644 index 00000000..ac5997ee --- /dev/null +++ b/src/assets/searchBar/tabDelete.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/common/Filter.tsx b/src/components/common/Filter.tsx new file mode 100644 index 00000000..6afab5f2 --- /dev/null +++ b/src/components/common/Filter.tsx @@ -0,0 +1,80 @@ +import { colors, typography } from '@/styles/global/global'; +import styled from '@emotion/styled'; +import { useState } from 'react'; +import { IconButton } from './IconButton'; +import downImg from '../../assets/filter/filterDown.svg'; +import upImg from '../../assets/filter/filterUp.svg'; + +interface FilterProps { + filters: string[]; + selectedFilter: string; + setSelectedFilter: (filter: string) => void; +} + +export const Filter = ({ filters, selectedFilter, setSelectedFilter }: FilterProps) => { + const [isOpenModal, setIsOpenModal] = useState(false); + + const handleFilterClick = (filter: string) => { + setIsOpenModal(false); + setSelectedFilter(filter); + }; + + const handleModalClick = () => { + if (!isOpenModal) { + setIsOpenModal(true); + } + }; + + return ( + + {selectedFilter} + + {isOpenModal ? ( + + {filters.map(filter => + filter === selectedFilter ? ( + handleFilterClick(filter)}>{filter} + ) : ( + handleFilterClick(filter)}>{filter} + ), + )} + + ) : ( + <> + )} + + ); +}; + +const Container = styled.div` + display: flex; + position: relative; + justify-content: center; + align-items: center; + width: 85px; +`; + +const Text = styled.p` + color: ${colors.grey[200]}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.regular}; +`; + +const SelectedText = styled.p` + color: ${colors.white}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.regular}; +`; + +const Modal = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + padding: 12px 20px; + position: absolute; + top: 30px; + left: -20px; + border: solid 1px ${colors.grey[200]}; + border-radius: 16px; + background: ${colors.black.main}; +`; diff --git a/src/components/common/SearchBar.tsx b/src/components/common/SearchBar.tsx deleted file mode 100644 index 3aea8c77..00000000 --- a/src/components/common/SearchBar.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import styled from '@emotion/styled'; -import searchIcon from '../../assets/searchBar/search.svg'; -import { IconButton } from './IconButton'; - -interface SearchBarProps { - placeholder: string; -} - -const SearchBar = ({ placeholder }: SearchBarProps) => { - return ( - - - - - ); -}; - -export default SearchBar; - -const SearchBarWrapper = styled.div` - display: flex; - align-items: center; - width: 100%; - height: 40px; - background-color: var(--color-darkgrey-main); - border-radius: 12px; - width: calc(100% - 40px); - margin: 76px 20px 16px 20px; - padding: 16px 20px; - box-sizing: border-box; -`; - -const Input = styled.input` - flex: 1; - border: none; - background: transparent; - color: #ffffff; - font-size: 14px; - padding: 8px 12px; - box-sizing: border-box; - - &::placeholder { - color: var(--color-grey-300); - } - &:focus { - outline: none; - } -`; diff --git a/src/components/common/TitleHeader.tsx b/src/components/common/TitleHeader.tsx index 72c60e9c..255cf953 100644 --- a/src/components/common/TitleHeader.tsx +++ b/src/components/common/TitleHeader.tsx @@ -30,7 +30,7 @@ const NextButton = styled.div<{ active: boolean }>` const InnerHeader = styled.div` position: relative; width: 100%; - height: 56px; + height: 24px; display: flex; flex-direction: row; justify-content: space-between; diff --git a/src/components/group/CompletedGroupModal.tsx b/src/components/group/CompletedGroupModal.tsx index 64af830c..b0954499 100644 --- a/src/components/group/CompletedGroupModal.tsx +++ b/src/components/group/CompletedGroupModal.tsx @@ -3,6 +3,7 @@ import leftArrow from '../../assets/common/leftArrow.svg'; import type { Group } from './MyGroupBox'; import { GroupCard } from './GroupCard'; import TitleHeader from '../common/TitleHeader'; +import { Modal, Overlay } from './Modal.styles'; interface CompletedGroupModalProps { onClose: () => void; @@ -91,7 +92,7 @@ const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => { {userName}님이 참여했던 모임방들을 확인해보세요. {dummyCompletedGroups.map(group => ( - + ))} @@ -101,28 +102,6 @@ const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => { export default CompletedGroupModal; -const Overlay = styled.div` - display: flex; - justify-content: center; - align-items: flex-start; - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background: rgba(0, 0, 0, 1); - z-index: 110; -`; - -const Modal = styled.div` - display: flex; - flex-direction: column; - background-color: var(--color-main-black); - width: 100%; - max-width: 767px; - height: 100vh; -`; - const Text = styled.p` font-size: var(--font-size-medium01); font-weight: var(--font-weight-regular); diff --git a/src/components/group/GroupCard.tsx b/src/components/group/GroupCard.tsx index 6fca81f4..f12b94d3 100644 --- a/src/components/group/GroupCard.tsx +++ b/src/components/group/GroupCard.tsx @@ -2,53 +2,60 @@ import { forwardRef } from 'react'; import styled from '@emotion/styled'; import peopleIcon from '@/assets/common/darkPeople.svg'; import type { Group } from './MyGroupBox'; +import { colors, typography } from '@/styles/global/global'; interface Props { group: Group; - type?: 'recruiting' | 'ongoing'; + isOngoing?: boolean; + type?: 'main' | 'search' | 'modal'; } -export const GroupCard = forwardRef(({ group, type }, ref) => { - return ( - - - - {group.title} - - - people -

{group.participants}

- / {group.maximumParticipants}명 -
- {type === 'recruiting' ? ( - {group.deadLine}일 뒤 모집 마감 - ) : ( - {group.deadLine}일 뒤 종료 - )} -
-
-
- ); -}); +export const GroupCard = forwardRef( + ({ group, isOngoing, type = 'main' }, ref) => { + return ( + + + + {group.title} + + + people +

{group.participants}

+ / {group.maximumParticipants}명 +
+ {isOngoing === true ? ( + {group.deadLine}일 뒤 모집 마감 + ) : ( + {group.deadLine}일 뒤 종료 + )} +
+
+
+ ); + }, +); -const Card = styled.div` +const Card = styled.div<{ cardType: 'main' | 'search' | 'modal' }>` display: flex; align-items: center; - background: var(--color-darkgrey-main); - border-radius: 12px; + background: ${({ cardType }) => + cardType === 'search' ? 'var(--color-black-main)' : 'var(--color-darkgrey-main)'}; + border-radius: ${({ cardType }) => (cardType === 'search' ? `none` : '12px')}; box-sizing: border-box; - padding: 12px; + padding: ${({ cardType }) => (cardType === 'search' ? '24px 12px 12px 12px' : '12px')}; gap: 12px; width: 100%; - border: 1px solid var(--color-grey-300); + border: ${({ cardType }) => (cardType === 'main' ? '1px solid var(--color-grey-300)' : 'none')}; + border-top: ${({ cardType }) => + cardType === 'search' ? `1px solid ${colors.darkgrey.dark}` : ''}; `; -const Cover = styled.img` - width: 80px; - height: 107px; - border-radius: 6px; +const Cover = styled.img<{ cardType: 'main' | 'search' | 'modal' }>` object-fit: cover; flex-shrink: 0; + + width: ${({ cardType }) => (cardType === 'search' ? '60px' : '80px')}; + height: ${({ cardType }) => (cardType === 'search' ? '80px' : '107px')}; `; const Info = styled.div` @@ -59,8 +66,8 @@ const Info = styled.div` `; const Title = styled.h3` - font-size: var(--font-size-large01); - font-weight: var(--font-weight-bold); + font-size: ${typography.fontSize.lg}; + font-weight: ${typography.fontWeight.semibold}; color: #ffffff; margin-bottom: 10px; line-height: 1.4; diff --git a/src/components/group/Modal.styles.ts b/src/components/group/Modal.styles.ts new file mode 100644 index 00000000..c00958aa --- /dev/null +++ b/src/components/group/Modal.styles.ts @@ -0,0 +1,24 @@ +import { colors } from '@/styles/global/global'; +import styled from '@emotion/styled'; + +export const Overlay = styled.div` + display: flex; + justify-content: center; + align-items: flex-start; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: ${colors.black.main}; + z-index: 110; +`; + +export const Modal = styled.div` + display: flex; + flex-direction: column; + background: ${colors.black.main}; + width: 100%; + max-width: 767px; + height: 100vh; +`; diff --git a/src/components/group/MyGroupModal.tsx b/src/components/group/MyGroupModal.tsx index d636da46..dc036d90 100644 --- a/src/components/group/MyGroupModal.tsx +++ b/src/components/group/MyGroupModal.tsx @@ -4,6 +4,7 @@ import TitleHeader from '../common/TitleHeader'; import leftArrow from '../../assets/common/leftArrow.svg'; import type { Group } from './MyGroupBox'; import { GroupCard } from './GroupCard'; +import { Modal, Overlay } from './Modal.styles'; interface MyGroupModalProps { onClose: () => void; @@ -116,7 +117,8 @@ export const MyGroupModal = ({ onClose }: MyGroupModalProps) => { ))} @@ -125,28 +127,6 @@ export const MyGroupModal = ({ onClose }: MyGroupModalProps) => { ); }; -const Overlay = styled.div` - display: flex; - justify-content: center; - align-items: flex-start; - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background: rgba(0, 0, 0, 1); - z-index: 110; -`; - -const Modal = styled.div` - display: flex; - flex-direction: column; - background-color: var(--color-main-black); - width: 100%; - max-width: 767px; - height: 100vh; -`; - const TabContainer = styled.div` display: flex; gap: 8px; diff --git a/src/components/group/RecruitingGroupBox.tsx b/src/components/group/RecruitingGroupBox.tsx index 2ca0c816..2b8c134b 100644 --- a/src/components/group/RecruitingGroupBox.tsx +++ b/src/components/group/RecruitingGroupBox.tsx @@ -27,7 +27,7 @@ export function RecruitingGroupBox({ groups, title }: Props) { {filtered.map(group => ( - + ))} diff --git a/src/components/search/RecentSearchTabs.tsx b/src/components/search/RecentSearchTabs.tsx new file mode 100644 index 00000000..8ee0859e --- /dev/null +++ b/src/components/search/RecentSearchTabs.tsx @@ -0,0 +1,71 @@ +import { colors, typography } from '@/styles/global/global'; +import styled from '@emotion/styled'; +import tabDeleteIcon from '../../assets/searchBar/tabDelete.svg'; +import { IconButton } from '../common/IconButton'; + +interface RecentSearchTabsProps { + recentSearches: string[]; + handleDelete: (term: string) => void; + handleRecentSearchClick: (term: string) => void; +} + +const RecentSearchTabs = ({ + recentSearches, + handleDelete, + handleRecentSearchClick, +}: RecentSearchTabsProps) => { + return ( + + 최근 검색어 + + {recentSearches.length === 0 ? ( + 최근 검색어가 아직 없어요. + ) : ( + recentSearches.map(recentSearch => ( + + handleRecentSearchClick(recentSearch)}>{recentSearch} + handleDelete(recentSearch)} /> + + )) + )} + + + ); +}; + +export default RecentSearchTabs; + +const Container = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + margin: 0 20px; +`; + +const Title = styled.h2` + color: ${colors.grey[100]}; + font-size: ${typography.fontSize.lg}; + font-weight: ${typography.fontWeight.semibold}; +`; + +const TabContainer = styled.div` + display: flex; + gap: 16px; + flex-wrap: wrap; +`; + +const Tab = styled.span` + display: flex; + justify-content: space-between; + border: 1px solid ${colors.grey[300]}; + border-radius: 20px; + padding: 8px 12px; +`; + +const Text = styled.p` + color: ${colors.grey[200]}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.regular}; + text-align: center; + line-height: 24px; +`; diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx new file mode 100644 index 00000000..0d6db926 --- /dev/null +++ b/src/components/search/SearchBar.tsx @@ -0,0 +1,80 @@ +import styled from '@emotion/styled'; +import searchIcon from '../../assets/searchBar/search.svg'; + +import deleteIcon from '../../assets/searchBar/delete.svg'; +import { IconButton } from '../common/IconButton'; + +interface SearchBarProps { + placeholder?: string; + value?: string; + onChange?: (v: string) => void; + onClick?: () => void; + onSearch?: () => void; +} + +const SearchBar = ({ placeholder, value, onChange, onClick, onSearch }: SearchBarProps) => { + return ( + + onChange?.(e.target.value)} + onKeyDown={e => { + if (e.key === 'Enter') { + onSearch?.(); + } + }} + /> + + {value && onChange?.('')} />} + onSearch?.()} /> + + + ); +}; + +export default SearchBar; + +const SearchBarWrapper = styled.div` + display: flex; + align-items: center; + width: 100%; + height: 40px; + background-color: var(--color-darkgrey-main); + border-radius: 12px; + width: calc(100% - 40px); + margin: 76px 20px 16px 20px; + padding: 16px 20px; + box-sizing: border-box; + + .delete-btn { + opacity: 0; + pointer-events: none; + transition: opacity 0.2s; + } + &:focus-within .delete-btn { + opacity: 1; + pointer-events: auto; + } +`; + +const Input = styled.input` + flex: 1; + border: none; + background: transparent; + color: #ffffff; + font-size: 14px; + padding: 8px 0; + box-sizing: border-box; + + &::placeholder { + color: var(--color-grey-300); + } + &:focus { + outline: none; + } +`; +const IconButtonContainer = styled.div` + display: flex; + gap: 20px; +`; diff --git a/src/components/search/SearchResult.tsx b/src/components/search/SearchResult.tsx new file mode 100644 index 00000000..c65d3a11 --- /dev/null +++ b/src/components/search/SearchResult.tsx @@ -0,0 +1,210 @@ +import styled from '@emotion/styled'; +import { useState } from 'react'; +import type { Group } from '../group/MyGroupBox'; +import { GroupCard } from '../group/GroupCard'; +import { colors, typography } from '@/styles/global/global'; +import { Filter } from '../common/Filter'; + +const GENRE = ['문학', '과학·IT', '사회과학', '인문학', '예술']; + +const FILTER = ['마감임박순', '인기순']; + +const dummyMyGroups: Group[] = [ + { + id: '1', + title: '시집만 읽는 사람들 3월', + userName: 'hoho2', + participants: 15, + maximumParticipants: 30, + coverUrl: + 'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg', + deadLine: 1, + genre: '문학', + isOnGoing: true, + }, + { + id: '2', + title: '시집만 읽는 사람들 3월', + userName: 'hoho2', + participants: 15, + maximumParticipants: 30, + coverUrl: + 'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg', + deadLine: 2, + genre: '문학', + isOnGoing: true, + }, + { + id: '3', + title: '시집만 읽는 사람들 3월', + userName: 'hoho2', + participants: 15, + maximumParticipants: 30, + coverUrl: + 'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg', + deadLine: 3, + genre: '문학', + isOnGoing: true, + }, + { + id: '4', + title: '시집만 읽는 사람들 3월', + userName: 'hoho2', + participants: 15, + maximumParticipants: 30, + coverUrl: + 'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg', + deadLine: 4, + genre: '문학', + isOnGoing: true, + }, + { + id: '5', + title: '시집만 읽는 사람들 3월', + userName: 'hoho2', + participants: 15, + maximumParticipants: 30, + coverUrl: + 'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg', + deadLine: 4, + genre: '과학·IT', + isOnGoing: false, + }, + { + id: '6', + title: '시집만 읽는 사람들 3월', + userName: 'hoho2', + participants: 15, + maximumParticipants: 30, + coverUrl: + 'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg', + deadLine: 4, + genre: '과학·IT', + isOnGoing: false, + }, +]; + +const SearchResult = () => { + const [selected, setSelected] = useState(''); + const [showGroup, setShowGroup] = useState(dummyMyGroups); + const [selectedFilter, setSelectedFilter] = useState('마감임박순'); + + const handleSelectTab = (tab: string) => { + if (selected === tab) { + setSelected(''); + } else setSelected(tab); + }; + + const isEmptyShowGroup = () => { + if (showGroup.length === 0) { + return true; + } else return false; + }; + + return ( + <> + + {GENRE.map(tab => ( + handleSelectTab(tab)}> + {tab} + + ))} + + + 전체 {showGroup.length} + + + + {isEmptyShowGroup() ? ( + + 해당하는 모임방이 없어요 + 직접 모임방을 만들어보세요. + + ) : ( + showGroup.map(group => ( + + )) + )} + + + ); +}; + +export default SearchResult; + +const TabContainer = styled.div` + display: flex; + flex-wrap: wrap; + gap: 12px; + padding: 0 20px; + margin-bottom: 24px; +`; + +const Tab = styled.button<{ selected?: boolean }>` + white-space: nowrap; + padding: 8px 12px; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.regular}; + border: none; + border-radius: 16px; + background: ${({ selected }) => + selected ? 'var(--color-purple-main)' : 'var(--color-darkgrey-main)'}; + color: #fff; + cursor: pointer; +`; + +const Content = styled.div` + display: flex; + flex-direction: column; + gap: 20px; + overflow-y: auto; + padding: 0 20px; +`; + +const GroupCardHeader = styled.div` + display: flex; + justify-content: space-between; + padding: 0 20px; + margin-bottom: 10px; +`; + +const GroupNum = styled.span` + display: flex; + align-items: center; + color: ${colors.grey[100]}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.medium}; +`; + +const EmptyContent = styled.div` + display: flex; + height: 100vh; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; +`; + +const EmptyMainText = styled.p` + color: ${colors.white}; + font-size: ${typography.fontSize.lg}; + font-weight: ${typography.fontWeight.semibold}; + text-align: center; + justify-self: center; +`; +const EmptySubText = styled.p` + color: ${colors.grey[100]}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.regular}; + text-align: center; + justify-self: center; +`; diff --git a/src/pages/group/Group.tsx b/src/pages/group/Group.tsx index ca342442..9c69b761 100644 --- a/src/pages/group/Group.tsx +++ b/src/pages/group/Group.tsx @@ -1,6 +1,6 @@ import MainHeader from '@/components/common/MainHeader'; import NavBar from '@/components/common/NavBar'; -import SearchBar from '@/components/common/SearchBar'; +import SearchBar from '@/components/search/SearchBar'; import type { Group as GroupType } from '@/components/group/MyGroupBox'; import { MyGroupBox } from '../../components/group/MyGroupBox'; import Blank from '@/components/common/Blank'; @@ -9,6 +9,7 @@ import { RecruitingGroupCarousel, type Section } from '@/components/group/Recrui import { useState } from 'react'; import { MyGroupModal } from '@/components/group/MyGroupModal'; import CompletedGroupModal from '@/components/group/CompletedGroupModal'; +import { useNavigate } from 'react-router-dom'; import makegroupfab from '../../assets/common/makegroupfab.svg'; const dummyMyGroups: GroupType[] = [ @@ -117,6 +118,7 @@ const sections: Section[] = [ ]; const Group = () => { + const navigate = useNavigate(); const [isMyGroupModalOpen, setIsMyGroupModalOpen] = useState(false); const [isCompletedGroupModalOpen, setIsCompletedGroupModalOpen] = useState(false); @@ -125,12 +127,16 @@ const Group = () => { const openCompletedGroupModal = () => setIsCompletedGroupModalOpen(true); const closeCompletedGroupModal = () => setIsCompletedGroupModalOpen(false); + + const handleSearchBarClick = () => { + navigate('/groupSearch'); + }; return ( {isMyGroupModalOpen && } {isCompletedGroupModalOpen && } - + diff --git a/src/pages/groupSearch/GroupSearch.tsx b/src/pages/groupSearch/GroupSearch.tsx new file mode 100644 index 00000000..c1d73dad --- /dev/null +++ b/src/pages/groupSearch/GroupSearch.tsx @@ -0,0 +1,73 @@ +import TitleHeader from '@/components/common/TitleHeader'; +import { Modal, Overlay } from '@/components/group/Modal.styles'; +import leftArrow from '../../assets/common/leftArrow.svg'; +import { useNavigate } from 'react-router-dom'; +import SearchBar from '@/components/search/SearchBar'; +import { useState } from 'react'; +import RecentSearchTabs from '@/components/search/RecentSearchTabs'; +import SearchResult from '@/components/search/SearchResult'; + +const GroupSearch = () => { + const navigate = useNavigate(); + const [searchTerm, setSearchTerm] = useState(''); + const [isSearching, setIsSearching] = useState(false); + + const [recentSearches, setRecentSearches] = useState([ + '딸기12', + '당근', + '수박245', + '참', + '메론1', + ]); + + const handleSearch = (term: string) => { + setIsSearching(true); + + setRecentSearches(prev => { + const filtered = prev.filter(t => t !== term); + return [term, ...filtered].slice(0, 5); + }); + }; + + const handleDelete = (recentSearch: string) => { + setRecentSearches(prev => prev.filter(t => t !== recentSearch)); + }; + + const handleRecentSearchClick = (recentSearch: string) => { + setSearchTerm(recentSearch); + }; + + const handleBackButton = () => { + navigate('/group'); + }; + return ( + + + } + onLeftClick={handleBackButton} + /> + { + if (searchTerm.trim()) handleSearch(searchTerm.trim()); + }} + /> + {isSearching ? ( + + ) : ( + + )} + + + ); +}; + +export default GroupSearch; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 3b2b967d..b5c5db30 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -12,6 +12,7 @@ import SignupDone from './signup/SignupDone'; import CreateGroup from './group/CreateGroup'; import Group from './group/Group'; import Feed from './feed/Feed'; +import GroupSearch from './groupSearch/groupSearch'; const Router = () => { const router = createBrowserRouter( @@ -25,6 +26,7 @@ const Router = () => { } /> } /> } /> + } /> } /> , ),