Skip to content
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

Merged
merged 10 commits into from
Aug 6, 2024
51 changes: 51 additions & 0 deletions frontend/src/components/common/Accordion/Accordion.stories.tsx
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>
),
};
39 changes: 39 additions & 0 deletions frontend/src/components/common/Accordion/index.tsx
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);
};
Comment on lines +13 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 부분은 추후에 수정 필요한 부분으로 이해하고 넘어갈게요!


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;
56 changes: 56 additions & 0 deletions frontend/src/components/common/Accordion/style.ts
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;
4 changes: 2 additions & 2 deletions frontend/src/components/common/ToggleSwitch/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const Switch = styled.div<StyleProps>`
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;
Expand Down
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 = {};
28 changes: 28 additions & 0 deletions frontend/src/components/dashboard/DashboardSidebar/index.tsx
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accordion.ListItem 컴포넌트 요소를 따로 빼고, 그 안에 button 요소를 넣으셨네요. 이런 경우에 해당 리스트(<li>) 항목에 대한 bullet point 표식은 Accordion.ListItem 보다는 button 요소에 삽입했다면 어땠을까 싶습니다. 현재는 isSelected prop에 의한 스타일 변경 사항이 button 요소에만 적용되고 bullet point에는 적용되지 않아서, 실제 Storybook으로 체크해보면 조금 어색하게 느껴지거든요.

Accordion.ListItem에 삽입되어 있는 아래의 스타일 코드를 S.NavButton으로 대신 적용하는 아이디어를 한 번 검토해주시면 좋겠습니다.

  padding: 0.4rem 0rem;
  &::before {
    content: '•';
    width: 1rem;
    aspect-ratio: 1/1;
    margin-right: 0.8rem;
  }

))}
</Accordion>
</S.Contents>
</S.Container>
);
}
44 changes: 44 additions & 0 deletions frontend/src/components/dashboard/DashboardSidebar/style.ts
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 = {};
44 changes: 44 additions & 0 deletions frontend/src/components/recruitment/RecruitmentSidebar/index.tsx
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>
);
}
Loading
Loading