'use client';
import { ReactNode, ComponentType, memo } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';
import { LucideIcon } from 'lucide-react';
type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline';
/**
* 정보 필드 컴포넌트
* 카드 내부의 레이블-값 쌍을 표시합니다.
*/
export interface InfoFieldProps {
label: string;
value: string | number | ReactNode;
valueClassName?: string;
className?: string;
}
export const InfoField = memo(function InfoField({
label,
value,
valueClassName = '',
className = '',
}: InfoFieldProps) {
return (
);
});
/**
* 통합 MobileCard Props
*
* molecules/MobileCard + organisms/ListMobileCard 기능 통합
* - 두 가지 사용 방식 모두 지원
* - 하위 호환성 유지
*/
export interface MobileCardProps {
// === 공통 (필수) ===
title: string | ReactNode;
// === 공통 (선택) ===
id?: string;
subtitle?: string;
description?: string;
icon?: ReactNode;
className?: string;
// === 클릭 핸들러 (별칭 지원) ===
onClick?: () => void;
onCardClick?: () => void; // onClick 별칭
// === Badge - 두 가지 형식 지원 ===
// 방식 1: 단순 (molecules 스타일)
badge?: string | { label: string; variant?: BadgeVariant };
badgeVariant?: BadgeVariant;
badgeClassName?: string;
// 방식 2: ReactNode (ListMobileCard 스타일)
statusBadge?: ReactNode;
headerBadges?: ReactNode;
// === Checkbox Selection (별칭 지원) ===
isSelected?: boolean;
onToggle?: () => void; // molecules 스타일
onToggleSelection?: () => void; // ListMobileCard 스타일
showCheckbox?: boolean; // 기본값: onToggle/onToggleSelection 있으면 true
// === 정보 표시 - 두 가지 방식 지원 ===
// 방식 1: 배열 (molecules 스타일) - 자동 렌더링
details?: Array<{
label: string;
value: string | ReactNode;
badge?: boolean;
badgeVariant?: BadgeVariant;
colSpan?: number;
}>;
fields?: Array<{
// details 별칭
label: string;
value: string;
badge?: boolean;
badgeVariant?: string;
colSpan?: number;
}>;
// 방식 2: ReactNode (ListMobileCard 스타일) - 완전 커스텀
infoGrid?: ReactNode;
// === Actions - 두 가지 방식 지원 ===
// 방식 1: ReactNode (권장)
// 방식 2: 배열 (자동 버튼 생성)
actions?:
| ReactNode
| Array<{
label: string;
onClick: () => void;
icon?: LucideIcon | ComponentType<{ className?: string }>;
variant?: 'default' | 'outline' | 'destructive';
}>;
// === Layout ===
detailsColumns?: 1 | 2 | 3; // details 그리드 컬럼 수 (기본: 2)
showSeparator?: boolean; // 구분선 표시 여부 (기본: infoGrid 사용시 true)
// === 추가 콘텐츠 ===
topContent?: ReactNode;
bottomContent?: ReactNode;
}
export function MobileCard({
// 공통
title,
id,
subtitle,
description,
icon,
className,
// 클릭
onClick,
onCardClick,
// Badge
badge,
badgeVariant = 'default',
badgeClassName,
statusBadge,
headerBadges,
// Selection
isSelected = false,
onToggle,
onToggleSelection,
showCheckbox,
// 정보 표시
details,
fields,
infoGrid,
// Actions
actions,
// Layout
detailsColumns = 2,
showSeparator,
// 추가 콘텐츠
topContent,
bottomContent,
}: MobileCardProps) {
// === 별칭 통합 ===
const handleClick = onClick || onCardClick;
const handleToggle = onToggle || onToggleSelection;
const itemDetails = details || fields || [];
const shouldShowCheckbox = showCheckbox ?? !!handleToggle;
const shouldShowSeparator = showSeparator ?? (!!infoGrid || !!headerBadges);
// === Badge 렌더링 ===
const renderBadge = () => {
// statusBadge 우선 (ReactNode)
if (statusBadge) return statusBadge;
if (!badge) return null;
if (typeof badge === 'string') {
return (
{badge}
);
}
return (
{badge.label}
);
};
// === Details 자동 렌더링 ===
const renderDetails = () => {
if (itemDetails.length === 0) return null;
const gridColsClass =
detailsColumns === 1
? 'grid-cols-1'
: detailsColumns === 3
? 'grid-cols-3'
: 'grid-cols-2';
return (
{itemDetails.map((detail, index) => (
{detail.label}:
{detail.badge ? (
{detail.value}
) : (
{detail.value}
)}
))}
);
};
// === Actions 렌더링 ===
const renderActions = () => {
if (!actions) return null;
// ReactNode인 경우 그대로 렌더링
if (!Array.isArray(actions)) {
return (
e.stopPropagation()}>
{shouldShowSeparator && }
{actions}
);
}
// Array인 경우 버튼 자동 생성
if (actions.length === 0) return null;
return (
e.stopPropagation()}>
{shouldShowSeparator &&
}
{actions.map((action, index) => {
const Icon = action.icon;
return (
);
})}
);
};
// === 정보 영역 렌더링 (infoGrid 우선) ===
const renderInfoArea = () => {
if (infoGrid) return infoGrid;
return renderDetails();
};
return (
{/* 상단 추가 콘텐츠 */}
{topContent}
{/* 헤더 영역 */}
{/* Checkbox */}
{shouldShowCheckbox && handleToggle && (
e.stopPropagation()}
className="mt-0.5 h-5 w-5"
/>
)}
{/* Icon */}
{icon && {icon}
}
{/* 헤더 뱃지들 */}
{headerBadges && (
{headerBadges}
)}
{/* 제목 */}
{title}
{/* 부제목 */}
{subtitle && (
{subtitle}
)}
{/* 우측 상단: 뱃지 */}
{renderBadge()}
{/* 설명 */}
{description && (
{description}
)}
{/* 구분선 (infoGrid/headerBadges 사용시) */}
{shouldShowSeparator && (infoGrid || itemDetails.length > 0) && (
)}
{/* 정보 영역 */}
{renderInfoArea()}
{/* 액션 버튼 */}
{renderActions()}
{/* 하단 추가 콘텐츠 */}
{bottomContent}
);
}
// 하위 호환성을 위한 별칭 export
export { MobileCard as ListMobileCard };