Skip to content

Commit

Permalink
사이드바(Drawer) 컴포넌트 UI 구현 (#57)
Browse files Browse the repository at this point in the history
* feat: (#41) 카테고리 토글 컴포넌트 UI 구현

* feat: (#41) 유저 정보 창 UI r구현중

* feat: (#41) 회원 유저 프로필 창 UI 구현

* feat: (#41) 비회원 프로필 창 UI 구현

* feat: (#41) 카테고리 타입 선언

* feat: (#41) 유저 타입 선언

* refactor: (#41) 선언한 타입으로 기존의 코드 변경

* feat: (#41) 사이드에 있는 유저 대쉬보드 컴포넌트 UI 구현

* feat: (#41) 공용 Drawer 컴포넌트 UI 구현
Drawer 사용 방법에 대한 예제 코드를 스토리북에 작성

* refactor: (#41) drawer 동작에 필요한 코드를 useDrawer 훅으로 분리

* design: (#41) 로그아웃 버튼이 잘못 위치한 부분 수정

* refactor: (#41) 유저의 정보를 받는 props 변수명을 가독성을 위해 변경
user => userInfo

* refactor: (#41) 코드 가독성과 예쁜 디자인을 위한 코드 수정
  • Loading branch information
Gilpop8663 authored Jul 18, 2023
1 parent fd5b750 commit 9e863e8
Show file tree
Hide file tree
Showing 25 changed files with 803 additions and 3 deletions.
3 changes: 3 additions & 0 deletions frontend/src/assets/chevron-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/assets/chevron-up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions frontend/src/assets/kakao_login.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { Meta, StoryObj } from '@storybook/react';

import { Category } from '@type/category';

import CategoryToggle from '.';

const meta: Meta<typeof CategoryToggle> = {
component: CategoryToggle,
};

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

const MOCK_CATEGORIES: Category[] = [
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: false },
{ id: 13, name: '패션', isFavorite: false },
{ id: 14, name: '금융', isFavorite: false },
];

export const Default: Story = {
render: () => (
<CategoryToggle
handleFavoriteClick={() => {}}
title="즐겨찾기"
categoryList={MOCK_CATEGORIES}
/>
),
};

export const Closed: Story = {
render: () => (
<CategoryToggle
handleFavoriteClick={() => {}}
title="즐겨찾기"
categoryList={MOCK_CATEGORIES}
isInitialOpen={false}
/>
),
};
56 changes: 56 additions & 0 deletions frontend/src/components/common/Dashboard/CategoryToggle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState } from 'react';

import { Category } from '@type/category';

import chevronDown from '@assets/chevron-down.svg';
import chevronUp from '@assets/chevron-up.svg';

import * as S from './style';

interface CategoryToggleProps {
title: string;
categoryList: Category[];
handleFavoriteClick: (categoryId: number) => void;
isInitialOpen?: boolean;
}

export default function CategoryToggle({
title,
categoryList,
handleFavoriteClick,
isInitialOpen = true,
}: CategoryToggleProps) {
const [isToggleOpen, setIsToggleOpen] = useState(isInitialOpen);

const handleToggleClick = () => {
setIsToggleOpen(prevIsToggleOpen => !prevIsToggleOpen);
};

return (
<S.Container>
<S.TitleContainer
onClick={handleToggleClick}
aria-label={isToggleOpen ? `${title} 닫기` : `${title} 열기`}
type="button"
>
<S.TriangleImage src={isToggleOpen ? chevronUp : chevronDown} alt="" />
<span>{title}</span>
</S.TitleContainer>
{isToggleOpen && (
<S.CategoryList>
{categoryList.length === 0 && <S.Caption>현재 카테고리가 없습니다</S.Caption>}
{categoryList.map(({ id, name, isFavorite }) => (
<S.CategoryItem key={id}>
<S.Circle
title="즐겨찾기 버튼"
onClick={() => handleFavoriteClick(id)}
$isFavorite={isFavorite}
/>
<S.CategoryName to={`/posts?categoryId=${id}`}>{name}</S.CategoryName>
</S.CategoryItem>
))}
</S.CategoryList>
)}
</S.Container>
);
}
64 changes: 64 additions & 0 deletions frontend/src/components/common/Dashboard/CategoryToggle/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Link } from 'react-router-dom';

import { styled } from 'styled-components';

import { theme } from '@styles/theme';

export const Container = styled.div`
font: var(--text-caption);
@media (min-width: ${theme.breakpoint.sm}) {
font: var(--text-body);
}
`;

export const TitleContainer = styled.button`
display: flex;
align-items: center;
font: inherit;
cursor: pointer;
`;

export const TriangleImage = styled.img`
width: 16px;
height: 16px;
margin-right: 8px;
`;

export const CategoryList = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 12px;
`;

export const CategoryItem = styled.div`
display: flex;
align-items: center;
`;

export const Circle = styled.button<{ $isFavorite: boolean }>`
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 12px;
background-color: ${({ $isFavorite }) => ($isFavorite ? 'var(--primary-color)' : '#CCCCCC')};
cursor: pointer;
`;

export const Caption = styled.span`
font: var(--text-caption);
color: var(--dark-gray);
`;

export const CategoryName = styled(Link)`
text-decoration: none;
color: inherit;
`;
116 changes: 116 additions & 0 deletions frontend/src/components/common/Dashboard/Dashboard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { Meta, StoryObj } from '@storybook/react';

import { Category } from '@type/category';
import { User } from '@type/user';

import Dashboard from '.';

const meta: Meta<typeof Dashboard> = {
component: Dashboard,
};

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

const MOCK_USER_INFO: User = {
nickname: '우아한 코끼리',
postCount: 4,
voteCount: 128,
userPoint: 200,
};

const MOCK_CATEGORIES: Category[] = [
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: false },
{ id: 13, name: '패션', isFavorite: false },
{ id: 14, name: '금융', isFavorite: false },
];

const MOCK_FAVORITE_CATEGORIES: Category[] = [
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: true },
{ id: 13, name: '패션', isFavorite: true },
{ id: 14, name: '금융', isFavorite: false },
];

const MOCK_LONG_CATEGORIES: Category[] = [
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: true },
{ id: 13, name: '패션', isFavorite: true },
{ id: 14, name: '금융', isFavorite: false },
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: true },
{ id: 13, name: '패션', isFavorite: true },
{ id: 14, name: '금융', isFavorite: false },
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: true },
{ id: 13, name: '패션', isFavorite: true },
{ id: 14, name: '금융', isFavorite: false },
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: true },
{ id: 13, name: '패션', isFavorite: true },
{ id: 14, name: '금융', isFavorite: false },
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: true },
{ id: 13, name: '패션', isFavorite: true },
{ id: 14, name: '금융', isFavorite: false },
{ id: 12312, name: '음식', isFavorite: false },
{ id: 12, name: '연애', isFavorite: true },
{ id: 13, name: '패션', isFavorite: true },
{ id: 14, name: '금융', isFavorite: false },
];

export const LoggedIn: Story = {
render: () => (
<Dashboard
userInfo={MOCK_USER_INFO}
categoryList={MOCK_CATEGORIES}
handleFavoriteClick={() => {}}
handleLogoutClick={() => {}}
/>
),
};

export const FavoriteCategory: Story = {
render: () => (
<Dashboard
userInfo={MOCK_USER_INFO}
categoryList={MOCK_FAVORITE_CATEGORIES}
handleFavoriteClick={() => {}}
handleLogoutClick={() => {}}
/>
),
};

export const SelectedCategory: Story = {
render: () => (
<Dashboard
userInfo={MOCK_USER_INFO}
categoryList={MOCK_FAVORITE_CATEGORIES}
selectedCategory="패션"
handleFavoriteClick={() => {}}
handleLogoutClick={() => {}}
/>
),
};

export const LongCategoryList: Story = {
render: () => (
<Dashboard
userInfo={MOCK_USER_INFO}
categoryList={MOCK_LONG_CATEGORIES}
handleFavoriteClick={() => {}}
handleLogoutClick={() => {}}
/>
),
};

export const Guest: Story = {
render: () => (
<Dashboard
categoryList={MOCK_CATEGORIES}
handleFavoriteClick={() => {}}
handleLogoutClick={() => {}}
/>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Meta, StoryObj } from '@storybook/react';

import GuestProfile from '.';

const meta: Meta<typeof GuestProfile> = {
component: GuestProfile,
};

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

export const Default: Story = {
render: () => <GuestProfile />,
};
19 changes: 19 additions & 0 deletions frontend/src/components/common/Dashboard/GuestProfile/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { Link } from 'react-router-dom';

import { BASE_PATH } from '@constants/path';

import kakaoLogin from '@assets/kakao_login.svg';

import * as S from './style';

export default function GuestProfile() {
return (
<S.Container>
<Link to={BASE_PATH.LOGIN}>
<S.Image src={kakaoLogin} alt="로그인 페이지로 이동" />
</Link>
<S.TextCard>로그인 후 이용할 수 있습니다</S.TextCard>
</S.Container>
);
}
20 changes: 20 additions & 0 deletions frontend/src/components/common/Dashboard/GuestProfile/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { styled } from 'styled-components';

import { ProfileContainer } from '../profileStyle';

export const Container = styled(ProfileContainer)`
align-items: center;
`;

export const Image = styled.img`
width: 183px;
height: 40px;
`;

export const TextCard = styled.span`
margin-top: 20px;
font: var(--text-caption);
color: var(--dark-gray);
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Meta, StoryObj } from '@storybook/react';

import { User } from '@type/user';

import UserProfile from '.';

const meta: Meta<typeof UserProfile> = {
component: UserProfile,
};

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

const MOCK_USER_INFO: User = {
nickname: '우아한 코끼리',
postCount: 4,
voteCount: 128,
userPoint: 200,
};

export const NoBadge: Story = {
render: () => <UserProfile userInfo={MOCK_USER_INFO} />,
};

export const Badge: Story = {
render: () => <UserProfile userInfo={{ ...MOCK_USER_INFO, badge: '만근자' }} />,
};
Loading

0 comments on commit 9e863e8

Please sign in to comment.