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

내 정보 페이지 UI 구현, Accordion 컴포넌트 구현 #179

Merged
merged 6 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
inyeong-kang marked this conversation as resolved.
Show resolved Hide resolved
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;
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ export default function TimePickerOption({
return () => {
timeBox.removeEventListener('scroll', handleScroll);
};
}, [handlePickTime, timeUnit]);
}, [currentTime, handlePickTime, option, timeUnit]);

return (
<S.TimeBox ref={timeBoxRef}>
{Array.from({ length: timeUnit }).map((_, index) => (
<S.Time
key={index}
ref={index === currentTime ? timeBoxChildRef : null}
isPicked={currentTime === index}
$isPicked={currentTime === index}
>
{index}
</S.Time>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ export const TimeBox = styled.div`
scrollbar-width: none;
`;

export const Time = styled.div<{ isPicked: boolean }>`
export const Time = styled.div<{ $isPicked: boolean }>`
display: flex;
justify-content: center;
align-items: center;

width: 100%;
height: 50px;

background: ${props => (props.isPicked ? '#F2F2F2' : 'var(--white)')};
background: ${props => (props.$isPicked ? '#F2F2F2' : 'var(--white)')};

font: var(--text-small);
font-weight: ${props => (props.isPicked ? 'bold' : 'light')};
font-weight: ${props => (props.$isPicked ? 'bold' : 'light')};
`;
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>
);
}
Loading