-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat-fe: 공통 컴포넌트 SideBar 구현 #271
Changes from all commits
bdefd99
8725969
4296d91
5a95187
c2d546a
d081cbc
b0a69f7
ff5f429
17b705b
33ac81a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import Accordion from './index'; | ||
import S from './style'; | ||
|
||
const meta: Meta<typeof Accordion> = { | ||
title: 'Common/Accordion', | ||
component: Accordion, | ||
parameters: { | ||
layout: 'centered', | ||
docs: { | ||
description: { | ||
component: 'Accordion 컴포넌트는 제목을 클릭하여 내용을 확장하거나 축소할 수 있는 컴포넌트입니다.', | ||
}, | ||
}, | ||
}, | ||
tags: ['autodocs'], | ||
argTypes: { | ||
title: { | ||
description: 'Accordion의 제목입니다.', | ||
control: { type: 'text' }, | ||
table: { | ||
type: { summary: 'string' }, | ||
}, | ||
}, | ||
children: { | ||
description: 'Accordion의 내부 요소입니다.', | ||
control: { type: 'text' }, | ||
table: { | ||
type: { summary: 'React.ReactNode' }, | ||
}, | ||
}, | ||
}, | ||
args: { | ||
title: '공고', | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof Accordion>; | ||
|
||
export const Default: Story = { | ||
render: (args) => ( | ||
<div style={{ width: '200px' }}> | ||
<Accordion {...args}> | ||
<S.ListItem>프론트엔드 7기 모집</S.ListItem> | ||
<S.ListItem>백엔드 7기 모집</S.ListItem> | ||
<S.ListItem>안드로이드 7기 모집</S.ListItem> | ||
</Accordion> | ||
</div> | ||
), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import React, { useState } from 'react'; | ||
import { HiOutlineClipboardList } from 'react-icons/hi'; | ||
|
||
import S from './style'; | ||
import ChevronButton from '../ChevronButton'; | ||
|
||
interface AccordionProps { | ||
title: string; | ||
children: React.ReactNode; | ||
} | ||
|
||
function Accordion({ title, children }: AccordionProps) { | ||
// TODO: 현재 아코디언의 오픈값을 True로 설정합니다. 추후에 아코디언이 추가될 경우 변경이 필요합니다. | ||
const [isOpen] = useState(true); | ||
|
||
const toggleAccordion = () => { | ||
// setIsOpen(!isOpen); | ||
}; | ||
|
||
return ( | ||
<S.Container> | ||
<S.Header onClick={toggleAccordion}> | ||
<S.Title> | ||
<HiOutlineClipboardList /> | ||
<S.TitleText>{title}</S.TitleText> | ||
</S.Title> | ||
<ChevronButton | ||
size="sm" | ||
direction={isOpen ? 'down' : 'up'} | ||
/> | ||
</S.Header> | ||
{isOpen && <S.List>{children}</S.List>} | ||
</S.Container> | ||
); | ||
} | ||
|
||
Accordion.ListItem = S.ListItem; | ||
|
||
export default Accordion; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import styled from '@emotion/styled'; | ||
|
||
const Container = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.8rem; | ||
`; | ||
|
||
const Header = styled.div` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
padding: 0.8rem 0.4rem; | ||
|
||
width: 100%; | ||
/* cursor: pointer; */ // TODO: 추후에 아코디언이 추가될 경우 변경이 필요합니다. | ||
border-bottom: 1px solid ${({ theme }) => theme.baseColors.grayscale[400]}; | ||
`; | ||
|
||
const Title = styled.div` | ||
display: flex; | ||
align-items: center; | ||
gap: 0.4rem; | ||
${({ theme }) => theme.typography.heading[500]} | ||
width: 80%; | ||
`; | ||
|
||
const TitleText = styled.span` | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
width: 80%; | ||
white-space: nowrap; | ||
`; | ||
|
||
const List = styled.ul` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.4rem; | ||
padding: 0 0 0 0.8rem; | ||
`; | ||
|
||
const ListItem = styled.li` | ||
${({ theme }) => theme.typography.common.block} | ||
margin-bottom: 0; | ||
`; | ||
|
||
const S = { | ||
Container, | ||
Header, | ||
Title, | ||
TitleText, | ||
List, | ||
ListItem, | ||
}; | ||
|
||
export default S; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import DashboardSidebar from './index'; | ||
|
||
const meta: Meta<typeof DashboardSidebar> = { | ||
title: 'Components/Dashboard/Sidebar', | ||
component: DashboardSidebar, | ||
parameters: { | ||
layout: 'centered', | ||
docs: { | ||
description: { | ||
component: 'DashboardSidebar 컴포넌트는 로고와 여러 개의 Accordion으로 구성된 사이드바입니다.', | ||
}, | ||
}, | ||
}, | ||
tags: ['autodocs'], | ||
decorators: [ | ||
(Child) => ( | ||
<div | ||
style={{ | ||
height: '1000px', | ||
width: '400px', | ||
backgroundColor: 'gray', | ||
display: 'flex', | ||
justifyContent: 'center', | ||
alignContent: 'center', | ||
}} | ||
> | ||
<Child /> | ||
</div> | ||
), | ||
], | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof DashboardSidebar>; | ||
|
||
export const Default: Story = {}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import Accordion from '@components/common/Accordion'; | ||
import S from './style'; | ||
|
||
export default function DashboardSidebar() { | ||
// TODO: URL Param과 같은 방법으로 현재 공고가 Selected 인지 확인할 수 있도록 합니다. | ||
|
||
const options = [ | ||
{ text: '프론트엔드 7기 모집', isSelected: true }, | ||
{ text: '백엔드 7기 모집', isSelected: false }, | ||
{ text: '안드로이드 7기 모집', isSelected: false }, | ||
]; | ||
|
||
return ( | ||
<S.Container> | ||
<S.Logo>ㅋㄹㄹ</S.Logo> | ||
<S.Contents> | ||
<Accordion title="공고"> | ||
{options.map(({ text, isSelected }, index) => ( | ||
// eslint-disable-next-line react/no-array-index-key | ||
<Accordion.ListItem key={index}> | ||
<S.NavButton isSelected={isSelected}>{text}</S.NavButton> | ||
</Accordion.ListItem> | ||
Comment on lines
+20
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
))} | ||
</Accordion> | ||
</S.Contents> | ||
</S.Container> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import styled from '@emotion/styled'; | ||
|
||
const Container = styled.div` | ||
width: 300px; | ||
border-right: 1px solid ${({ theme }) => theme.baseColors.grayscale[400]}; | ||
padding: 3.6rem 1.6rem; | ||
height: 100%; | ||
|
||
background-color: ${({ theme }) => theme.baseColors.grayscale[50]}; | ||
border-radius: 1.6rem 0 0 1.6rem; | ||
`; | ||
|
||
const Logo = styled.div` | ||
${({ theme }) => theme.typography.heading[700]} | ||
margin-bottom: 3.2rem; | ||
`; | ||
|
||
const Contents = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 1.4rem; | ||
`; | ||
|
||
const NavButton = styled.button<{ isSelected: boolean }>` | ||
${({ theme }) => theme.typography.common.block} | ||
color: ${({ theme, isSelected }) => (isSelected ? theme.colors.brand.primary : theme.colors.text.default)}; | ||
margin-bottom: 0; | ||
|
||
&::before { | ||
content: '•'; | ||
width: 1rem; | ||
aspect-ratio: 1/1; | ||
margin: 0 0.8rem; | ||
} | ||
`; | ||
|
||
const S = { | ||
Container, | ||
Logo, | ||
Contents, | ||
NavButton, | ||
}; | ||
|
||
export default S; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import RecruitmentSidebar from './index'; | ||
|
||
const meta: Meta<typeof RecruitmentSidebar> = { | ||
title: 'Components/Recruitment/RecruitmentSidebar', | ||
component: RecruitmentSidebar, | ||
parameters: { | ||
layout: 'centered', | ||
docs: { | ||
description: { | ||
component: 'RecruitmentSidebar 컴포넌트는 모집 공고를 작성하고 게시하는 과정을 안내하는 사이드바입니다.', | ||
}, | ||
}, | ||
}, | ||
tags: ['autodocs'], | ||
decorators: [ | ||
(Child) => ( | ||
<div | ||
style={{ | ||
height: '1000px', | ||
width: '400px', | ||
backgroundColor: 'gray', | ||
display: 'flex', | ||
justifyContent: 'center', | ||
alignContent: 'center', | ||
}} | ||
> | ||
<Child /> | ||
</div> | ||
), | ||
], | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof RecruitmentSidebar>; | ||
|
||
export const Default: Story = {}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import S from './style'; | ||
|
||
interface StepProps { | ||
stepNumber: number; | ||
label: string; | ||
isSelected: boolean; | ||
} | ||
function Step({ stepNumber, label, isSelected }: StepProps) { | ||
return ( | ||
<S.StepContainer isSelected={isSelected}> | ||
<S.StepNumber isSelected={isSelected}>{stepNumber}</S.StepNumber> | ||
<S.StepLabel isSelected={isSelected}>{label}</S.StepLabel> | ||
</S.StepContainer> | ||
); | ||
} | ||
|
||
export default function RecruitmentSidebar() { | ||
// TODO: isSelected에 해당하는 값을 넣어야 합니다. | ||
const options = [ | ||
{ text: '공고 작성', isSelected: true }, | ||
{ text: '지원서 작성', isSelected: false }, | ||
{ text: '공고 게시', isSelected: false }, | ||
]; | ||
|
||
return ( | ||
<S.Container> | ||
<S.SidebarHeader> | ||
<S.Title>공고 생성</S.Title> | ||
<S.Description>모집 공고를 작성하고 인터넷에 게시하세요.</S.Description> | ||
</S.SidebarHeader> | ||
<S.SidebarContents> | ||
{options.map(({ text, isSelected }, index) => ( | ||
<Step | ||
// eslint-disable-next-line react/no-array-index-key | ||
key={index} | ||
stepNumber={index + 1} | ||
label={text} | ||
isSelected={isSelected} | ||
/> | ||
))} | ||
</S.SidebarContents> | ||
</S.Container> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요 부분은 추후에 수정 필요한 부분으로 이해하고 넘어갈게요!