Card
예약 정보, 고객 프로필, 서비스 상세 정보를 시각적으로 그룹화하는 컨테이너 컴포넌트입니다.
개요
Card는 관련된 정보를 하나의 시각적 단위로 묶어 표시하는 컴포넌트입니다. Cals 예약 관리 시스템에서 예약 카드, 고객 프로필, 서비스 정보 등을 표시할 때 사용합니다.
주요 특징:
- 예약 상태별 시각적 구분 (보더/배경)
- Header, Content, Footer 영역 분리
- Cals 브랜딩 컬러 통합
- 반응형 레이아웃 지원
설치
npx @vortex/cli add card기본 사용법
import {
Card,
CardHeader,
CardContent,
CardFooter,
CardTitle,
CardDescription,
} from "@vortex/ui";
export default function ReservationCard() {
return (
<Card>
<CardHeader>
<CardTitle>헤어 커트 예약</CardTitle>
<CardDescription>2024년 1월 15일 오후 2시</CardDescription>
</CardHeader>
<CardContent>
<p>고객: 홍길동</p>
<p>담당: 김디자이너</p>
</CardContent>
<CardFooter>
<p className="text-sm text-muted-foreground">예약 번호: #12345</p>
</CardFooter>
</Card>
);
}Variants
기본 Card
<Card>
<CardHeader>
<CardTitle>기본 카드</CardTitle>
<CardDescription>설명 텍스트</CardDescription>
</CardHeader>
<CardContent>
<p>카드 내용이 여기에 표시됩니다.</p>
</CardContent>
</Card>예약 상태별 Card
{
/* Available - 예약 가능 */
}
<Card className="border-[#4caf50] bg-[#4caf50]/5">
<CardHeader>
<CardTitle className="text-[#4caf50]">예약 가능</CardTitle>
<CardDescription>2024년 1월 15일 오후 2시</CardDescription>
</CardHeader>
<CardContent>
<p>이 시간대는 예약 가능합니다.</p>
</CardContent>
</Card>;
{
/* Pending - 예약 대기 */
}
<Card className="border-[#ff9800] bg-[#ff9800]/5">
<CardHeader>
<CardTitle className="text-[#ff9800]">예약 대기</CardTitle>
<CardDescription>홍길동님의 예약</CardDescription>
</CardHeader>
<CardContent>
<p>예약 확인 대기 중입니다.</p>
</CardContent>
</Card>;
{
/* Confirmed - 예약 확정 */
}
<Card className="border-[#03a9f4] bg-[#03a9f4]/5">
<CardHeader>
<CardTitle className="text-[#03a9f4]">예약 확정</CardTitle>
<CardDescription>김고객님의 예약</CardDescription>
</CardHeader>
<CardContent>
<p>예약이 확정되었습니다.</p>
</CardContent>
</Card>;
{
/* Cancelled - 예약 취소 */
}
<Card className="border-[#f44336] bg-[#f44336]/5">
<CardHeader>
<CardTitle className="text-[#f44336]">예약 취소</CardTitle>
<CardDescription>이전 예약 기록</CardDescription>
</CardHeader>
<CardContent>
<p>고객 요청으로 취소되었습니다.</p>
</CardContent>
</Card>;
{
/* Completed - 서비스 완료 */
}
<Card className="border-[#9c27b0] bg-[#9c27b0]/5">
<CardHeader>
<CardTitle className="text-[#9c27b0]">서비스 완료</CardTitle>
<CardDescription>2024년 1월 10일</CardDescription>
</CardHeader>
<CardContent>
<p>서비스가 완료되었습니다.</p>
</CardContent>
</Card>;인터랙티브 Card
<Card className="cursor-pointer hover:shadow-lg transition-shadow">
<CardHeader>
<CardTitle>클릭 가능한 카드</CardTitle>
<CardDescription>마우스를 올려보세요</CardDescription>
</CardHeader>
<CardContent>
<p>이 카드는 클릭할 수 있습니다.</p>
</CardContent>
</Card>Cals 브랜딩
Primary Pink 강조
<Card className="border-[#e91e63]">
<CardHeader>
<CardTitle className="text-[#e91e63]">VIP 고객 예약</CardTitle>
<CardDescription>우선 처리 예약</CardDescription>
</CardHeader>
<CardContent>
<p>VIP 고객의 예약입니다.</p>
</CardContent>
</Card>Secondary Blue 정보 카드
<Card className="bg-[#03a9f4]/10 border-[#03a9f4]">
<CardHeader>
<CardTitle className="text-[#03a9f4]">안내 사항</CardTitle>
</CardHeader>
<CardContent>
<p>예약 변경은 24시간 전까지 가능합니다.</p>
</CardContent>
</Card>Accent Purple 프로모션
<Card className="bg-gradient-to-br from-[#9c27b0]/20 to-[#e91e63]/20 border-[#9c27b0]">
<CardHeader>
<CardTitle className="text-[#9c27b0]">특별 프로모션</CardTitle>
<CardDescription>이번 주 한정</CardDescription>
</CardHeader>
<CardContent>
<p>첫 예약 고객 20% 할인</p>
</CardContent>
</Card>브랜드별 비교
| 특성 | Foundation | iCignal | Cals |
|---|---|---|---|
| 목적 | 범용 카드 | 전자 담배 제품/이벤트 | 예약 관리 시스템 |
| 디자인 | 중립적 | 프리미엄 다크 | 밝고 친근한 |
| Primary | - | Blue #0066cc | Pink #e91e63 |
| 예약 상태 | - | - | 5가지 컬러 시스템 |
| 주요 사용 | 범용 컨테이너 | 제품 카드, 이벤트 | 예약 카드, 고객 프로필 |
| 인터랙션 | 기본 | 호버 효과 강조 | 상태별 시각적 피드백 |
Props API
Card
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | 추가 CSS 클래스 |
| children | ReactNode | - | 카드 내용 |
CardHeader
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | 추가 CSS 클래스 |
| children | ReactNode | - | 헤더 내용 (Title, Description) |
CardTitle
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | 추가 CSS 클래스 |
| children | ReactNode | - | 제목 텍스트 |
CardDescription
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | 추가 CSS 클래스 |
| children | ReactNode | - | 설명 텍스트 |
CardContent
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | 추가 CSS 클래스 |
| children | ReactNode | - | 본문 내용 |
CardFooter
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | 추가 CSS 클래스 |
| children | ReactNode | - | 푸터 내용 |
접근성
- 시맨틱 HTML:
<div>기반, 필요 시role속성 추가 - 키보드 네비게이션: 인터랙티브 카드는
tabIndex={0}추가 - ARIA 레이블: 복잡한 카드는
aria-label또는aria-labelledby사용 - 색상 대비: WCAG AA 기준 충족 (텍스트 대비 4.5:1 이상)
{
/* 접근성 개선 예제 */
}
<Card
role="article"
aria-labelledby="reservation-title"
className="cursor-pointer"
tabIndex={0}
>
<CardHeader>
<CardTitle id="reservation-title">헤어 커트 예약</CardTitle>
<CardDescription>2024년 1월 15일 오후 2시</CardDescription>
</CardHeader>
<CardContent>
<p>고객: 홍길동</p>
</CardContent>
</Card>;예제
1. 예약 상세 카드
import {
Card,
CardHeader,
CardContent,
CardFooter,
CardTitle,
CardDescription,
} from "@vortex/ui";
import { Calendar, Clock, User, Phone } from "lucide-react";
export default function ReservationDetailCard() {
return (
<Card className="border-[#03a9f4] bg-[#03a9f4]/5">
<CardHeader>
<CardTitle className="text-[#03a9f4] flex items-center gap-2">
<Calendar className="w-5 h-5" />
예약 확정
</CardTitle>
<CardDescription>예약 번호: #RSV-20240115-001</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-muted-foreground" />
<span>고객: 홍길동</span>
</div>
<div className="flex items-center gap-2">
<Phone className="w-4 h-4 text-muted-foreground" />
<span>연락처: 010-1234-5678</span>
</div>
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-muted-foreground" />
<span>2024년 1월 15일 오후 2:00</span>
</div>
<div className="mt-4 p-3 bg-white rounded-md">
<p className="text-sm font-medium">서비스: 헤어 커트 + 스타일링</p>
<p className="text-sm text-muted-foreground">담당: 김디자이너</p>
<p className="text-sm text-muted-foreground">소요 시간: 약 60분</p>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<p className="text-sm text-muted-foreground">결제 금액: 50,000원</p>
<p className="text-sm font-medium text-[#03a9f4]">확정됨</p>
</CardFooter>
</Card>
);
}2. 고객 프로필 카드
import {
Card,
CardHeader,
CardContent,
CardTitle,
CardDescription,
} from "@vortex/ui";
import { User, Mail, Phone, Calendar } from "lucide-react";
export default function CustomerProfileCard() {
return (
<Card className="border-[#e91e63]">
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-[#e91e63]">VIP 고객</CardTitle>
<CardDescription>멤버십 레벨: 플래티넘</CardDescription>
</div>
<div className="w-16 h-16 rounded-full bg-[#e91e63]/10 flex items-center justify-center">
<User className="w-8 h-8 text-[#e91e63]" />
</div>
</div>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-muted-foreground" />
<span>홍길동</span>
</div>
<div className="flex items-center gap-2">
<Mail className="w-4 h-4 text-muted-foreground" />
<span>hong@example.com</span>
</div>
<div className="flex items-center gap-2">
<Phone className="w-4 h-4 text-muted-foreground" />
<span>010-1234-5678</span>
</div>
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-muted-foreground" />
<span>가입일: 2023년 6월 1일</span>
</div>
<div className="mt-4 p-3 bg-gradient-to-r from-[#e91e63]/10 to-[#9c27b0]/10 rounded-md">
<p className="text-sm font-medium">누적 방문: 24회</p>
<p className="text-sm text-muted-foreground">
최근 방문: 2024년 1월 10일
</p>
</div>
</CardContent>
</Card>
);
}3. 시간대 예약 상태 카드
import { Card, CardHeader, CardContent, CardTitle } from "@vortex/ui";
import { Clock, Check, X } from "lucide-react";
const timeSlots = [
{ time: "10:00", status: "available", customer: null },
{ time: "11:00", status: "confirmed", customer: "김고객" },
{ time: "12:00", status: "available", customer: null },
{ time: "13:00", status: "pending", customer: "이고객" },
{ time: "14:00", status: "confirmed", customer: "박고객" },
{ time: "15:00", status: "available", customer: null },
];
const statusConfig = {
available: {
color: "#4caf50",
bg: "bg-[#4caf50]/5",
border: "border-[#4caf50]",
label: "예약 가능",
},
pending: {
color: "#ff9800",
bg: "bg-[#ff9800]/5",
border: "border-[#ff9800]",
label: "대기 중",
},
confirmed: {
color: "#03a9f4",
bg: "bg-[#03a9f4]/5",
border: "border-[#03a9f4]",
label: "확정됨",
},
};
export default function TimeSlotCards() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{timeSlots.map((slot) => {
const config = statusConfig[slot.status];
return (
<Card
key={slot.time}
className={`${config.border} ${config.bg} cursor-pointer hover:shadow-md transition-all`}
>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span
className="flex items-center gap-2"
style={{ color: config.color }}
>
<Clock className="w-5 h-5" />
{slot.time}
</span>
{slot.status === "available" ? (
<Check className="w-5 h-5 text-[#4caf50]" />
) : (
<X className="w-5 h-5" style={{ color: config.color }} />
)}
</CardTitle>
</CardHeader>
<CardContent>
{slot.customer ? (
<p className="text-sm">{slot.customer}님 예약</p>
) : (
<p className="text-sm text-muted-foreground">{config.label}</p>
)}
</CardContent>
</Card>
);
})}
</div>
);
}관련 컴포넌트
Last updated on