-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FE] feat: 리뷰 모아보기 페이지의 공통 컴포넌트 구현 및 퍼블리싱 (#790)
* feat: Switch 컴포넌트 제작 * chore: Switch 컴포넌트 이름 변경 * feat: 리뷰 모아보기에 대한 라우팅 추가 및 임시 페이지 구현 * feat: 리뷰 모아보기와 리뷰 목록 페이지의 공통 레이아웃 제작 * refactor: 공통 레이아웃 제작에 따른 ReviewList 리팩토링 * feat: 리뷰 목록 반응형 적용 * feat: OptionSwitch 반응형 적용 * refactor: ReviewDisplayLayout 반응형 수정 * feat: 공통 Dropdown 컴포넌트 작성 * design: 화살표 버튼 오른쪽 고정 및 옵션을 드래그하지 못하게 수정 * feat: Dropdown 외부 클릭 시 닫히도록 하는 기능 구현 * refactor: Dropdown 로직을 훅으로 분리 * chore: props 명칭 변경 및 선택된 아이템 ellipsis 처리 * feat: Accordion 공통 컴포넌트 작성 * chore: index에 Dropdown, Accordion 추가 * feat: theme에 Dropdown의 z-index 추가 * chore: 누락된 index 추가 * design: Dropdown border 색상 변경 * refactor: Accordion 로직 훅으로 분리 * fix: px을 rem으로 수정 * design: Dropdown 및 Accordion의 margin-bottom 속성 제거 * feat: 초기에 열려있는 Accordion 구현을 위해 prop 추가 * feat: 모아보기 페이지 type 정의 * feat: 모아보기 페이지 목 데이터 작성 * design: Accordion 컴포넌트에서 불필요한 props 제거 * design: Accordion 반응형 구현 * feat: 목 데이터를 사용하여 모아보기 페이지 퍼블리싱 * design: Accordion height 수정 및 스타일 인터페이스 정의 * style: prop명 변경 * design: Dropdown 스타일 인터페이스 정의 * feat: Accordion 제목 왼쪽 정렬 및 애니메이션 추가 * style: 바뀐 prop명 적용 * design: Dropdown이 500px 이하에서 왼쪽 정렬되도록 수정 * merge --------- Co-authored-by: ImxYJL <allensain14@gmail.com>
- Loading branch information
1 parent
a06f159
commit 2878453
Showing
14 changed files
with
464 additions
and
8 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { useEffect, useRef, useState } from 'react'; | ||
|
||
import DownArrowIcon from '@/assets/downArrow.svg'; | ||
import useAccordion from '@/hooks/useAccordion'; | ||
import { EssentialPropsWithChildren } from '@/types'; | ||
|
||
import * as S from './styles'; | ||
|
||
interface AccordionProps { | ||
title: string; | ||
isInitiallyOpened?: boolean; | ||
} | ||
|
||
const Accordion = ({ title, isInitiallyOpened = false, children }: EssentialPropsWithChildren<AccordionProps>) => { | ||
const { isOpened, handleAccordionButtonClick } = useAccordion({ isInitiallyOpened }); | ||
const [contentHeight, setContentHeight] = useState(0); | ||
const contentRef = useRef<HTMLDivElement>(null); | ||
|
||
useEffect(() => { | ||
if (contentRef.current) { | ||
setContentHeight(contentRef.current.clientHeight); | ||
} | ||
}, [isOpened]); | ||
|
||
return ( | ||
<S.AccordionContainer $isOpened={isOpened}> | ||
<S.AccordionButton onClick={handleAccordionButtonClick}> | ||
<S.AccordionTitle>{title}</S.AccordionTitle> | ||
<S.ArrowIcon src={DownArrowIcon} $isOpened={isOpened} alt="" /> | ||
</S.AccordionButton> | ||
<S.AccordionContentsWrapper> | ||
<S.AccordionContents $isOpened={isOpened} $contentHeight={contentHeight} ref={contentRef}> | ||
{children} | ||
</S.AccordionContents> | ||
</S.AccordionContentsWrapper> | ||
</S.AccordionContainer> | ||
); | ||
}; | ||
|
||
export default Accordion; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import styled from '@emotion/styled'; | ||
|
||
interface AccordionStyleProps { | ||
$isOpened: boolean; | ||
$contentHeight?: number; | ||
} | ||
|
||
export const AccordionContainer = styled.div<AccordionStyleProps>` | ||
display: flex; | ||
flex-direction: column; | ||
gap: ${({ $isOpened }) => ($isOpened ? '2rem' : 0)}; | ||
width: 100%; | ||
padding: 1rem; | ||
background-color: ${({ theme, $isOpened }) => ($isOpened ? theme.colors.white : theme.colors.lightGray)}; | ||
border: 0.1rem solid ${({ theme }) => theme.colors.placeholder}; | ||
border-radius: ${({ theme }) => theme.borderRadius.basic}; | ||
&:hover { | ||
border: 0.1rem solid ${({ theme }) => theme.colors.primaryHover}; | ||
} | ||
`; | ||
|
||
export const AccordionButton = styled.button` | ||
display: flex; | ||
gap: 1rem; | ||
align-items: center; | ||
justify-content: space-between; | ||
width: 100%; | ||
height: fit-content; | ||
min-height: 3rem; | ||
`; | ||
|
||
export const AccordionTitle = styled.p` | ||
text-align: left; | ||
::before { | ||
content: 'Q. '; | ||
} | ||
`; | ||
|
||
export const ArrowIcon = styled.img<AccordionStyleProps>` | ||
transform: ${({ $isOpened }) => ($isOpened ? 'rotate(180deg)' : 'rotate(0deg)')}; | ||
transition: transform 0.3s ease-in-out; | ||
`; | ||
|
||
export const AccordionContentsWrapper = styled.div` | ||
overflow: hidden; | ||
`; | ||
|
||
export const AccordionContents = styled.div<AccordionStyleProps>` | ||
margin-top: ${({ $isOpened, $contentHeight }) => ($isOpened ? '0' : `-${$contentHeight}px`)}; | ||
opacity: ${({ $isOpened }) => ($isOpened ? '1' : '0')}; | ||
transition: 0.3s ease; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import DownArrowIcon from '@/assets/downArrow.svg'; | ||
import useDropdown from '@/hooks/useDropdown'; | ||
|
||
import * as S from './styles'; | ||
|
||
interface DropdownItem { | ||
text: string; | ||
value: string; | ||
} | ||
|
||
interface DropdownProps { | ||
items: DropdownItem[]; | ||
selectedItem: string; | ||
handleSelect: (item: string) => void; | ||
} | ||
|
||
const Dropdown = ({ items, selectedItem: selectedOption, handleSelect }: DropdownProps) => { | ||
const { isOpened, handleDropdownButtonClick, handleOptionClick, dropdownRef } = useDropdown({ handleSelect }); | ||
|
||
return ( | ||
<S.DropdownContainer ref={dropdownRef}> | ||
<S.DropdownButton onClick={handleDropdownButtonClick}> | ||
<S.SelectedOption>{selectedOption}</S.SelectedOption> | ||
<S.ArrowIcon src={DownArrowIcon} $isOpened={isOpened} alt="" /> | ||
</S.DropdownButton> | ||
{isOpened && ( | ||
<S.ItemContainer> | ||
{items.map((item) => { | ||
return ( | ||
<S.DropdownItem key={item.value} onClick={() => handleOptionClick(item.value)}> | ||
{item.text} | ||
</S.DropdownItem> | ||
); | ||
})} | ||
</S.ItemContainer> | ||
)} | ||
</S.DropdownContainer> | ||
); | ||
}; | ||
|
||
export default Dropdown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import styled from '@emotion/styled'; | ||
|
||
interface DropdownStyleProps { | ||
$isOpened: boolean; | ||
} | ||
|
||
export const DropdownContainer = styled.div` | ||
position: relative; | ||
display: flex; | ||
flex-direction: column; | ||
width: 24rem; | ||
`; | ||
|
||
export const DropdownButton = styled.button` | ||
display: flex; | ||
gap: 1rem; | ||
justify-content: space-between; | ||
width: 100%; | ||
padding: 1rem; | ||
background-color: ${({ theme }) => theme.colors.white}; | ||
border: 0.1rem solid ${({ theme }) => theme.colors.placeholder}; | ||
border-radius: ${({ theme }) => theme.borderRadius.basic}; | ||
&:hover { | ||
background-color: ${({ theme }) => theme.colors.lightGray}; | ||
} | ||
`; | ||
|
||
export const SelectedOption = styled.p` | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
white-space: nowrap; | ||
`; | ||
|
||
export const ArrowIcon = styled.img<DropdownStyleProps>` | ||
transform: ${({ $isOpened }) => ($isOpened ? 'rotate(180deg)' : 'rotate(0deg)')}; | ||
transition: transform 0.3s ease-in-out; | ||
`; | ||
|
||
export const ItemContainer = styled.ul` | ||
position: absolute; | ||
z-index: ${({ theme }) => theme.zIndex.dropdown}; | ||
top: 100%; | ||
overflow: hidden; | ||
width: 100%; | ||
border: 0.1rem solid ${({ theme }) => theme.colors.placeholder}; | ||
border-radius: ${({ theme }) => theme.borderRadius.basic}; | ||
`; | ||
|
||
export const DropdownItem = styled.li` | ||
cursor: pointer; | ||
user-select: none; | ||
display: flex; | ||
align-items: center; | ||
width: 100%; | ||
height: 4rem; | ||
padding: 0 1rem; | ||
background-color: ${({ theme }) => theme.colors.white}; | ||
&:hover { | ||
background-color: ${({ theme }) => theme.colors.lightGray}; | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { useState } from 'react'; | ||
|
||
interface UseAccordionProps { | ||
isInitiallyOpened: boolean; | ||
} | ||
|
||
const useAccordion = ({ isInitiallyOpened }: UseAccordionProps) => { | ||
const [isOpened, setIsOpened] = useState(isInitiallyOpened); | ||
|
||
const handleAccordionButtonClick = () => { | ||
setIsOpened((prev) => !prev); | ||
}; | ||
|
||
return { | ||
isOpened, | ||
handleAccordionButtonClick, | ||
}; | ||
}; | ||
|
||
export default useAccordion; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { useEffect, useRef, useState } from 'react'; | ||
|
||
interface UseDropdownProps { | ||
handleSelect: (option: string) => void; | ||
} | ||
|
||
const useDropdown = ({ handleSelect }: UseDropdownProps) => { | ||
const [isOpened, setIsOpened] = useState(false); | ||
|
||
const dropdownRef = useRef<HTMLDivElement>(null); | ||
|
||
const handleDropdownButtonClick = () => { | ||
setIsOpened((prev) => !prev); | ||
}; | ||
|
||
const handleOptionClick = (option: string) => { | ||
handleSelect(option); | ||
setIsOpened(false); | ||
}; | ||
|
||
const handleClickOutside = (event: MouseEvent) => { | ||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { | ||
setIsOpened(false); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
document.addEventListener('mousedown', handleClickOutside); | ||
|
||
return () => { | ||
document.removeEventListener('mousedown', handleClickOutside); | ||
}; | ||
}, [dropdownRef]); | ||
|
||
return { isOpened, handleDropdownButtonClick, handleOptionClick, dropdownRef }; | ||
}; | ||
|
||
export default useDropdown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { GroupedReviews, GroupedSection, ReviewSummary } from '@/types'; | ||
|
||
export const REVIEW_SUMMARY_MOCK_DATA: ReviewSummary = { | ||
projectName: '리뷰미', | ||
revieweeName: '에프이', | ||
reviewCount: 5, | ||
}; | ||
|
||
export const GROUPED_SECTION_MOCK_DATA: GroupedSection = { | ||
sections: [ | ||
{ id: 0, name: '강점 카테고리' }, | ||
{ id: 1, name: '커뮤니케이션, 협업 능력' }, | ||
{ id: 2, name: '문제 해결 능력' }, | ||
{ id: 3, name: '시간 관리 능력' }, | ||
{ id: 4, name: '기술 역량, 전문 지식' }, | ||
{ id: 5, name: '성장 마인드셋' }, | ||
{ id: 6, name: '단점 피드백' }, | ||
{ id: 7, name: '추가 리뷰 및 응원' }, | ||
], | ||
}; | ||
|
||
export const GROUPED_REVIEWS_MOCK_DATA: GroupedReviews = { | ||
reviews: [ | ||
{ | ||
question: { | ||
name: '커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요', | ||
type: 'CHECKBOX', | ||
}, | ||
answers: null, | ||
votes: [ | ||
{ content: '반대 의견을 내더라도 듣는 사람이 기분 나쁘지 않게 이야기해요', count: 5 }, | ||
{ content: '팀원들의 의견을 잘 모아서 회의가 매끄럽게 진행되도록 해요', count: 4 }, | ||
{ content: '팀의 분위기를 주도해요', count: 3 }, | ||
{ content: '주장을 이야기할 때에는 합당한 근거가 뒤따라요', count: 2 }, | ||
{ content: '팀에게 필요한 것과 그렇지 않은 것을 잘 구분해요', count: 2 }, | ||
{ content: '팀 내 주어진 요구사항에 우선순위를 잘 매겨요 (커뮤니케이션 능력을 특화하자)', count: 1 }, | ||
{ content: '서로 다른 분야간의 소통도 중요하게 생각해요', count: 1 }, | ||
], | ||
}, | ||
{ | ||
question: { | ||
name: '위에서 선택한 사항에 대해 조금 더 자세히 설명해주세요', | ||
type: 'TEXT', | ||
}, | ||
answers: [ | ||
{ | ||
content: | ||
'장의 시작부분은 짧고 직접적이며, 뒤따라 나올 복잡한 정보를 어떻게 해석해야 할 것인지 프레임을 짜주는 역할을 해야 한다. 그러면 아무리 긴 문장이라도 쉽게 읽힌다.', | ||
}, | ||
{ | ||
content: | ||
'고액공제건강보험과 건강저축계좌를 만들어 노동자와 고용주가 세금공제를 받을 수 있도록 하면 결과적으로 노동자의 의료보험 부담이 커진다.', | ||
}, | ||
{ | ||
content: | ||
'장의 시작부분은 짧고 직접적이며, 뒤따라 나올 복잡한 정보를 어떻게 해석해야 할 것인지 프레임을 짜주는 역할을 해야 한다. 그러면 아무리 긴 문장이라도 쉽게 읽힌다.', | ||
}, | ||
{ | ||
content: | ||
'고액공제건강보험과 건강저축계좌를 만들어 노동자와 고용주가 세금공제를 받을 수 있도록 하면 결과적으로 노동자의 의료보험 부담이 커진다.', | ||
}, | ||
], | ||
votes: null, | ||
}, | ||
], | ||
}; |
Oops, something went wrong.