Skip to content
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
15 changes: 6 additions & 9 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { NextConfig } from "next";

const nextConfig: NextConfig = {
// TurboPack 설정
experimental: {
turbo: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
}
},
}

Expand Down
39 changes: 38 additions & 1 deletion src/app/design-system/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
'use client';

import Button from '@/shared/components/Button';
import ConfirmPop from '@/shared/components/ModalPop/ConfirmPop';
import ModalLayout from '@/shared/components/ModalPop/ModalLayout';
import { useState } from 'react';

function Page() {
const [isModalOpen, setModalOpen] = useState(false);
const [isConfirmOpen, setConfirmOpen] = useState(false);

return (
<div className="p-6 space-y-6">
{/* 페이지 제목 */}
Expand Down Expand Up @@ -54,7 +62,36 @@ function Page() {

<div className="space-y-2">
<h3 className="text-xl font-medium border-b pb-1">popup</h3>
{/* 여기 컴포넌트 삽입 */}
{/* 모달 열기 버튼 */}
<button
onClick={() => setModalOpen(true)}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
기본 모달 열기
</button>
<button
onClick={() => setConfirmOpen(true)}
className="ml-4 px-4 py-2 bg-blue-500 text-white rounded"
>
컨펌 모달 열기
</button>

<ModalLayout
open={isModalOpen}
onClose={() => setModalOpen(false)}
title="제목"
description="설명"
buttons={<>{/* 여기다가 버튼 컴포넌트 넣으면 됨 */}</>}
>
<div>모달팝업 내용</div>
</ModalLayout>

<ConfirmPop
open={isConfirmOpen}
onClose={() => setConfirmOpen(false)}
title="Confirm제목"
description="설명"
/>
</div>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export default function RootLayout({
}>) {
return (
<html lang="ko-KR">
<body>{children}</body>
<body>
{children}
<div id="modal-root"></div>
</body>
</html>
);
}
4 changes: 0 additions & 4 deletions src/shared/assets/icons/close_20.svg

This file was deleted.

4 changes: 4 additions & 0 deletions src/shared/assets/icons/close_32.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions src/shared/components/ModalPop/ConfirmPop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import ModalLayout from './ModalLayout';

interface Props {
ref?: React.Ref<HTMLDivElement>;
open: boolean;
onClose: () => void;
title?: string;
description?: React.ReactNode;
children?: React.ReactNode;
}
function ConfirmPop({ ref, open, onClose, title, description, children }: Props) {
return (
<ModalLayout
ref={ref}
size="sm"
open={open}
onClose={onClose}
title={title}
description={description}
buttons={
<>
<button>취소</button>
<button>확인</button>
</>
}
>
{children}
</ModalLayout>
);
}
export default ConfirmPop;
90 changes: 90 additions & 0 deletions src/shared/components/ModalPop/ModalLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use client';

import Close from '@/shared/assets/icons/close_32.svg';
import Portal from './Portal';
import tw from '@/shared/utills/tw';
import { useEffect } from 'react';

interface Props {
ref?: React.Ref<HTMLDivElement>;
size?: 'sm' | 'md';
open: boolean;
onClose: () => void;
title?: string;
description?: React.ReactNode;
children?: React.ReactNode;
buttons?: React.ReactNode;
}

function ModalLayout({
ref,
size = 'md',
open,
onClose,
title,
description,
children,
buttons,
}: Props) {
// ESC키 모달 닫기
useEffect(() => {
if (!open) return;

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};

document.addEventListener('keydown', handleKeyDown);

return () => document.removeEventListener('keydown', handleKeyDown);
}, [open, onClose]);

if (!open) return null;

return (
<Portal>
<div className="fixed inset-0 bg-black/80 flex-center " onClick={onClose} aria-hidden="true">
<div
className={tw(
'relative p-8 rounded-lg bg-bg-pop shadow-[0_4px_9px_0_rgba(255,255,255,0.25)]',
size === 'sm' && 'p-5 w-[18.75rem]',
size === 'md' && 'w-[31.25rem]'
)}
ref={ref}
aria-modal="true"
aria-labelledby={title ? 'modal-title' : undefined}
aria-describedby={description ? 'modal-description' : undefined}
onClick={(e) => e.stopPropagation()}
>
<div className={tw('flex items-center flex-col gap-2')}>
{title && (
<h1
id="modal-title"
className={tw('text-2xl font-bold text-white', size === 'sm' && 'text-xl')}
>
{title}
</h1>
)}
{description && (
<p id="modal-description" className="text-white">
{description}
</p>
)}
</div>

{children && <div className="mt-5 py-2 text-white">{children}</div>}

{buttons && (
<div className={tw('flex justify-center gap-2 pt-8', size === 'sm' && 'pt-5')}>
{buttons}
</div>
)}
<button onClick={onClose} aria-label="팝업 닫기" className="absolute top-2 right-2">
<Close aria-hidden className="w-8 h-8" />
</button>
</div>
</div>
</Portal>
);
}
export default ModalLayout;
19 changes: 19 additions & 0 deletions src/shared/components/ModalPop/Portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client';
import { ReactNode, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';

interface Props {
children: ReactNode;
}

export default function Portal({ children }: Props) {
const [target, setTarget] = useState<HTMLElement | null>(null);

useEffect(() => {
const el = document.getElementById('modal-root');
setTarget(el);
}, []);

if (!target) return null;
return createPortal(children, target);
}
7 changes: 5 additions & 2 deletions src/shared/styles/_base.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
@layer base;

@layer base {
button {
cursor: pointer;
}
}