Skip to Content
ComponentsCalsLayoutCard

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>

브랜드별 비교

특성FoundationiCignalCals
목적범용 카드전자 담배 제품/이벤트예약 관리 시스템
디자인중립적프리미엄 다크밝고 친근한
Primary-Blue #0066ccPink #e91e63
예약 상태--5가지 컬러 시스템
주요 사용범용 컨테이너제품 카드, 이벤트예약 카드, 고객 프로필
인터랙션기본호버 효과 강조상태별 시각적 피드백

Props API

Card

PropTypeDefaultDescription
classNamestring-추가 CSS 클래스
childrenReactNode-카드 내용

CardHeader

PropTypeDefaultDescription
classNamestring-추가 CSS 클래스
childrenReactNode-헤더 내용 (Title, Description)

CardTitle

PropTypeDefaultDescription
classNamestring-추가 CSS 클래스
childrenReactNode-제목 텍스트

CardDescription

PropTypeDefaultDescription
classNamestring-추가 CSS 클래스
childrenReactNode-설명 텍스트

CardContent

PropTypeDefaultDescription
classNamestring-추가 CSS 클래스
childrenReactNode-본문 내용

CardFooter

PropTypeDefaultDescription
classNamestring-추가 CSS 클래스
childrenReactNode-푸터 내용

접근성

  • 시맨틱 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> ); }

관련 컴포넌트

  • Container - 페이지 레이아웃 컨테이너
  • Grid - 그리드 레이아웃
  • Stack - 수직/수평 스택
  • Button - 액션 버튼
Last updated on