Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.14",
Copy link

@coderabbitai coderabbitai bot Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

잘못된 패키지 의존성이 추가되었습니다.

파이프라인 실패 로그에 따르면 실제 구현에서는 @radix-ui/react-alert-dialog를 사용하려고 하지만, 여기서는 @radix-ui/react-dialog가 추가되었습니다. PR 목표에서 언급한 대로 알림 모달의 적절한 동작을 위해 @radix-ui/react-alert-dialog를 사용해야 합니다.

다음 diff를 적용하여 올바른 패키지를 추가하세요:

-    "@radix-ui/react-dialog": "^1.1.14",
+    "@radix-ui/react-alert-dialog": "^1.1.14",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@radix-ui/react-dialog": "^1.1.14",
- "@radix-ui/react-dialog": "^1.1.14",
+ "@radix-ui/react-alert-dialog": "^1.1.14",
🤖 Prompt for AI Agents
In package.json at line 20, the dependency "@radix-ui/react-dialog" is
incorrectly added instead of "@radix-ui/react-alert-dialog". Replace
"@radix-ui/react-dialog" with "@radix-ui/react-alert-dialog" to match the actual
implementation and ensure the alert modal functions correctly as intended.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wkdtnqls0506 Perfect! I can see you've fixed the package dependency issue in commit b7ca0ad. Thank you for quickly applying the correction to use @radix-ui/react-alert-dialog instead of @radix-ui/react-dialog. This ensures the AlertModal component will have the proper alert dialog behavior that prevents closing on overlay clicks, which is essential for alert modals that require user confirmation.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

"@hookform/resolvers": "^5.1.1",
"@tanstack/react-query": "^5.77.0",
"@tanstack/react-query-devtools": "^5.77.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

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

61 changes: 61 additions & 0 deletions src/components/ui/AlertModal/AlertModal.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { style } from "@vanilla-extract/css";

import { radius, semantic, typography } from "@/styles";
import { zIndex } from "@/styles/zIndex.css";

export const overlay = style({
position: "fixed",
inset: 0,
backgroundColor: semantic.background.dim,
zIndex: zIndex.overlay,
});

export const content = style({
maxHeight: "80vh",
display: "flex",
flexDirection: "column",
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "min(30rem, 90vw)",
background: semantic.background.white,
borderRadius: radius[120],
zIndex: zIndex.modal,
});

export const textWrapper = style({
overflowY: "auto",
padding: "4.8rem 1.6rem",
display: "flex",
flexDirection: "column",
alignItems: "center",
});

export const title = style({
...typography.title3Sb,
color: semantic.text.normal,
});

export const description = style({
...typography.body2Rg,
color: semantic.text.alternative,
marginTop: "0.8rem",
});

export const buttonGroup = style({
display: "flex",
width: "100%",
});

export const cancelButton = style({
flex: 1,
minWidth: 0,
overflow: "hidden",
});

export const confirmButton = style({
flex: 1,
minWidth: 0,
overflow: "hidden",
});
124 changes: 124 additions & 0 deletions src/components/ui/AlertModal/AlertModal.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { useState } from "react";

import { Button } from "../Button";
import { AlertModal, type AlertModalProps } from "./AlertModal";

const meta: Meta<typeof AlertModal> = {
title: "Components/AlertModal",
component: AlertModal,
tags: ["autodocs"],
argTypes: {
open: { control: false },
onOpenChange: { control: false },
title: { control: "text", description: "모달의 타이틀" },
description: { control: "text", description: "모달의 서브 설명" },
confirmLabel: { control: "text", description: "확인 버튼 라벨" },
cancelLabel: { control: "text", description: "취소 버튼 라벨" },
onConfirm: { action: "onConfirm", description: "확인 버튼 클릭 시 콜백" },
onCancel: { action: "onCancel", description: "취소 버튼 클릭 시 콜백" },
},
parameters: {
layout: "centered",
docs: {
description: {
component:
"AlertModal은 사용자에게 확인/취소 액션을 요청할 때 사용하는 모달입니다. title, description, confirmLabel, cancelLabel, onConfirm, onCancel 등을 지원합니다.",
},
},
},
};

export default meta;
type Story = StoryObj<typeof AlertModal>;

const AlertModalTemplate = (args: Partial<AlertModalProps>) => {
const [open, setOpen] = useState(false);

return (
<>
<Button onClick={() => setOpen(true)}>모달 열기</Button>
<AlertModal {...args} open={open} onOpenChange={setOpen} />
</>
);
};

export const Default: Story = {
render: args => <AlertModalTemplate {...args} />,
args: {
title: "정말 삭제하시겠어요?",
description: "삭제 후에는 복구할 수 없습니다.",
confirmLabel: "삭제",
cancelLabel: "취소",
},
parameters: {
docs: {
description: {
story:
"기본 AlertModal. 타이틀과 설명, 확인/취소 버튼을 모두 포함합니다.",
},
},
},
};

export const NoDescription: Story = {
render: args => <AlertModalTemplate {...args} />,
args: {
title: "약관을 동의하시겠습니까?",
confirmLabel: "동의",
cancelLabel: "취소",
},
parameters: {
docs: {
description: {
story: "description 없이 타이틀과 버튼만 표시되는 AlertModal입니다.",
},
},
},
};

export const AsyncConfirm: Story = {
render: args => {
const AsyncTemplate = () => {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);

const handleConfirm = async () => {
setLoading(true);
await new Promise(res => setTimeout(res, 1500));
setLoading(false);
setOpen(false);
alert("비동기 작업 완료!");
};

return (
<>
<Button onClick={() => setOpen(true)}>모달 열기</Button>
<AlertModal
{...args}
open={open}
onOpenChange={setOpen}
onConfirm={handleConfirm}
confirmLabel={loading ? "처리 중..." : args.confirmLabel}
/>
</>
);
};

return <AsyncTemplate />;
},
args: {
title: "비동기 확인 예시",
description: "확인 버튼 클릭 시 비동기 작업을 실행합니다.",
confirmLabel: "확인",
cancelLabel: "취소",
},
parameters: {
docs: {
description: {
story:
"확인 버튼 클릭 시 비동기 작업을 수행하는 AlertModal 예시입니다. 버튼 라벨이 로딩 상태로 변경됩니다.",
},
},
},
};
108 changes: 108 additions & 0 deletions src/components/ui/AlertModal/AlertModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { DialogProps } from "@radix-ui/react-dialog";
import * as Dialog from "@radix-ui/react-dialog";

import { Button } from "../Button";
import * as styles from "./AlertModal.css";

export type AlertModalProps = {
/** 모달 제목 */
title?: string;

/** 모달 설명 */
description?: string;

/** 확인 버튼 클릭 시 실행되는 콜백 */
onConfirm?: () => void | Promise<void>;

/** 취소 버튼 클릭 시 실행되는 콜백 */
onCancel?: () => void;

/** 확인 버튼 텍스트 */
confirmLabel?: string;

/** 취소 버튼 텍스트 */
cancelLabel?: string;
} & DialogProps;

/**
* AlertModal 컴포넌트
*
* @description
* Radix UI의 Dialog를 래핑한 모달 컴포넌트입니다.
* 제목과 설명을 표시하고, 확인/취소 버튼을 제공합니다.
*
* @example
* ```tsx
* const [open, setOpen] = useState(false);
*
* <AlertModal
* open={open}
* onOpenChange={setOpen}
* title="로그아웃 하시겠어요?"
* description="로그아웃하면 다시 로그인해야 합니다."
* cancelLabel="취소"
* confirmLabel="로그아웃"
* onCancel={() => console.log("취소")}
* onConfirm={() => console.log("확인")}
* />
* ```
Comment on lines +35 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

JSDoc 예제 코드에서 AlertDialog 래퍼 사용 누락

JSDoc 예제에서 footer 버튼들이 AlertDialog.CancelAlertDialog.Action으로 래핑되지 않았습니다. 이는 실제 사용 시 혼란을 야기할 수 있습니다.

예제 코드를 다음과 같이 수정하세요:

 * footer={
 *   <>
-*     <Button variant="assistive">취소</Button>
-*     <Button variant="primary">삭제</Button>
+*     <AlertDialog.Cancel asChild>
+*       <Button variant="assistive">취소</Button>
+*     </AlertDialog.Cancel>
+*     <AlertDialog.Action asChild>
+*       <Button variant="primary">삭제</Button>
+*     </AlertDialog.Action>
 *   </>
 * }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* @example
* ```tsx
* <AlertModal
* title="정말 삭제하시겠어요?"
* trigger={<Button>모달 열기</Button>}
* content={<div>삭제하면 복구할 수 없습니다.</div>}
* footer={
* <>
* <Button variant="assistive">취소</Button>
* <Button variant="primary">삭제</Button>
* </>
* }
* />
* ```
* @example
*
🤖 Prompt for AI Agents
In src/components/ui/AlertModal/AlertModal.tsx between lines 35 and 48, the
JSDoc example code for AlertModal is missing the wrapping of footer buttons with
AlertDialog.Cancel and AlertDialog.Action components. To fix this, update the
example so that the cancel button is wrapped with AlertDialog.Cancel and the
delete button is wrapped with AlertDialog.Action, reflecting the actual usage
pattern and preventing confusion.

*/
export const AlertModal = ({
open,
onOpenChange,
title,
description,
onConfirm,
onCancel,
confirmLabel = "확인",
cancelLabel = "취소",
}: AlertModalProps) => {
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className={styles.overlay} />
<Dialog.Content className={styles.content}>
{(title || description) && (
<div className={styles.textWrapper}>
{title && (
<Dialog.Title className={styles.title}>{title}</Dialog.Title>
)}
{description && (
<Dialog.Description className={styles.description}>
{description}
</Dialog.Description>
)}
</div>
)}

<div className={styles.buttonGroup}>
<Dialog.Close asChild>
<Button
className={styles.cancelButton}
variant='assistive'
size='large'
onClick={onCancel}
style={{
borderRadius: "0 0 0 1.2rem",
}}
>
{cancelLabel}
</Button>
</Dialog.Close>
<Button
className={styles.confirmButton}
variant='primary'
size='large'
onClick={onConfirm}
style={{
borderRadius: "0 0 1.2rem 0",
}}
>
{confirmLabel}
</Button>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
};
1 change: 1 addition & 0 deletions src/components/ui/AlertModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AlertModal } from "./AlertModal";
3 changes: 2 additions & 1 deletion src/components/ui/GNB/GNB.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { style } from "@vanilla-extract/css";
import { recipe } from "@vanilla-extract/recipes";

import { semantic, typography } from "@/styles";
import { zIndex } from "@/styles/zIndex.css";

export const wrapper = recipe({
base: {
Expand All @@ -12,7 +13,7 @@ export const wrapper = recipe({
width: "100%",
height: "5.6rem",
padding: "1.4rem 2rem",
zIndex: 999,
zIndex: zIndex.gnb,
},
variants: {
background: {
Expand Down
4 changes: 4 additions & 0 deletions src/styles/reset.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ globalStyle("html", {
fontSize: "62.5%",
});

globalStyle("html, body", {
height: "100%",
});

globalStyle(
"html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video",
{
Expand Down
9 changes: 9 additions & 0 deletions src/styles/zIndex.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createGlobalTheme } from "@vanilla-extract/css";

export const zIndex = createGlobalTheme(":root", {
base: "0",
gnb: "100",
overlay: "900",
modal: "1000",
toast: "1100",
});