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 컴포넌트 개선 #773

Merged
merged 13 commits into from
Oct 10, 2024
1 change: 1 addition & 0 deletions frontend/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const preview: Preview = {
loaders: [mswLoader],
decorators: [
(Story) => {
localStorage.setItem('clubId', '1');
return (
<ModalProvider>
<QueryClientProvider client={new QueryClient()}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useState } from 'react';

import { reactRouterParameters, withRouter } from 'storybook-addon-remix-react-router';
import type { Meta, StoryObj } from '@storybook/react';
import DashboardSidebar from '.';
Expand All @@ -23,47 +25,89 @@ const meta: Meta<typeof DashboardSidebar> = {
options: [
{
text: '첫번째 옵션',
isSelected: true,
isSelected: false,
dashboardId: '1',
applyFormId: '10',
status: {
isClosed: true,
isPending: false,
isOngoing: false,
status: 'Closed',
},
},
{
text: '두번째 옵션',
isSelected: false,
dashboardId: '2',
applyFormId: '11',
status: {
isClosed: true,
isPending: false,
isOngoing: false,
status: 'Closed',
},
},
{
text: '세번째 옵션',
isSelected: true,
dashboardId: '2',
applyFormId: '12',
status: {
isClosed: false,
isPending: false,
isOngoing: true,
status: 'Ongoing',
},
},
{
text: '네번째 옵션',
isSelected: false,
dashboardId: '2',
applyFormId: '13',
status: {
isClosed: false,
isPending: true,
isOngoing: false,
status: 'Pending',
},
},
],
},
tags: ['autodocs'],
decorators: [
withRouter,
(Child) => (
<div
style={{
height: '1000px',
width: '400px',
backgroundColor: 'gray',
display: 'flex',
justifyContent: 'center',
alignContent: 'center',
}}
>
<Child />
</div>
),
(Child, context) => {
const [isOpen, setIsOpen] = useState(true);

const handleToggle = () => {
if (isOpen) setIsOpen(false);
if (!isOpen) setIsOpen(true);
};

return (
<div
style={{
height: '1000px',
width: '400px',
backgroundColor: 'gray',
display: 'flex',
justifyContent: 'center',
alignContent: 'center',
}}
>
<Child
args={{
...context.args,
sidebarStyle: { isSidebarOpen: isOpen, onClickSidebarToggle: handleToggle },
}}
/>
</div>
);
},
],
};

export default meta;
type Story = StoryObj<typeof DashboardSidebar>;

export const Default: Story = {
args: {
options: [
{ text: '우아한테크코스 6기 프론트엔드', isSelected: true, dashboardId: '1', applyFormId: '10' },
{ text: '우아한테크코스 6기 백엔드', isSelected: false, dashboardId: '2', applyFormId: '11' },
{ text: '우아한테크코스 6기 안드로이드', isSelected: false, dashboardId: '3', applyFormId: '12' },
],
},
};
export const Default: Story = {};
154 changes: 128 additions & 26 deletions frontend/src/components/dashboard/DashboardSidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,147 @@
import { useMemo } from 'react';

import Logo from '@assets/images/logo.svg';
import Accordion from '@components/_common/molecules/Accordion';
import { routes } from '@router/path';
import { Link } from 'react-router-dom';
import { Link, useLocation } from 'react-router-dom';

import type { RecruitmentStatusObject } from '@utils/compareTime';

import { Fragment } from 'react/jsx-runtime';
import { HiChevronDoubleLeft, HiOutlineHome } from 'react-icons/hi2';
import { HiOutlineMenu } from 'react-icons/hi';
import { GrDocumentLocked, GrDocumentTime, GrDocumentUser } from 'react-icons/gr';
import type { IconType } from 'react-icons';

import IconButton from '@components/_common/atoms/IconButton';
import LogoutButton from './LogoutButton';

import S from './style';

interface Option {
text: string;
isSelected: boolean;
applyFormId: string;
dashboardId: string;
status: RecruitmentStatusObject;
}

interface SidebarStyle {
isSidebarOpen: boolean;
onClickSidebarToggle: () => void;
}

interface DashboardSidebarProps {
options: Option[];
sidebarStyle: SidebarStyle;
options?: Option[];
}

export default function DashboardSidebar({ options }: DashboardSidebarProps) {
export default function DashboardSidebar({ sidebarStyle, options }: DashboardSidebarProps) {
const pendingPosts = useMemo(() => options?.filter(({ status }) => status.isPending), [options]);
const onGoingPosts = useMemo(() => options?.filter(({ status }) => status.isOngoing), [options]);
const closedPosts = useMemo(() => options?.filter(({ status }) => status.isClosed), [options]);
Comment on lines +39 to +41
Copy link
Contributor

Choose a reason for hiding this comment

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

useMemo 👍


const sidebarContentList = [
{ title: '모집 예정 공고 목록', posts: pendingPosts },
{ title: '진행 중 공고 목록', posts: onGoingPosts },
{ title: '마감 된 공고 목록', posts: closedPosts },
];

const location = useLocation();

const IconObj: Record<RecruitmentStatusObject['status'], IconType> = {
Pending: GrDocumentTime,
Ongoing: GrDocumentUser,
Closed: GrDocumentLocked,
};

return (
<S.Container>
<Link to={routes.dashboard.list()}>
<S.Logo
src={Logo}
alt="크루루 로고"
/>
</Link>

<S.Contents>
<Accordion title={<Link to={routes.dashboard.list()}>공고</Link>}>
{options.map(({ text, isSelected, applyFormId, dashboardId }, index) => (
// eslint-disable-next-line react/no-array-index-key
<Accordion.ListItem key={index}>
<S.LinkContainer isSelected={isSelected}>
<Link to={routes.dashboard.post({ dashboardId, applyFormId })}>{text}</Link>
</S.LinkContainer>
</Accordion.ListItem>
))}
</Accordion>
</S.Contents>

<LogoutButton />
<S.Container isSidebarOpen={sidebarStyle.isSidebarOpen}>
<S.SidebarHeader>
{sidebarStyle.isSidebarOpen && (
<Link to={routes.dashboard.list()}>
<S.Logo
src={Logo}
alt="크루루 로고"
/>
</Link>
)}

<IconButton
size="sm"
outline={false}
onClick={sidebarStyle.onClickSidebarToggle}
>
<S.SidebarToggleIcon>
{sidebarStyle.isSidebarOpen ? (
<HiChevronDoubleLeft
size={24}
strokeWidth={0.8}
/>
) : (
<HiOutlineMenu
size={24}
strokeWidth={2.4}
/>
)}
</S.SidebarToggleIcon>
</IconButton>
</S.SidebarHeader>

<nav>
<S.Contents>
<S.SidebarItem>
<Link to={routes.dashboard.list()}>
<S.SidebarItemLink
isSelected={location.pathname === routes.dashboard.list()}
isSidebarOpen={sidebarStyle.isSidebarOpen}
>
<S.IconContainer>
<HiOutlineHome
size={22}
strokeWidth={2}
/>
</S.IconContainer>
{sidebarStyle.isSidebarOpen && <S.SidebarItemTextHeader>모집 공고</S.SidebarItemTextHeader>}
</S.SidebarItemLink>
</Link>
</S.SidebarItem>

{!!options?.length && <S.Divider />}

{sidebarContentList.map(({ title, posts }) => {
if (posts?.length === 0) return null;
return (
<Fragment key={title}>
<S.ContentSubTitle>{sidebarStyle.isSidebarOpen ? title : <S.Circle />}</S.ContentSubTitle>
{posts?.map(({ text, isSelected, applyFormId, dashboardId, status }) => {
const Icon = IconObj[status.status];

return (
<S.SidebarItem key={applyFormId}>
<Link to={routes.dashboard.post({ dashboardId, applyFormId })}>
<S.SidebarItemLink
isSelected={isSelected}
isSidebarOpen={sidebarStyle.isSidebarOpen}
>
<S.IconContainer>
<Icon
size={16}
strokeWidth={4}
/>
</S.IconContainer>
{sidebarStyle.isSidebarOpen && <S.SidebarItemText>{text}</S.SidebarItemText>}
</S.SidebarItemLink>
</Link>
</S.SidebarItem>
);
})}
</Fragment>
);
})}
</S.Contents>
</nav>

{sidebarStyle.isSidebarOpen && <LogoutButton />}
</S.Container>
);
}
Loading
Loading