From 7f4ebc1b2d3314350e1c2459f452c2c6e30f36c2 Mon Sep 17 00:00:00 2001 From: Jeongwoo Park <121204715+lurgi@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:26:40 +0900 Subject: [PATCH] =?UTF-8?q?feat-fe:=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20SideBar=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=20(#271)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/Accordion/Accordion.stories.tsx | 51 +++++++++++++ .../src/components/common/Accordion/index.tsx | 39 ++++++++++ .../src/components/common/Accordion/style.ts | 56 ++++++++++++++ .../components/common/ToggleSwitch/style.ts | 4 +- .../DashboardSidebar.stories.tsx | 37 ++++++++++ .../dashboard/DashboardSidebar/index.tsx | 28 +++++++ .../dashboard/DashboardSidebar/style.ts | 44 +++++++++++ .../RecruitmentSidebar.stories.tsx | 37 ++++++++++ .../recruitment/RecruitmentSidebar/index.tsx | 44 +++++++++++ .../recruitment/RecruitmentSidebar/style.ts | 74 +++++++++++++++++++ frontend/src/styles/theme.ts | 2 +- 11 files changed, 413 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/common/Accordion/Accordion.stories.tsx create mode 100644 frontend/src/components/common/Accordion/index.tsx create mode 100644 frontend/src/components/common/Accordion/style.ts create mode 100644 frontend/src/components/dashboard/DashboardSidebar/DashboardSidebar.stories.tsx create mode 100644 frontend/src/components/dashboard/DashboardSidebar/index.tsx create mode 100644 frontend/src/components/dashboard/DashboardSidebar/style.ts create mode 100644 frontend/src/components/recruitment/RecruitmentSidebar/RecruitmentSidebar.stories.tsx create mode 100644 frontend/src/components/recruitment/RecruitmentSidebar/index.tsx create mode 100644 frontend/src/components/recruitment/RecruitmentSidebar/style.ts diff --git a/frontend/src/components/common/Accordion/Accordion.stories.tsx b/frontend/src/components/common/Accordion/Accordion.stories.tsx new file mode 100644 index 000000000..f7856fb36 --- /dev/null +++ b/frontend/src/components/common/Accordion/Accordion.stories.tsx @@ -0,0 +1,51 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Accordion from './index'; +import S from './style'; + +const meta: Meta = { + 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; + +export const Default: Story = { + render: (args) => ( +
+ + 프론트엔드 7기 모집 + 백엔드 7기 모집 + 안드로이드 7기 모집 + +
+ ), +}; diff --git a/frontend/src/components/common/Accordion/index.tsx b/frontend/src/components/common/Accordion/index.tsx new file mode 100644 index 000000000..d900c2f0b --- /dev/null +++ b/frontend/src/components/common/Accordion/index.tsx @@ -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 ( + + + + + {title} + + + + {isOpen && {children}} + + ); +} + +Accordion.ListItem = S.ListItem; + +export default Accordion; diff --git a/frontend/src/components/common/Accordion/style.ts b/frontend/src/components/common/Accordion/style.ts new file mode 100644 index 000000000..6c6c8acb1 --- /dev/null +++ b/frontend/src/components/common/Accordion/style.ts @@ -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; diff --git a/frontend/src/components/common/ToggleSwitch/style.ts b/frontend/src/components/common/ToggleSwitch/style.ts index 92983df71..8a9265291 100644 --- a/frontend/src/components/common/ToggleSwitch/style.ts +++ b/frontend/src/components/common/ToggleSwitch/style.ts @@ -14,9 +14,9 @@ const Switch = styled.div` aspect-ratio: 5/3; background-color: ${({ isChecked, theme }) => - isChecked ? theme.baseColors.purplescale[600] : theme.baseColors.grayscale[100]}; + isChecked ? theme.colors.brand.primary : theme.baseColors.grayscale[100]}; outline: 0.2rem solid - ${({ isChecked, theme }) => (isChecked ? theme.baseColors.purplescale[600] : theme.baseColors.grayscale[700])}; + ${({ isChecked, theme }) => (isChecked ? theme.colors.brand.primary : theme.baseColors.grayscale[700])}; border-radius: 99rem; display: flex; diff --git a/frontend/src/components/dashboard/DashboardSidebar/DashboardSidebar.stories.tsx b/frontend/src/components/dashboard/DashboardSidebar/DashboardSidebar.stories.tsx new file mode 100644 index 000000000..ff85804e1 --- /dev/null +++ b/frontend/src/components/dashboard/DashboardSidebar/DashboardSidebar.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import DashboardSidebar from './index'; + +const meta: Meta = { + title: 'Components/Dashboard/Sidebar', + component: DashboardSidebar, + parameters: { + layout: 'centered', + docs: { + description: { + component: 'DashboardSidebar 컴포넌트는 로고와 여러 개의 Accordion으로 구성된 사이드바입니다.', + }, + }, + }, + tags: ['autodocs'], + decorators: [ + (Child) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/frontend/src/components/dashboard/DashboardSidebar/index.tsx b/frontend/src/components/dashboard/DashboardSidebar/index.tsx new file mode 100644 index 000000000..49d2a2a04 --- /dev/null +++ b/frontend/src/components/dashboard/DashboardSidebar/index.tsx @@ -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 ( + + ㅋㄹㄹ + + + {options.map(({ text, isSelected }, index) => ( + // eslint-disable-next-line react/no-array-index-key + + {text} + + ))} + + + + ); +} diff --git a/frontend/src/components/dashboard/DashboardSidebar/style.ts b/frontend/src/components/dashboard/DashboardSidebar/style.ts new file mode 100644 index 000000000..629104b7c --- /dev/null +++ b/frontend/src/components/dashboard/DashboardSidebar/style.ts @@ -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; diff --git a/frontend/src/components/recruitment/RecruitmentSidebar/RecruitmentSidebar.stories.tsx b/frontend/src/components/recruitment/RecruitmentSidebar/RecruitmentSidebar.stories.tsx new file mode 100644 index 000000000..7413221ac --- /dev/null +++ b/frontend/src/components/recruitment/RecruitmentSidebar/RecruitmentSidebar.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import RecruitmentSidebar from './index'; + +const meta: Meta = { + title: 'Components/Recruitment/RecruitmentSidebar', + component: RecruitmentSidebar, + parameters: { + layout: 'centered', + docs: { + description: { + component: 'RecruitmentSidebar 컴포넌트는 모집 공고를 작성하고 게시하는 과정을 안내하는 사이드바입니다.', + }, + }, + }, + tags: ['autodocs'], + decorators: [ + (Child) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/frontend/src/components/recruitment/RecruitmentSidebar/index.tsx b/frontend/src/components/recruitment/RecruitmentSidebar/index.tsx new file mode 100644 index 000000000..288c649ef --- /dev/null +++ b/frontend/src/components/recruitment/RecruitmentSidebar/index.tsx @@ -0,0 +1,44 @@ +import S from './style'; + +interface StepProps { + stepNumber: number; + label: string; + isSelected: boolean; +} +function Step({ stepNumber, label, isSelected }: StepProps) { + return ( + + {stepNumber} + {label} + + ); +} + +export default function RecruitmentSidebar() { + // TODO: isSelected에 해당하는 값을 넣어야 합니다. + const options = [ + { text: '공고 작성', isSelected: true }, + { text: '지원서 작성', isSelected: false }, + { text: '공고 게시', isSelected: false }, + ]; + + return ( + + + 공고 생성 + 모집 공고를 작성하고 인터넷에 게시하세요. + + + {options.map(({ text, isSelected }, index) => ( + + ))} + + + ); +} diff --git a/frontend/src/components/recruitment/RecruitmentSidebar/style.ts b/frontend/src/components/recruitment/RecruitmentSidebar/style.ts new file mode 100644 index 000000000..2fae8defb --- /dev/null +++ b/frontend/src/components/recruitment/RecruitmentSidebar/style.ts @@ -0,0 +1,74 @@ +import styled from '@emotion/styled'; + +const Container = styled.div` + width: 28.4rem; + padding: 2.4rem; + + display: flex; + flex-direction: column; + gap: 3.6rem; + background-color: ${({ theme }) => theme.baseColors.grayscale[50]}; +`; + +const SidebarHeader = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; +`; + +const Title = styled.h2` + ${({ theme }) => theme.typography.heading[700]} +`; + +const Description = styled.p` + ${({ theme }) => theme.typography.common.default} +`; + +const SidebarContents = styled.div` + display: flex; + flex-direction: column; + gap: 2.4rem; + + padding: 0 1.6rem; +`; + +const StepContainer = styled.div<{ isSelected: boolean }>` + display: flex; + align-items: center; + margin-bottom: 1rem; + opacity: ${({ isSelected }) => (isSelected ? 1 : 0.5)}; +`; + +const StepNumber = styled.div<{ isSelected: boolean }>` + width: 2.4rem; + aspect-ratio: 1/1; + + display: flex; + align-items: center; + justify-content: center; + + border-radius: 50%; + background-color: ${({ isSelected, theme }) => + isSelected ? theme.colors.brand.primary : theme.baseColors.grayscale[400]}; + color: ${({ theme }) => theme.baseColors.grayscale[50]}; + + margin-right: 0.6rem; +`; + +const StepLabel = styled.div<{ isSelected: boolean }>` + ${({ theme }) => theme.typography.common.smallAccent} + color: ${({ isSelected, theme }) => (isSelected ? theme.colors.brand.primary : theme.baseColors.grayscale[400])}; +`; + +const S = { + Container, + SidebarHeader, + Title, + Description, + SidebarContents, + StepContainer, + StepNumber, + StepLabel, +}; + +export default S; diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts index 82a7bfca0..7b074af98 100644 --- a/frontend/src/styles/theme.ts +++ b/frontend/src/styles/theme.ts @@ -160,7 +160,7 @@ const typography = { letter-spacing: 0; `, block: css` - font-weight: 700; + font-weight: 500; font-size: 1.4rem; line-height: 2rem; letter-spacing: 0;