Skip to content

Commit

Permalink
내 정보 페이지 UI 구현, Accordion 컴포넌트 구현 (#179)
Browse files Browse the repository at this point in the history
* feat: (#175) Accordion 컴포넌트 구현 및 스토리 작성

* feat: (#175) 내정보 페이지 UI/UX 구현

* chore: (#175) 불필요한 코드 삭제

* feat: (#175) 회원 탈퇴 모달 컴포넌트 구현

* feat: (#175) Layout 컴포넌트 추가, 반응형 디자인 구현

* fix: (#175) isPicked props 앞에 $ 기호 추가
  • Loading branch information
inyeong-kang authored Aug 2, 2023
1 parent 9bcbe4d commit dac7aa1
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 5 deletions.
112 changes: 112 additions & 0 deletions frontend/src/components/common/Accordion/Accordion.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { Meta, StoryObj } from '@storybook/react';

import { styled } from 'styled-components';

import { useToggle } from '@hooks/useToggle';

import Modal from '../Modal';
import SquareButton from '../SquareButton';

import Accordion from '.';

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

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

export const Default: Story = {
render: () => (
<Accordion title="Click Title to Open Content">
<span>Hello This is Content!</span>
</Accordion>
),
};

export const NicknameChange: Story = {
render: () => (
<Accordion title="닉네임 변경">
<Input placeholder="새로운 닉네임을 입력해주세요" />
<ButtonWrapper>
<SquareButton aria-label="닉네임 변경" theme="fill">
변경
</SquareButton>
</ButtonWrapper>
</Accordion>
),
};

export const DeleteUserAccount = () => {
const { isOpen, openComponent, closeComponent } = useToggle();
return (
<Accordion title="회원 탈퇴">
<ButtonWrapper>
<SquareButton onClick={openComponent} aria-label="회원 탈퇴" theme="blank">
회원 탈퇴
</SquareButton>
</ButtonWrapper>
{isOpen && (
<Modal size="sm" onModalClose={closeComponent}>
<ModalBody>
<ModalTitle>정말 탈퇴하시겠어요?</ModalTitle>
<ModalDescription>
탈퇴 버튼 클릭 시, <br></br>계정은 삭제되며 복구되지 않아요.
</ModalDescription>
<ButtonListWrapper>
<SquareButton aria-label="회원 탈퇴" theme="fill">
탈퇴
</SquareButton>
<SquareButton onClick={closeComponent} aria-label="회원 탈퇴" theme="blank">
취소
</SquareButton>
</ButtonListWrapper>
</ModalBody>
</Modal>
)}
</Accordion>
);
};

const ButtonWrapper = styled.div`
width: 90px;
height: 50px;
`;

const Input = styled.input`
width: 80%;
border: 1px solid #f2f2f2;
padding: 20px;
`;

const ModalBody = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
gap: 30px;
width: 90%;
margin: 40px 20px 0px 16px;
font: var(--text-caption);
`;

const ModalTitle = styled.div`
font: var(--text-title);
`;

const ModalDescription = styled.div`
font: var(--text-body);
`;

const ButtonListWrapper = styled.div`
display: flex;
justify-content: space-around;
gap: 20px;
width: 90%;
height: 50px;
margin-top: 20px;
`;
30 changes: 30 additions & 0 deletions frontend/src/components/common/Accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { PropsWithChildren, useState } from 'react';

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

import * as S from './style';

interface AccordionProps extends PropsWithChildren {
title: string;
}

export default function Accordion({ title, children }: AccordionProps) {
const [isOpen, setIsOpen] = useState(false);

const toggleAccordion = () => {
setIsOpen(!isOpen);
};

return (
<S.Wrapper aria-label="Accordion">
<S.Title onClick={toggleAccordion} aria-controls={`${title}에 대한 내용`}>
{title}
<S.Image src={isOpen ? chevronUp : chevronDown} alt="" $isOpen={isOpen} />
</S.Title>
<S.Content id={`${title}에 대한 내용`} $isOpen={isOpen}>
{children}
</S.Content>
</S.Wrapper>
);
}
62 changes: 62 additions & 0 deletions frontend/src/components/common/Accordion/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import styled, { keyframes } from 'styled-components';

export const Wrapper = styled.div`
width: 100%;
font: var(--text-caption);
`;

export const Title = styled.div`
display: flex;
justify-content: space-between;
border: 1px solid #f2f2f2;
border-radius: 7px 7px 0 0;
padding: 16px;
background-color: #ffffff;
&:hover {
background-color: #f2f2f2;
}
cursor: pointer;
`;

export const Content = styled.div<{ $isOpen: boolean }>`
display: ${props => (props.$isOpen ? 'flex' : 'none')};
justify-content: space-between;
border: 1px solid #f2f2f2;
border-radius: 0 0 7px 7px;
padding: 16px;
opacity: ${props => (props.$isOpen ? 1 : 0)};
animation: ${props => (props.$isOpen ? fadeIn : fadeOut)} 0.2s ease-in-out;
`;

export const Image = styled.img<{ $isOpen: boolean }>`
width: 20px;
height: 20px;
`;

const fadeIn = keyframes`
from {
opacity: 0;
height: 0;
}
to {
opacity: 1;
height: auto;
}
`;

const fadeOut = keyframes`
from {
opacity: 1;
height: auto;
}
to {
opacity: 0;
height: 0;
}
`;
2 changes: 0 additions & 2 deletions frontend/src/mocks/example/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,3 @@ export const example = [
return res(ctx.status(200));
}),
];

window.console.log('husky pre-commit test');
14 changes: 14 additions & 0 deletions frontend/src/pages/MyInfo/MyInfo.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Meta, StoryObj } from '@storybook/react';

import MyInfo from '.';

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

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

export const Default: Story = {
render: () => <MyInfo />,
};
83 changes: 83 additions & 0 deletions frontend/src/pages/MyInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useNavigate } from 'react-router-dom';

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

import { useToggle } from '@hooks/useToggle';

import Accordion from '@components/common/Accordion';
import UserProfile from '@components/common/Dashboard/UserProfile';
import IconButton from '@components/common/IconButton';
import Layout from '@components/common/Layout';
import Modal from '@components/common/Modal';
import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader';
import SquareButton from '@components/common/SquareButton';

import * as S from './style';

export default function MyInfo() {
const navigate = useNavigate();
const { isOpen, openComponent, closeComponent } = useToggle();

// const { data: userInfo, error, isLoading, isError } = useUserInfo(); // 유저 정보 조회 관련 pr merge 필요
const MOCK_USER_INFO: User = {
nickname: '우아한 코끼리',
postCount: 4,
voteCount: 128,
userPoint: 200,
};

return (
<Layout isSidebarVisible={true}>
<S.Wrapper>
<S.HeaderWrapper>
<NarrowTemplateHeader>
<IconButton
category="back"
onClick={() => {
navigate(-1);
}}
/>
</NarrowTemplateHeader>
</S.HeaderWrapper>
<S.ProfileSection>
<UserProfile userInfo={MOCK_USER_INFO} />
</S.ProfileSection>
<S.UserControlSection>
<Accordion title="닉네임 변경">
<S.Input placeholder="새로운 닉네임을 입력해주세요" />
<S.ButtonWrapper>
<SquareButton aria-label="닉네임 변경" theme="fill">
변경
</SquareButton>
</S.ButtonWrapper>
</Accordion>
<Accordion title="회원 탈퇴">
<S.ButtonWrapper>
<SquareButton onClick={openComponent} aria-label="회원 탈퇴" theme="blank">
회원 탈퇴
</SquareButton>
</S.ButtonWrapper>
{isOpen && (
<Modal size="sm" onModalClose={closeComponent}>
<S.ModalBody>
<S.ModalTitle>정말 탈퇴하시겠어요?</S.ModalTitle>
<S.ModalDescription>
탈퇴 버튼 클릭 시, <br></br>계정은 삭제되며 복구되지 않아요.
</S.ModalDescription>
<S.ButtonListWrapper>
<SquareButton aria-label="회원 탈퇴" theme="fill">
탈퇴
</SquareButton>
<SquareButton onClick={closeComponent} aria-label="회원 탈퇴" theme="blank">
취소
</SquareButton>
</S.ButtonListWrapper>
</S.ModalBody>
</Modal>
)}
</Accordion>
</S.UserControlSection>
</S.Wrapper>
</Layout>
);
}
85 changes: 85 additions & 0 deletions frontend/src/pages/MyInfo/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { styled } from 'styled-components';

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

export const Wrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
gap: 30px;
padding-top: 55px;
position: relative;
@media (min-width: 768px) {
padding-top: 20px;
}
@media (min-width: ${theme.breakpoint.md}) {
padding-top: 20px;
}
`;

export const HeaderWrapper = styled.div`
width: 100%;
position: fixed;
z-index: ${theme.zIndex.header};
@media (min-width: ${theme.breakpoint.md}) {
display: none;
}
`;

export const ProfileSection = styled.section`
width: 90%;
`;

export const UserControlSection = styled.section`
width: 90%;
`;

export const ButtonWrapper = styled.div`
width: 90px;
height: 50px;
`;

export const Input = styled.input`
width: 80%;
border: 1px solid #f2f2f2;
padding: 20px;
`;

export const ModalBody = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
gap: 30px;
width: 90%;
margin: 40px 20px 0px 16px;
font: var(--text-caption);
`;

export const ModalTitle = styled.div`
font: var(--text-title);
`;

export const ModalDescription = styled.div`
font: var(--text-body);
`;

export const ButtonListWrapper = styled.div`
display: flex;
justify-content: space-around;
gap: 20px;
width: 90%;
height: 50px;
margin-top: 20px;
`;
Loading

0 comments on commit dac7aa1

Please sign in to comment.