Skip to content

Commit

Permalink
Merge pull request #223 from sopt-makers/feat/#219_dropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
SeojinSeojin authored Oct 23, 2023
2 parents f03f688 + 061972f commit 00242c7
Show file tree
Hide file tree
Showing 19 changed files with 261 additions and 471 deletions.
62 changes: 62 additions & 0 deletions src/components/common/Select/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import useOutsideClickListener from '@src/hooks/useOutsideClickListener';
import { LabelKeyType } from '@src/lib/types/universal';
import { useCallback, useRef, useState } from 'react';
import { S } from './style';

interface SelectProps<T extends LabelKeyType> {
options: T[];
baseValue: T;
baseLabel: string;
selectedValue: T;
setSelectedValue: React.Dispatch<React.SetStateAction<T>>;
labels: Record<T, string>;
}

export default function Select<T extends LabelKeyType>({
options,
selectedValue,
setSelectedValue,
baseValue,
baseLabel,
labels,
}: SelectProps<T>) {
const [isOpen, setIsOpen] = useState(false);

const toggleSelect = () => setIsOpen(!isOpen);

const handleSelect = (value: T) => {
setSelectedValue(value);
};

const closeSelectItem = useCallback(() => setIsOpen(false), []);

const selectItemWrapperRef = useRef<HTMLDivElement>(null);
const selectTriggerRef = useRef<HTMLButtonElement>(null);
useOutsideClickListener([selectItemWrapperRef, selectTriggerRef], closeSelectItem);

return (
<div>
<S.SelectTrigger
ref={selectTriggerRef}
onClick={toggleSelect}
isSelectionExist={selectedValue !== baseValue}
isOpened={isOpen}
>
{selectedValue === baseValue ? baseLabel : labels[selectedValue]}
</S.SelectTrigger>
{isOpen && (
<S.SelectItemWrapper ref={selectItemWrapperRef}>
{options.map((option, index) => (
<S.SelectItem
key={index}
isSelected={selectedValue === option}
onClick={() => handleSelect(option)}
>
{labels[option]}
</S.SelectItem>
))}
</S.SelectItemWrapper>
)}
</div>
);
}
70 changes: 70 additions & 0 deletions src/components/common/Select/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import styled from '@emotion/styled';
import arrowDown from '@src/assets/icons/arrow_down.svg';
import { colors } from '@src/lib/styles/colors';

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 ? colors.gray700 : colors.gray600};
border: 1px solid;
border-color: ${({ isSelectionExist }) => (isSelectionExist ? colors.gray200 : colors.gray700)};
&::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 ? colors.gray400 : 'transparent')};
padding: 6px 10px;
border-radius: 4px;
cursor: pointer;
transition: 0.1s;
color: ${colors.gray30};
`;

const SelectItemWrapper = styled.div`
display: flex;
flex-direction: column;
position: absolute;
background-color: ${colors.gray600};
z-index: 200;
width: 110px;
border-radius: 20px;
padding: 9px 12px;
margin-top: 8px;
gap: 8px;
&:hover {
${SelectItem} {
background-color: transparent;
&:hover {
background-color: ${colors.gray400};
}
}
}
`;

export const S = { SelectWrapper, SelectTrigger, SelectItem, SelectItemWrapper };
26 changes: 26 additions & 0 deletions src/hooks/useOutsideClickListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect } from 'react';

const useOutsideClickListener = (targetRefs: React.RefObject<Node>[], callback: () => void) => {
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target;
if (target instanceof Node) {
const isOutsideClick = targetRefs.every(
(targetRef: React.RefObject<Node>) =>
targetRef.current === null || !targetRef.current.contains(target),
);
if (isOutsideClick) {
callback();
}
}
};

document.addEventListener('mousedown', handleClickOutside);

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [targetRefs, callback]);
};

export default useOutsideClickListener;
2 changes: 0 additions & 2 deletions src/lib/constants/gtmClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ type GtmClass = {
informationCard프로젝트: string;
informationCardFAQ: string;
informationCardYoutube: string;
projectFilter: string;
};

export const GTM_CLASS: GtmClass = {
projectCard: 'project-card',
informationCard프로젝트: 'information_card_project',
informationCardFAQ: 'information_card_faq',
informationCardYoutube: 'information_card_youtube',
projectFilter: 'project_filter',
};
26 changes: 15 additions & 11 deletions src/lib/constants/project.ts
Original file line number Diff line number Diff line change
@@ -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, string> = {
[ProjectCategoryType.ALL]: '전체',
[ProjectCategoryType.APPJAM]: '앱잼',
[ProjectCategoryType.SOPKATHON]: '솝커톤',
[ProjectCategoryType.SOPTERM]: '솝텀',
[ProjectCategoryType.STUDY]: '스터디',
[ProjectCategoryType.JOINTSEMINAR]: '합동 세미나',
[ProjectCategoryType.ETC]: '기타',
};
2 changes: 2 additions & 0 deletions src/lib/types/universal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ type TabTypeOption<T> = {

export type TabType = TabTypeOption<Part>;
export type ExtraTabType = TabTypeOption<ExtraPart>;

export type LabelKeyType = string | number | symbol;
24 changes: 21 additions & 3 deletions src/views/ProjectPage/ProjectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
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>(ProjectCategoryType.ALL);
const state = useFetch(selectedCategory);

return (
<PageLayout showScrollTopButton>
<ProjectFilter selectedCategory={selectedCategory} setCategory={setCategory} />
<ProjectList state={state} selectedCategory={selectedCategory} />
<Root>
<ContentWrapper>
<SectionTitle>SOPT에서 진행된 프로젝트 둘러보기</SectionTitle>
<Select
options={activeProjectCategoryList}
labels={projectCategoryLabel}
baseLabel="활동"
selectedValue={selectedCategory}
setSelectedValue={setCategory}
baseValue={ProjectCategoryType.ALL}
/>
<ProjectList
state={state}
selectedCategory={selectedCategory ?? ProjectCategoryType.ALL}
/>
</ContentWrapper>
</Root>
</PageLayout>
);
}
Expand Down
49 changes: 0 additions & 49 deletions src/views/ProjectPage/components/filter/DesktopFilter.tsx

This file was deleted.

27 changes: 0 additions & 27 deletions src/views/ProjectPage/components/filter/MobileFilter.tsx

This file was deleted.

55 changes: 0 additions & 55 deletions src/views/ProjectPage/components/filter/MobileFilterModal.tsx

This file was deleted.

Loading

0 comments on commit 00242c7

Please sign in to comment.