From 20d7e50ed13df4e89e0cf5c41b4befbf0195c9d1 Mon Sep 17 00:00:00 2001 From: SeojinSeojin <1106laura@naver.com> Date: Thu, 19 Oct 2023 19:42:26 +0900 Subject: [PATCH 1/8] feat: implement useOutsideClickListener --- src/hooks/useOutsideClickListener.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/hooks/useOutsideClickListener.ts diff --git a/src/hooks/useOutsideClickListener.ts b/src/hooks/useOutsideClickListener.ts new file mode 100644 index 00000000..4fcb5d96 --- /dev/null +++ b/src/hooks/useOutsideClickListener.ts @@ -0,0 +1,26 @@ +import { useEffect } from 'react'; + +const useOutsideClickListener = (targetRefs: React.RefObject[], callback: () => void) => { + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target; + if (target instanceof Node) { + const isOutsideClick = targetRefs.every( + (targetRef: React.RefObject) => + targetRef.current === null || !targetRef.current.contains(target), + ); + if (isOutsideClick) { + callback(); + } + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [targetRefs, callback]); +}; + +export default useOutsideClickListener; From 04cfb1c2f46b3c142c11f882ac69b4d69cff46c1 Mon Sep 17 00:00:00 2001 From: SeojinSeojin <1106laura@naver.com> Date: Thu, 19 Oct 2023 19:42:52 +0900 Subject: [PATCH 2/8] feat: add Select component for dropdown selections --- src/components/common/Select/index.tsx | 63 ++++++++++++++++++++++++ src/components/common/Select/style.ts | 68 ++++++++++++++++++++++++++ src/lib/types/universal.ts | 2 + 3 files changed, 133 insertions(+) create mode 100644 src/components/common/Select/index.tsx create mode 100644 src/components/common/Select/style.ts diff --git a/src/components/common/Select/index.tsx b/src/components/common/Select/index.tsx new file mode 100644 index 00000000..ed5e9f23 --- /dev/null +++ b/src/components/common/Select/index.tsx @@ -0,0 +1,63 @@ +import { useCallback, useRef, useState } from 'react'; +import useOutsideClickListener from '@src/hooks/useOutsideClickListener'; +import { LabelKeyType } from '@src/lib/types/universal'; +import { St } from './style'; + +interface SelectProps { + options: T[]; + baseValue: T; + baseLabel: string; + selectedValue: T; + setSelectedValue: React.Dispatch>; + labels: Record; +} + +export default function Select({ + options, + selectedValue, + setSelectedValue, + baseValue, + baseLabel, + labels, +}: SelectProps) { + const [isOpen, setIsOpen] = useState(false); + + const toggleSelect = () => setIsOpen(!isOpen); + + const handleSelect = (value: T) => { + setSelectedValue(value); + setIsOpen(false); + }; + + const closeSelectItem = useCallback(() => setIsOpen(false), []); + + const selectItemWrapperRef = useRef(null); + const selectTriggerRef = useRef(null); + useOutsideClickListener([selectItemWrapperRef, selectTriggerRef], closeSelectItem); + + return ( +
+ + {selectedValue === baseValue ? baseLabel : labels[selectedValue]} + + {isOpen && ( + + {options.map((option, index) => ( + handleSelect(option)} + > + {labels[option]} + + ))} + + )} +
+ ); +} diff --git a/src/components/common/Select/style.ts b/src/components/common/Select/style.ts new file mode 100644 index 00000000..474533f7 --- /dev/null +++ b/src/components/common/Select/style.ts @@ -0,0 +1,68 @@ +import styled from '@emotion/styled'; +import arrowDown from '@src/assets/icons/arrow_down.svg'; + +const SelectWrapper = styled.div` + position: relative; +`; + +const SelectTrigger = styled.button<{ isSelectionExist: boolean; isOpened: boolean }>` + cursor: pointer; + position: relative; + width: 110px; + font-size: 16px; + font-weight: 500; + padding: 9px 22px; + text-align: left; + color: white; + border-radius: 20px; + background-color: ${({ isSelectionExist }) => (isSelectionExist ? '#202025' : '#1c1d1e')}; + border: 1px solid; + border-color: ${({ isSelectionExist }) => (isSelectionExist ? '#808388' : '#202025')}; + &::after { + content: ''; + background-repeat: no-repeat; + background-position: center; + position: absolute; + right: 22px; + top: 9px; + transition: 0.2s; + width: 10px; + height: 18px; + background-image: url(${arrowDown}); + transform: ${({ isOpened }) => (isOpened ? 'rotate(180deg)' : 'none')}; + } +`; + +const SelectItem = styled.div<{ isSelected: boolean }>` + background-color: ${({ isSelected }) => (isSelected ? '#404045' : 'transparent')}; + padding: 6px 10px; + border-radius: 4px; + cursor: pointer; + transition: 0.1s; + color: #efefef; +`; + +const SelectItemWrapper = styled.div` + display: flex; + flex-direction: column; + + position: absolute; + background-color: #202025; + z-index: 200; + width: 110px; + border-radius: 20px; + padding: 9px 12px; + margin-top: 8px; + gap: 8px; + + &:hover { + ${SelectItem} { + background-color: transparent; + &:hover { + background-color: #404045; + } + } + } +`; + +export const St = { SelectWrapper, SelectTrigger, SelectItem, SelectItemWrapper }; diff --git a/src/lib/types/universal.ts b/src/lib/types/universal.ts index e54adf8f..abf8a68f 100644 --- a/src/lib/types/universal.ts +++ b/src/lib/types/universal.ts @@ -34,3 +34,5 @@ type TabTypeOption = { export type TabType = TabTypeOption; export type ExtraTabType = TabTypeOption; + +export type LabelKeyType = string | number | symbol; From 3083ecf8961c7ffc2cf919930888a20185c6d39a Mon Sep 17 00:00:00 2001 From: SeojinSeojin <1106laura@naver.com> Date: Thu, 19 Oct 2023 19:43:14 +0900 Subject: [PATCH 3/8] feat: implement Select component and modify some styles in ProjectPage --- src/lib/constants/project.ts | 26 ++++---- src/views/ProjectPage/ProjectPage.tsx | 24 ++++++- .../components/filter/ProjectFilter.tsx | 63 +++++++++++++++---- .../components/project/ProjectList.tsx | 2 +- .../project/project-list.module.scss | 38 ++++++----- src/views/ProjectPage/styles.ts | 39 ++++++++++++ 6 files changed, 151 insertions(+), 41 deletions(-) create mode 100644 src/views/ProjectPage/styles.ts diff --git a/src/lib/constants/project.ts b/src/lib/constants/project.ts index 1899a556..5f776bec 100644 --- a/src/lib/constants/project.ts +++ b/src/lib/constants/project.ts @@ -1,14 +1,18 @@ import { ProjectCategoryType } from '@src/lib/types/project'; -export const projectCategoryList: { - type: ProjectCategoryType; - name: string; -}[] = [ - { type: ProjectCategoryType.ALL, name: '์ „์ฒด' }, - { type: ProjectCategoryType.APPJAM, name: '๐ŸŽŠ ์•ฑ์žผ' }, - { type: ProjectCategoryType.SOPKATHON, name: '๐Ÿ’ก ์†์ปคํ†ค' }, - { type: ProjectCategoryType.SOPTERM, name: '๐Ÿ›Ž ์†ํ…€ ํ”„๋กœ์ ํŠธ' }, - { type: ProjectCategoryType.STUDY, name: '๐Ÿ“– ์Šคํ„ฐ๋””' }, - { type: ProjectCategoryType.JOINTSEMINAR, name: '๐Ÿ‘ฅ ํ•ฉ๋™ ์„ธ๋ฏธ๋‚˜' }, - { type: ProjectCategoryType.ETC, name: '๐Ÿ’ฌ ๊ธฐํƒ€' }, +export const activeProjectCategoryList: ProjectCategoryType[] = [ + ProjectCategoryType.ALL, + ProjectCategoryType.APPJAM, + ProjectCategoryType.SOPKATHON, + ProjectCategoryType.SOPTERM, ]; + +export const projectCategoryLabel: Record = { + [ProjectCategoryType.ALL]: '์ „์ฒด', + [ProjectCategoryType.APPJAM]: '์•ฑ์žผ', + [ProjectCategoryType.SOPKATHON]: '์†์ปคํ†ค', + [ProjectCategoryType.SOPTERM]: '์†ํ…€', + [ProjectCategoryType.STUDY]: '์Šคํ„ฐ๋””', + [ProjectCategoryType.JOINTSEMINAR]: 'ํ•ฉ๋™ ์„ธ๋ฏธ๋‚˜', + [ProjectCategoryType.ETC]: '๊ธฐํƒ€', +}; diff --git a/src/views/ProjectPage/ProjectPage.tsx b/src/views/ProjectPage/ProjectPage.tsx index abe9bc0c..1a3b8bbb 100644 --- a/src/views/ProjectPage/ProjectPage.tsx +++ b/src/views/ProjectPage/ProjectPage.tsx @@ -1,8 +1,11 @@ import { useState } from 'react'; import PageLayout from '@src/components/common/PageLayout'; +import Select from '@src/components/common/Select'; +import { activeProjectCategoryList, projectCategoryLabel } from '@src/lib/constants/project'; import { ProjectCategoryType } from '@src/lib/types/project'; -import { ProjectFilter, ProjectList } from './components'; +import { ProjectList } from './components'; import useFetch from './hooks/useFetch'; +import { ContentWrapper, Root, SectionTitle } from './styles'; function Projects() { const [selectedCategory, setCategory] = useState(ProjectCategoryType.ALL); @@ -10,8 +13,23 @@ function Projects() { return ( - - + + + SOPT์—์„œ ์ง„ํ–‰๋œ ํ”„๋กœ์ ํŠธ ๋‘˜๋Ÿฌ๋ณด๊ธฐ +