Skip to content

Commit

Permalink
[Feature/BAR-157] 레이아웃 컴포넌트 작성 (#46)
Browse files Browse the repository at this point in the history
* feat: 아이콘 관련 처리 추가

* feat: Dialog 관련 수정 반영

* feat: Layout 작성 및 적용

* feat: 프로필 dialog 작성

* feat: action addon 추가

* refactor: 컨벤션에 맞게 함수명 수정

* feat: 헤더와 푸터 옵셔널 처리 추가
  • Loading branch information
miro-ring authored Jan 24, 2024
1 parent fddcbf1 commit 5e0ef93
Show file tree
Hide file tree
Showing 16 changed files with 449 additions and 86 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"template": "plop"
},
"dependencies": {
"@storybook/addon-actions": "^7.6.10",
"@svgr/webpack": "^8.1.0",
"@tanstack/react-query": "^5.17.9",
"@vanilla-extract/css": "^1.14.0",
Expand Down
26 changes: 20 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/assets/icons/bookmarkHeader.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 src/assets/icons/instagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icons/logo32.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/profileHeader.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 src/assets/images/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 26 additions & 33 deletions src/components/Dialog/Dialog.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Icon from '@components/Icon';
import ProfileDialog from '@components/Layout/components/ProfileDialog';
import { action } from '@storybook/addon-actions';
import type { Meta, StoryObj } from '@storybook/react';

import Dialog from '.';
Expand All @@ -19,9 +21,13 @@ type Story = StoryObj<typeof Dialog>;
export const Small: Story = {
render: () => (
<>
<Dialog type="small">
<Dialog.Button onClick={() => {}}>수정하기</Dialog.Button>
<Dialog.Button onClick={() => {}}>삭제하기</Dialog.Button>
<Dialog type="small" closeDialog={action('Dialog 외부 클릭')}>
<Dialog.Button onClick={action('수정하기 클릭')}>
수정하기
</Dialog.Button>
<Dialog.Button onClick={action('삭제하기 클릭')}>
삭제하기
</Dialog.Button>
</Dialog>
</>
),
Expand All @@ -30,13 +36,17 @@ export const Small: Story = {
export const MediumFolder: Story = {
render: () => (
<>
<Dialog type="medium">
<Dialog.Button onClick={() => {}}>
<Dialog type="medium" closeDialog={action('Dialog 외부 클릭')}>
<Dialog.Button onClick={action('기본 폴더 클릭')}>
OOO님의 폴더<span className={styles.badge}>기본</span>
</Dialog.Button>
<Dialog.Button onClick={() => {}}>폴더 이름1</Dialog.Button>
<Dialog.Button onClick={() => {}}>폴더 이름2</Dialog.Button>
<Dialog.Button onClick={() => {}}>
<Dialog.Button onClick={action('폴더 이름1 클릭')}>
폴더 이름1
</Dialog.Button>
<Dialog.Button onClick={action('폴더 이름2 클릭')}>
폴더 이름2
</Dialog.Button>
<Dialog.Button onClick={action('새 폴더 만들기 클릭')}>
<div className={styles.icon}>
<Icon icon="add" width={20} height={20} />
</div>
Expand All @@ -48,29 +58,12 @@ export const MediumFolder: Story = {
};

export const MediumProfile: Story = {
render: () => (
<>
<Dialog type="medium">
<Dialog.Title>
<div className={styles.profileIcon}>
<Icon icon="profileDialog" />
</div>
<div className={styles.circle} />
<span className={styles.iconTitleText}>바로가나다라마바님</span>
</Dialog.Title>
<Dialog.Button onClick={() => {}}>
<div className={styles.icon}>
<Icon icon="setting" width={20} height={20} />
</div>
<span className={styles.iconRegularText}>계정 설정</span>
</Dialog.Button>
<Dialog.Button onClick={() => {}}>
<div className={styles.icon}>
<Icon icon="logout" width={20} height={20} />
</div>
<span className={styles.iconRegularText}>로그아웃</span>
</Dialog.Button>
</Dialog>
</>
),
render: () => <ProfileDialog closeDialog={action('Dialog 외부 클릭')} />,
decorators: [
(Story) => (
<div style={{ height: '300px' }}>
<Story />
</div>
),
],
};
37 changes: 34 additions & 3 deletions src/components/Dialog/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { createContext, type PropsWithChildren } from 'react';
import { useEffect, useRef } from 'react';
import clsx from 'clsx';

import DialogButton from '@components/Dialog/components/DialogButton';
import DialogTitle from '@components/Dialog/components/DialogTitle';
Expand All @@ -8,18 +10,47 @@ interface DialogContextProps {
type: 'small' | 'medium';
}

type DialogRootProps = DialogContextProps & PropsWithChildren;
interface DialogProps {
className?: string;
closeDialog: () => void;
}

type DialogRootProps = DialogContextProps & PropsWithChildren<DialogProps>;

export const DialogContext = createContext<DialogContextProps | null>(null);

const DialogRoot = ({ children, type }: DialogRootProps) => {
const DialogRoot = ({
children,
type,
className,
closeDialog,
}: DialogRootProps) => {
const dialogRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
const isClickOutside =
dialogRef.current && !dialogRef.current?.contains(e.target as Node);
isClickOutside && closeDialog();
};

document.addEventListener('click', handleClickOutside);

return () => document.removeEventListener('click', handleClickOutside);
}, [closeDialog]);

return (
<DialogContext.Provider
value={{
type,
}}
>
<div className={styles.dialogRoot({ type })}>{children}</div>
<div
className={clsx(styles.dialogRoot({ type }), className)}
ref={dialogRef}
>
{children}
</div>
</DialogContext.Provider>
);
};
Expand Down
46 changes: 5 additions & 41 deletions src/components/Dialog/style.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { COLORS } from '@styles/tokens';
export const dialogRoot = recipe({
base: {
borderRadius: '12px',
boxShadow: '0px 8px 15px 0px rgba(28, 28, 28, 0.08);',
boxShadow: '0px 8px 15px 0px rgba(28, 28, 28, 0.08)',
backgroundColor: COLORS['Grey/White'],
},
variants: {
type: {
Expand All @@ -17,16 +18,15 @@ export const dialogRoot = recipe({
},
medium: {
width: '228px',
padding: '12px',
padding: '8px 12px',
},
},
},
});

export const dialogTitle = style({
position: 'relative',
padding: '6px 12px 10px',
lineHeight: '40px',
padding: '10px 12px',
textAlign: 'center',
});

export const line = style({
Expand Down Expand Up @@ -93,16 +93,6 @@ export const badge = style([
},
]);

export const iconTitleText = style([
sprinkles({
typography: '16/Title/Medium',
}),
{
color: COLORS['Grey/900'],
marginLeft: '48px',
},
]);

export const iconMediumText = style([
sprinkles({
typography: '15/Body/Medium',
Expand All @@ -113,33 +103,7 @@ export const iconMediumText = style([
},
]);

export const iconRegularText = style([
sprinkles({
typography: '15/Body/Regular',
}),
{
color: COLORS['Grey/800'],
marginLeft: '28px',
},
]);

export const icon = style({
position: 'absolute',
marginTop: '2px',
});

export const circle = style({
width: '40px',
height: '40px',
backgroundColor: COLORS['Grey/100'],
position: 'absolute',
top: '6px',
borderRadius: '50%',
zIndex: -1,
});

export const profileIcon = style({
position: 'absolute',
top: '13px',
left: '20px',
});
40 changes: 40 additions & 0 deletions src/components/Layout/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Link from 'next/link';

import Button from '@components/Button';
import Icon from '@components/Icon';

import * as styles from '../style.css';

const Footer = () => {
return (
<div className={styles.footerWrapper}>
<div>
<div>
<Button className={styles.footerButton}>서비스메일</Button>
<Link href="/" className={styles.footerLink}>
이용약관
</Link>
<Link href="/" className={styles.footerLink}>
개인정보보호방침
</Link>
</div>
<Button className={styles.instagramButton}>
<div className={styles.instagramIcon}>
<Icon icon="instagram" />
</div>
<span className={styles.instagramText}>인스타그램</span>
</Button>
</div>
<div>
<div className={styles.baroIcon}>
<Icon icon="logo32" width={32} height={36} />
</div>
<span className={styles.copyright}>
&copy; 2023. Baro. All rights reserved.
</span>
</div>
</div>
);
};

export default Footer;
Loading

0 comments on commit 5e0ef93

Please sign in to comment.