Skip to content

Commit

Permalink
[Feature/BAR-36] 모달 컴포넌트 개발 (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
miro-ring authored Jan 9, 2024
1 parent 2858061 commit 958f9e6
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 56 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {
"no-undef": "off"
}
}
]
}
2 changes: 2 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AppProps } from 'next/app';
import '@/src/styles/global.css';

import Layout from '@/src/components/Layout/Layout';
import Modal from '@/src/components/Modal';
import TanstackQueryProvider from '@/src/components/Providers/TanstackQueryProvider';
import Toast from '@/src/components/Toast/Toast';
import { pretendard } from '@/src/styles/font';
Expand All @@ -14,6 +15,7 @@ const App = ({ Component, pageProps }: AppProps) => {
<Layout>
<Component {...pageProps} />
<Toast />
<Modal />
</Layout>
</TanstackQueryProvider>
</main>
Expand Down
31 changes: 31 additions & 0 deletions src/components/Modal/Modal.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Meta, StoryObj } from '@storybook/react';

import { useModalStore } from '@/src/stores/modalStore';

import Modal from '.';

const meta: Meta<typeof Modal> = {
title: 'Components/Modal',
component: Modal,
decorators: [
(Story) => (
<div style={{ height: '500px' }}>
<Story />
</div>
),
],
};

export default meta;

type Story = StoryObj<typeof Modal>;

export const DeleteArticle: Story = {
render: function Render() {
const { openModal } = useModalStore();

openModal('deleteArticle');

return <Modal />;
},
};
15 changes: 15 additions & 0 deletions src/components/Modal/ModalContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { PropsWithChildren } from 'react';

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

const ModalContainer = ({ children }: PropsWithChildren<unknown>) => {
return (
<Portal id="modal-root">
<div className={styles.dimmed} />
<div className={styles.modalStyle}>{children}</div>
</Portal>
);
};

export default ModalContainer;
45 changes: 45 additions & 0 deletions src/components/Modal/components/DeleteArticle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { assignInlineVars } from '@vanilla-extract/dynamic';

import { useModalStore } from '@/src/stores/modalStore';
import { COLORS } from '@/src/styles/tokens';

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

const DeleteArticle = () => {
const { closeModal } = useModalStore();

return (
<ModalContainer>
<strong className={styles.title}>해당 글을 삭제할까요?</strong>
<p className={styles.body}>
{'삭제한 글은 복구할 수 없어요!\n삭제하시겠어요'}
</p>
<div>
<button
type="button"
className={styles.button}
style={assignInlineVars({
[styles.buttonColor]: COLORS['Grey/600'],
[styles.buttonBackgroundColor]: COLORS['Grey/150'],
})}
onClick={closeModal}
>
취소
</button>
<button
type="button"
className={styles.button}
style={assignInlineVars({
[styles.buttonColor]: COLORS['Grey/White'],
[styles.buttonBackgroundColor]: COLORS['Blue/Default'],
})}
>
만들기
</button>
</div>
</ModalContainer>
);
};

export default DeleteArticle;
13 changes: 13 additions & 0 deletions src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useModalStore } from '@/src/stores/modalStore';

import DeleteArticle from './components/DeleteArticle';

const Modal = () => {
const { type } = useModalStore();

if (type === 'deleteArticle') return <DeleteArticle />;

return null;
};

export default Modal;
32 changes: 0 additions & 32 deletions src/components/Modal/stores/modalStore.ts

This file was deleted.

69 changes: 69 additions & 0 deletions src/components/Modal/style.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createVar, style } from '@vanilla-extract/css';

import { sprinkles } from '@/src/styles/sprinkles.css';
import { COLORS } from '@/src/styles/tokens';
import { middleLayer, positionCenter, topLayer } from '@/src/styles/utils.css';

export const modalStyle = style([
positionCenter,
topLayer,
{
padding: '32px',
width: '400px',
borderRadius: '16px',
backgroundColor: COLORS['Grey/White'],
},
]);

export const dimmed = style([
middleLayer,
{
backgroundColor: COLORS['Dim/50'],
position: 'fixed',
left: '0',
top: '0',
right: '0',
bottom: '0',
},
]);

export const title = style([
sprinkles({
typography: '20/Title/Semibold',
}),
{
display: 'block',
marginBottom: '18px',
},
]);

export const body = style([
sprinkles({
typography: '15/Body/Regular',
}),
{
display: 'block',
},
]);

export const buttonColor = createVar();
export const buttonBackgroundColor = createVar();

export const button = style([
sprinkles({
typography: '15/Title/Medium',
}),
{
width: '164px',
color: buttonColor,
backgroundColor: buttonBackgroundColor,
padding: '16px 40px',
borderRadius: '8px',
marginTop: '24px',
selectors: {
'& + &': {
marginLeft: '8px',
},
},
},
]);
31 changes: 31 additions & 0 deletions src/components/Portal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { PropsWithChildren } from 'react';
import { useEffect } from 'react';
import ReactDOM from 'react-dom';

interface PortalProps {
id: 'modal-root';
tag?: keyof HTMLElementTagNameMap;
}

const Portal = ({
children,
id,
tag = 'div',
}: PropsWithChildren<PortalProps>) => {
const root = document.createElement(tag);
root.id = id;

useEffect(() => {
if (root) {
document.body.appendChild(root);
}

return () => {
document.body.removeChild(root);
};
}, [root]);

return ReactDOM.createPortal(children, root);
};

export default Portal;
6 changes: 3 additions & 3 deletions src/components/Toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect } from 'react';
import type { Meta, StoryObj } from '@storybook/react';

import { TOAST_DURATION_TIME } from '@/src/models/toastModel';
import { useToast } from '@/src/stores/toastStore';
import { useToastStore } from '@/src/stores/toastStore';

import Toast from './Toast';

Expand All @@ -23,7 +23,7 @@ type Story = StoryObj<typeof Toast>;

export const Basic: Story = {
render: function Render() {
const { showToast } = useToast();
const { showToast } = useToastStore();

useEffect(() => {
showToast({ message: '테스트', type: TOAST_DURATION_TIME.SHOW });
Expand All @@ -42,7 +42,7 @@ export const Basic: Story = {

export const WithAction: Story = {
render: function Render() {
const { showToast } = useToast();
const { showToast } = useToastStore();

useEffect(() => {
showToast({ message: '테스트', type: TOAST_DURATION_TIME.ACTION });
Expand Down
6 changes: 3 additions & 3 deletions src/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useEffect } from 'react';

import { useToast } from '@/src/stores/toastStore';
import { useToastStore } from '@/src/stores/toastStore';

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

const Toast = () => {
const { toastData, hideToast } = useToast();
const { toastData, hideToast } = useToastStore();

const { message, type, isToastVisible } = toastData;

useEffect(() => {
let timer;
let timer: ReturnType<typeof setTimeout>;

if (isToastVisible) {
timer = setTimeout(() => hideToast(), type);
Expand Down
6 changes: 3 additions & 3 deletions src/components/Toast/style.css.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { recipe } from '@vanilla-extract/recipes';

import { sprinkles } from '@/src/styles/sprinkles.css';
import { theme } from '@/src/styles/theme.css';
import { COLORS } from '@/src/styles/tokens';
import { modalLayer } from '@/src/styles/utils.css';

export const toast = recipe({
Expand All @@ -11,9 +11,9 @@ export const toast = recipe({
typography: '14/Body/Regular',
}),
{
backgroundColor: theme.colors['Dim/70'],
backgroundColor: COLORS['Dim/70'],
position: 'fixed',
color: theme.colors['Grey/White'],
color: COLORS['Grey/White'],
bottom: 0,
left: '50%',
transform: 'translateX(-50%) translateY(30px)',
Expand Down
23 changes: 23 additions & 0 deletions src/stores/modalStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { useStoreWithEqualityFn as useStore } from 'zustand/traditional';

type ModalType = 'deleteArticle';

interface State {
type: ModalType | null;
}

interface Action {
openModal: (type: ModalType) => void;
closeModal: () => void;
}

export const modalStore = createStore<State & Action>((set) => ({
type: null,
openModal: (type) => set({ type }),
closeModal: () => set({ type: null }),
}));

export const useModalStore = () =>
useStore(modalStore, (state) => state, shallow);
16 changes: 2 additions & 14 deletions src/stores/toastStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,5 @@ export const toastStore = createStore<State & Action>((set, get) => ({
},
}));

const useToastStore = <T>(
selector: (state: State & Action) => T,
equals?: (a: T, b: T) => boolean,
) => useStore(toastStore, selector, equals);

export const useToast = () =>
useToastStore(
(state) => ({
toastData: state.toastData,
showToast: state.showToast,
hideToast: state.hideToast,
}),
shallow,
);
export const useToastStore = () =>
useStore(toastStore, (state) => state, shallow);
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"compilerOptions": {
"strict": true,
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
Expand Down

0 comments on commit 958f9e6

Please sign in to comment.