DynamicItemForm 개선: - 품목코드 자동생성 기능 추가 - 조건부 표시 로직 개선 - 불필요한 컴포넌트 정리 (DynamicField, DynamicSection 등) - 타입 시스템 단순화 새로운 기능: - Sales 페이지 마이그레이션 (견적관리, 거래처관리) - 공통 컴포넌트 추가 (atoms, molecules, organisms, templates) 문서화: - 구현 문서 및 참조 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
219 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
} |