Files
sam-react-prod/src/components/molecules/StandardDialog.tsx
byeongcheolryu 3be5714805 refactor: 품목관리 시스템 리팩토링 및 Sales 페이지 추가
DynamicItemForm 개선:
- 품목코드 자동생성 기능 추가
- 조건부 표시 로직 개선
- 불필요한 컴포넌트 정리 (DynamicField, DynamicSection 등)
- 타입 시스템 단순화

새로운 기능:
- Sales 페이지 마이그레이션 (견적관리, 거래처관리)
- 공통 컴포넌트 추가 (atoms, molecules, organisms, templates)

문서화:
- 구현 문서 및 참조 문서 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 12:48:41 +09:00

219 lines
4.8 KiB
TypeScript

"use client";
/**
* 표준 다이얼로그 컴포넌트
*
* 디자인시스템관리 -> 다이얼로그 기준으로 작성된 공통 다이얼로그
* - 반응형 지원 (모바일/태블릿/데스크톱)
* - 접근성 준수 (DialogTitle 필수)
* - 일관된 스타일링
* - 상/하/좌/우 모든 마진 적용
*/
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export interface StandardDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description?: string;
children: React.ReactNode;
footer?: React.ReactNode;
size?: "sm" | "md" | "lg" | "xl" | "full";
showClose?: boolean;
className?: string;
}
const sizeClasses = {
sm: "sm:max-w-sm",
md: "sm:max-w-md",
lg: "sm:max-w-lg",
xl: "sm:max-w-xl",
full: "sm:max-w-[95vw]",
};
/**
* 표준 다이얼로그
*
* @example
* ```tsx
* <StandardDialog
* open={isOpen}
* onOpenChange={setIsOpen}
* title="품목 등록"
* description="새로운 품목을 등록합니다"
* size="lg"
* >
* <div>컨텐츠</div>
* </StandardDialog>
* ```
*/
export function StandardDialog({
open,
onOpenChange,
title,
description,
children,
footer,
size = "md",
showClose = true,
className,
}: StandardDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={cn(sizeClasses[size], className)}>
<DialogHeader className="px-6 pt-6">
<DialogTitle>{title}</DialogTitle>
{description && <DialogDescription>{description}</DialogDescription>}
</DialogHeader>
<div className="px-6 pb-6 overflow-y-auto max-h-[60vh]">{children}</div>
{footer && <DialogFooter>{footer}</DialogFooter>}
</DialogContent>
</Dialog>
);
}
/**
* 확인 다이얼로그
*/
export interface ConfirmDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description: string;
onConfirm: () => void;
confirmText?: string;
cancelText?: string;
confirmVariant?:
| "default"
| "destructive"
| "outline"
| "secondary"
| "ghost"
| "link";
loading?: boolean;
}
export function ConfirmDialog({
open,
onOpenChange,
title,
description,
onConfirm,
confirmText = "확인",
cancelText = "취소",
confirmVariant = "default",
loading = false,
}: ConfirmDialogProps) {
const handleConfirm = () => {
onConfirm();
onOpenChange(false);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader className="px-6 pt-6">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<div className="px-6 pb-4"></div>
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={loading}
>
{cancelText}
</Button>
<Button
variant={confirmVariant}
onClick={handleConfirm}
disabled={loading}
>
{confirmText}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
/**
* 폼 다이얼로그
*/
export interface FormDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description?: string;
children: React.ReactNode;
onSave: () => void;
onCancel?: () => void;
saveText?: string;
cancelText?: string;
size?: "sm" | "md" | "lg" | "xl" | "full";
loading?: boolean;
disabled?: boolean;
}
export function FormDialog({
open,
onOpenChange,
title,
description,
children,
onSave,
onCancel,
saveText = "저장",
cancelText = "취소",
size = "md",
loading = false,
disabled = false,
}: FormDialogProps) {
const handleCancel = () => {
if (onCancel) {
onCancel();
}
onOpenChange(false);
};
const handleSave = () => {
onSave();
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={cn(sizeClasses[size])}>
<DialogHeader className="px-6 pt-6">
<DialogTitle>{title}</DialogTitle>
{description && <DialogDescription>{description}</DialogDescription>}
</DialogHeader>
<div className="px-6 pb-4 overflow-y-auto max-h-[60vh]">{children}</div>
<DialogFooter>
<Button variant="outline" onClick={handleCancel} disabled={loading}>
{cancelText}
</Button>
<Button onClick={handleSave} disabled={loading || disabled}>
{saveText}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}