Skip to content

Commit 6617264

Browse files
authored
Merge branch 'dev' into feat/button#32
2 parents 77af43a + 9841fff commit 6617264

File tree

9 files changed

+198
-19
lines changed

9 files changed

+198
-19
lines changed

next.config.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
4-
// TurboPack 설정
5-
experimental: {
6-
turbo: {
7-
rules: {
8-
'*.svg': {
9-
loaders: ['@svgr/webpack'],
10-
as: '*.js',
11-
},
4+
turbopack: {
5+
rules: {
6+
'*.svg': {
7+
loaders: ['@svgr/webpack'],
8+
as: '*.js',
129
},
13-
},
10+
}
1411
},
1512
}
1613

src/app/design-system/page.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import Button from '@/shared/components/button/Button';
2-
import TextButton from '@/shared/components/button/TextButton';
1+
'use client';
2+
3+
import Button from '@/shared/components/Button';
4+
import ConfirmPop from '@/shared/components/ModalPop/ConfirmPop';
5+
import ModalLayout from '@/shared/components/ModalPop/ModalLayout';
6+
import { useState } from 'react';
37

48
function Page() {
9+
const [isModalOpen, setModalOpen] = useState(false);
10+
const [isConfirmOpen, setConfirmOpen] = useState(false);
11+
512
return (
613
<div className="p-6 space-y-6">
714
{/* 페이지 제목 */}
@@ -67,7 +74,36 @@ function Page() {
6774

6875
<div className="space-y-2">
6976
<h3 className="text-xl font-medium border-b pb-1">popup</h3>
70-
{/* 여기 컴포넌트 삽입 */}
77+
{/* 모달 열기 버튼 */}
78+
<button
79+
onClick={() => setModalOpen(true)}
80+
className="px-4 py-2 bg-blue-500 text-white rounded"
81+
>
82+
기본 모달 열기
83+
</button>
84+
<button
85+
onClick={() => setConfirmOpen(true)}
86+
className="ml-4 px-4 py-2 bg-blue-500 text-white rounded"
87+
>
88+
컨펌 모달 열기
89+
</button>
90+
91+
<ModalLayout
92+
open={isModalOpen}
93+
onClose={() => setModalOpen(false)}
94+
title="제목"
95+
description="설명"
96+
buttons={<>{/* 여기다가 버튼 컴포넌트 넣으면 됨 */}</>}
97+
>
98+
<div>모달팝업 내용</div>
99+
</ModalLayout>
100+
101+
<ConfirmPop
102+
open={isConfirmOpen}
103+
onClose={() => setConfirmOpen(false)}
104+
title="Confirm제목"
105+
description="설명"
106+
/>
71107
</div>
72108
</div>
73109
</div>

src/app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export default function RootLayout({
1212
}>) {
1313
return (
1414
<html lang="ko-KR">
15-
<body>{children}</body>
15+
<body>
16+
{children}
17+
<div id="modal-root"></div>
18+
</body>
1619
</html>
1720
);
1821
}

src/shared/assets/icons/close_20.svg

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import ModalLayout from './ModalLayout';
2+
3+
interface Props {
4+
ref?: React.Ref<HTMLDivElement>;
5+
open: boolean;
6+
onClose: () => void;
7+
title?: string;
8+
description?: React.ReactNode;
9+
children?: React.ReactNode;
10+
}
11+
function ConfirmPop({ ref, open, onClose, title, description, children }: Props) {
12+
return (
13+
<ModalLayout
14+
ref={ref}
15+
size="sm"
16+
open={open}
17+
onClose={onClose}
18+
title={title}
19+
description={description}
20+
buttons={
21+
<>
22+
<button>취소</button>
23+
<button>확인</button>
24+
</>
25+
}
26+
>
27+
{children}
28+
</ModalLayout>
29+
);
30+
}
31+
export default ConfirmPop;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use client';
2+
3+
import Close from '@/shared/assets/icons/close_32.svg';
4+
import Portal from './Portal';
5+
import tw from '@/shared/utills/tw';
6+
import { useEffect } from 'react';
7+
8+
interface Props {
9+
ref?: React.Ref<HTMLDivElement>;
10+
size?: 'sm' | 'md';
11+
open: boolean;
12+
onClose: () => void;
13+
title?: string;
14+
description?: React.ReactNode;
15+
children?: React.ReactNode;
16+
buttons?: React.ReactNode;
17+
}
18+
19+
function ModalLayout({
20+
ref,
21+
size = 'md',
22+
open,
23+
onClose,
24+
title,
25+
description,
26+
children,
27+
buttons,
28+
}: Props) {
29+
// ESC키 모달 닫기
30+
useEffect(() => {
31+
if (!open) return;
32+
33+
const handleKeyDown = (e: KeyboardEvent) => {
34+
if (e.key === 'Escape') onClose();
35+
};
36+
37+
document.addEventListener('keydown', handleKeyDown);
38+
39+
return () => document.removeEventListener('keydown', handleKeyDown);
40+
}, [open, onClose]);
41+
42+
if (!open) return null;
43+
44+
return (
45+
<Portal>
46+
<div className="fixed inset-0 bg-black/80 flex-center " onClick={onClose} aria-hidden="true">
47+
<div
48+
className={tw(
49+
'relative p-8 rounded-lg bg-bg-pop shadow-[0_4px_9px_0_rgba(255,255,255,0.25)]',
50+
size === 'sm' && 'p-5 w-[18.75rem]',
51+
size === 'md' && 'w-[31.25rem]'
52+
)}
53+
ref={ref}
54+
aria-modal="true"
55+
aria-labelledby={title ? 'modal-title' : undefined}
56+
aria-describedby={description ? 'modal-description' : undefined}
57+
onClick={(e) => e.stopPropagation()}
58+
>
59+
<div className={tw('flex items-center flex-col gap-2')}>
60+
{title && (
61+
<h1
62+
id="modal-title"
63+
className={tw('text-2xl font-bold text-white', size === 'sm' && 'text-xl')}
64+
>
65+
{title}
66+
</h1>
67+
)}
68+
{description && (
69+
<p id="modal-description" className="text-white">
70+
{description}
71+
</p>
72+
)}
73+
</div>
74+
75+
{children && <div className="mt-5 py-2 text-white">{children}</div>}
76+
77+
{buttons && (
78+
<div className={tw('flex justify-center gap-2 pt-8', size === 'sm' && 'pt-5')}>
79+
{buttons}
80+
</div>
81+
)}
82+
<button onClick={onClose} aria-label="팝업 닫기" className="absolute top-2 right-2">
83+
<Close aria-hidden className="w-8 h-8" />
84+
</button>
85+
</div>
86+
</div>
87+
</Portal>
88+
);
89+
}
90+
export default ModalLayout;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use client';
2+
import { ReactNode, useEffect, useState } from 'react';
3+
import { createPortal } from 'react-dom';
4+
5+
interface Props {
6+
children: ReactNode;
7+
}
8+
9+
export default function Portal({ children }: Props) {
10+
const [target, setTarget] = useState<HTMLElement | null>(null);
11+
12+
useEffect(() => {
13+
const el = document.getElementById('modal-root');
14+
setTarget(el);
15+
}, []);
16+
17+
if (!target) return null;
18+
return createPortal(children, target);
19+
}

src/shared/styles/_base.css

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
@layer base;
2-
1+
@layer base {
2+
button {
3+
cursor: pointer;
4+
}
5+
}

0 commit comments

Comments
 (0)