-
Notifications
You must be signed in to change notification settings - Fork 0
feat: AlertModal 컴포넌트 생성 (#87) #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
a6a921d
4d687c9
b37827c
10a6931
c4ad3a1
b7ca0ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| 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", | ||
| }); |
| 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 예시입니다. 버튼 라벨이 로딩 상태로 변경됩니다.", | ||
| }, | ||
| }, | ||
| }, | ||
| }; |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion JSDoc 예제 코드에서 AlertDialog 래퍼 사용 누락 JSDoc 예제에서 footer 버튼들이 예제 코드를 다음과 같이 수정하세요: * 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| 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> | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { AlertModal } from "./AlertModal"; |
| 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", | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
잘못된 패키지 의존성이 추가되었습니다.
파이프라인 실패 로그에 따르면 실제 구현에서는
@radix-ui/react-alert-dialog를 사용하려고 하지만, 여기서는@radix-ui/react-dialog가 추가되었습니다. PR 목표에서 언급한 대로 알림 모달의 적절한 동작을 위해@radix-ui/react-alert-dialog를 사용해야 합니다.다음 diff를 적용하여 올바른 패키지를 추가하세요:
📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
b7ca0ad
There was a problem hiding this comment.
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-dialoginstead 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!