List
데이터를 리스트 형태로 표시하는 컴포넌트입니다.
개요
List는 여러 항목을 세로로 나열하여 표시하는 컴포넌트입니다. Cals List는 Cals 브랜드 스타일과 예약 상태 컬러가 적용되어 있으며, 고객 목록, 예약 히스토리, 서비스 목록 표시에 최적화되어 있습니다. Foundation List의 모든 기능을 상속합니다.
주요 특징:
- 다양한 List Item 스타일
- 예약 상태별 아이템 색상
- Cals 브랜드 컬러 적용 (Pink, Blue, Purple)
- 예약 상태 컬러 완벽 지원 (Available, Pending, Confirmed, Cancelled, Completed)
- 아이콘 및 Badge 지원
- 인터랙티브 액션
사용 사례:
- Cals 고객 목록
- 예약 히스토리 타임라인
- 서비스 카탈로그
- 알림 및 활동 로그
설치
npx @vortex/cli add list --package cals기본 사용법
import "@vortex/ui-cals/theme"; // Cals 테마 적용
import { List, ListItem } from "@vortex/ui-cals";
export default function Example() {
return (
<List>
<ListItem>항목 1</ListItem>
<ListItem>항목 2</ListItem>
<ListItem>항목 3</ListItem>
</List>
);
}Variants (변형)
Default
기본 리스트 스타일입니다.
<List>
<ListItem>고객 1</ListItem>
<ListItem>고객 2</ListItem>
<ListItem>고객 3</ListItem>
</List>With Icons
아이콘이 포함된 리스트입니다.
import { User, Calendar, Phone } from "lucide-react";
<List>
<ListItem>
<User size={16} className="mr-sm" />
홍길동
</ListItem>
<ListItem>
<Calendar size={16} className="mr-sm" />
2024-01-20 14:00
</ListItem>
<ListItem>
<Phone size={16} className="mr-sm" />
010-1234-5678
</ListItem>
</List>;With Status Colors
예약 상태 컬러가 적용된 리스트입니다.
import { Badge } from "@vortex/ui-cals";
<List>
<ListItem className="border-l-4 border-available">
<Badge variant="available">예약 가능</Badge>
<span>2024-01-20 10:00</span>
</ListItem>
<ListItem className="border-l-4 border-pending">
<Badge variant="pending">승인 대기</Badge>
<span>2024-01-20 14:00</span>
</ListItem>
<ListItem className="border-l-4 border-confirmed">
<Badge variant="confirmed">예약 확정</Badge>
<span>2024-01-20 16:00</span>
</ListItem>
</List>;Interactive List
클릭 가능한 인터랙티브 리스트입니다.
import { ChevronRight } from "lucide-react";
<List>
<ListItem
className="hover:bg-muted cursor-pointer"
onClick={() => console.log("clicked")}
>
<span>고객 상세보기</span>
<ChevronRight size={16} className="ml-auto" />
</ListItem>
<ListItem className="hover:bg-muted cursor-pointer">
<span>예약 내역 확인</span>
<ChevronRight size={16} className="ml-auto" />
</ListItem>
</List>;Cals 브랜딩
브랜드 컬러
Cals List는 다음 브랜드 컬러를 사용합니다:
- Primary (Pink):
#e91e63- 주요 항목 강조 - Secondary (Blue):
#03a9f4- 링크, 보조 항목 - Accent (Purple):
#9c27b0- 특별 상태 표시
예약 상태 컬러 ⭐
Cals는 예약 시스템에 특화된 5가지 상태 컬러를 제공합니다:
- Available (Green):
#4caf50- 예약 가능 - Pending (Orange):
#ff9800- 승인 대기 - Confirmed (Blue):
#03a9f4- 예약 확정 - Cancelled (Red):
#f44336- 예약 취소 - Completed (Purple):
#9c27b0- 서비스 완료
Cals 특화 사용 가이드
고객 목록 (예약 상태 포함):
import { List, ListItem } from '@vortex/ui-cals'
import { Badge, Button } from '@vortex/ui-cals'
import { User, Calendar, Phone, MapPin } from 'lucide-react'
const customers = [
{
name: '홍길동',
phone: '010-1234-5678',
lastReservation: '2024-01-18',
status: 'completed'
},
{
name: '김영희',
phone: '010-2345-6789',
lastReservation: '2024-01-20',
status: 'confirmed'
},
{
name: '이철수',
phone: '010-3456-7890',
lastReservation: '2024-01-15',
status: 'pending'
}
]
<List>
{customers.map((customer, index) => (
<ListItem
key={index}
className={`border-l-4 border-${customer.status} p-md hover:bg-${customer.status}/10 cursor-pointer`}
>
<div className="flex items-center justify-between w-full">
<div className="space-y-xs">
<div className="flex items-center gap-sm">
<User size={16} className="text-muted-foreground" />
<span className="font-medium">{customer.name}</span>
<Badge variant={customer.status} size="sm">
{customer.status === 'completed' && '완료'}
{customer.status === 'confirmed' && '확정'}
{customer.status === 'pending' && '대기'}
</Badge>
</div>
<div className="flex items-center gap-sm text-sm text-muted-foreground">
<Phone size={14} />
{customer.phone}
</div>
<div className="flex items-center gap-sm text-sm text-muted-foreground">
<Calendar size={14} />
최근 예약: {customer.lastReservation}
</div>
</div>
<Button variant="outline" size="sm">
상세보기
</Button>
</div>
</ListItem>
))}
</List>예약 히스토리 타임라인:
import { List, ListItem } from "@vortex/ui-cals";
import { Badge } from "@vortex/ui-cals";
import { Calendar, Clock, Check, X, AlertCircle } from "lucide-react";
const history = [
{
date: "2024-01-20",
time: "14:00",
service: "프리미엄 패키지",
status: "confirmed",
},
{
date: "2024-01-18",
time: "10:00",
service: "스탠다드 패키지",
status: "completed",
},
{
date: "2024-01-15",
time: "16:00",
service: "VIP 패키지",
status: "cancelled",
},
];
const getStatusIcon = (status) => {
switch (status) {
case "confirmed":
return <Clock size={16} className="text-confirmed" />;
case "completed":
return <Check size={16} className="text-completed" />;
case "cancelled":
return <X size={16} className="text-cancelled" />;
default:
return <AlertCircle size={16} />;
}
};
<List className="relative before:absolute before:left-6 before:top-0 before:bottom-0 before:w-0.5 before:bg-border">
{history.map((item, index) => (
<ListItem
key={index}
className="pl-xl relative before:absolute before:left-4 before:top-1/2 before:-translate-y-1/2 before:w-4 before:h-4 before:rounded-full before:bg-background before:border-2 before:border-current"
style={{ color: `var(--${item.status})` }}
>
<div className="space-y-xs">
<div className="flex items-center gap-sm">
{getStatusIcon(item.status)}
<Badge variant={item.status}>
{item.status === "confirmed" && "예약 확정"}
{item.status === "completed" && "완료"}
{item.status === "cancelled" && "취소"}
</Badge>
</div>
<div className="font-medium">{item.service}</div>
<div className="text-sm text-muted-foreground flex items-center gap-sm">
<Calendar size={14} />
{item.date} {item.time}
</div>
</div>
</ListItem>
))}
</List>;Foundation/iCignal/Cals 비교
| 속성 | Foundation | iCignal | Cals |
|---|---|---|---|
| 브랜드 컬러 | 중립 회색 | iCignal Blue | Cals Pink/Blue/Purple |
| 특화 기능 | 없음 | Analytics 항목 | 예약 상태 항목 |
| 사용 맥락 | 범용 | 데이터 분석 | 예약 관리 |
| 상태 컬러 | ❌ | ❌ | ✅ 5가지 예약 상태 |
| 타임라인 스타일 | ❌ | ❌ | ✅ 예약 히스토리 타임라인 |
| 테마 | 중립적 테마 | iCignal 테마 | Cals 테마 (@vortex/ui-cals/theme) |
import 경로 차이:
// Foundation
import { List } from "@vortex/ui-foundation";
// iCignal
import "@vortex/ui-icignal/theme";
import { List } from "@vortex/ui-icignal";
// Cals
import "@vortex/ui-cals/theme"; // 테마 import 필수
import { List } from "@vortex/ui-cals";Props API
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | 추가 CSS 클래스 |
| children | ReactNode | - | 리스트 아이템 |
접근성
List 컴포넌트는 WCAG 2.1 AA 기준을 준수합니다.
시맨틱 HTML:
<ul>,<ol>,<li>사용- 적절한 리스트 타입 선택
키보드 네비게이션:
Tab: 인터랙티브 항목 간 이동Enter/Space: 항목 선택
스크린 리더 지원:
- 리스트 항목 수 안내
- 상태 정보 명확하게 전달
색상 대비:
- 텍스트와 배경 4.5:1 이상
- 상태 컬러 대비 충족
예제
예제 1: 고객 목록 ⭐
import "@vortex/ui-cals/theme";
import { List, ListItem } from "@vortex/ui-cals";
import { Badge, Button } from "@vortex/ui-cals";
import { Card, CardHeader, CardContent } from "@vortex/ui-cals";
import { User, Phone, MapPin, Calendar, Mail } from "lucide-react";
export default function CustomerList() {
const customers = [
{
name: "홍길동",
phone: "010-1234-5678",
email: "hong@example.com",
address: "서울시 강남구",
lastVisit: "2024-01-18",
totalReservations: 12,
status: "completed",
},
{
name: "김영희",
phone: "010-2345-6789",
email: "kim@example.com",
address: "서울시 서초구",
lastVisit: "2024-01-20",
totalReservations: 8,
status: "confirmed",
},
{
name: "이철수",
phone: "010-3456-7890",
email: "lee@example.com",
address: "서울시 송파구",
lastVisit: "2024-01-15",
totalReservations: 5,
status: "pending",
},
];
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold">고객 목록</h2>
<Button variant="primary" size="sm">
<User size={16} className="mr-xs" />
신규 고객 추가
</Button>
</div>
</CardHeader>
<CardContent>
<List>
{customers.map((customer, index) => (
<ListItem
key={index}
className={`border-l-4 border-${customer.status} p-md hover:bg-${customer.status}/10 cursor-pointer transition-colors`}
>
<div className="flex items-start justify-between w-full">
<div className="space-y-sm flex-1">
<div className="flex items-center gap-sm">
<User size={20} className="text-muted-foreground" />
<span className="font-semibold text-lg">
{customer.name}
</span>
<Badge variant={customer.status} size="sm">
{customer.status === "completed" && "완료"}
{customer.status === "confirmed" && "확정"}
{customer.status === "pending" && "대기"}
</Badge>
</div>
<div className="grid grid-cols-2 gap-sm text-sm text-muted-foreground">
<div className="flex items-center gap-xs">
<Phone size={14} />
{customer.phone}
</div>
<div className="flex items-center gap-xs">
<Mail size={14} />
{customer.email}
</div>
<div className="flex items-center gap-xs">
<MapPin size={14} />
{customer.address}
</div>
<div className="flex items-center gap-xs">
<Calendar size={14} />
최근 방문: {customer.lastVisit}
</div>
</div>
<div className="flex items-center gap-md text-sm">
<span className="text-muted-foreground">
총 예약:{" "}
<strong className="text-foreground">
{customer.totalReservations}회
</strong>
</span>
</div>
</div>
<div className="flex gap-xs">
<Button variant="outline" size="sm">
상세보기
</Button>
<Button variant="ghost" size="sm">
예약 생성
</Button>
</div>
</div>
</ListItem>
))}
</List>
</CardContent>
</Card>
);
}예제 2: 예약 히스토리 타임라인 ⭐
import "@vortex/ui-cals/theme";
import { List, ListItem } from "@vortex/ui-cals";
import { Badge } from "@vortex/ui-cals";
import { Calendar, Clock, Check, X, Star } from "lucide-react";
export default function ReservationHistory() {
const history = [
{
date: "2024-01-20",
time: "14:00",
service: "프리미엄 패키지",
price: "150,000원",
status: "confirmed",
note: "예약 확정됨",
},
{
date: "2024-01-18",
time: "10:00",
service: "스탠다드 패키지",
price: "100,000원",
status: "completed",
rating: 5,
note: "서비스 완료 - 만족도 최상",
},
{
date: "2024-01-15",
time: "16:00",
service: "VIP 패키지",
price: "200,000원",
status: "cancelled",
note: "고객 요청으로 취소",
},
{
date: "2024-01-10",
time: "11:00",
service: "베이직 패키지",
price: "80,000원",
status: "completed",
rating: 4,
note: "서비스 완료",
},
];
const getStatusIcon = (status) => {
switch (status) {
case "confirmed":
return <Clock size={18} className="text-confirmed" />;
case "completed":
return <Check size={18} className="text-completed" />;
case "cancelled":
return <X size={18} className="text-cancelled" />;
default:
return null;
}
};
return (
<div className="space-y-md">
<h3 className="text-lg font-semibold">예약 이력</h3>
<List className="relative before:absolute before:left-8 before:top-0 before:bottom-0 before:w-0.5 before:bg-border">
{history.map((item, index) => (
<ListItem
key={index}
className={`pl-xl relative mb-md before:absolute before:left-6 before:top-6 before:w-5 before:h-5 before:rounded-full before:bg-${item.status} before:border-4 before:border-background`}
>
<div
className={`bg-${item.status}/10 border border-${item.status}/20 rounded-lg p-md space-y-sm`}
>
<div className="flex items-start justify-between">
<div className="flex items-center gap-sm">
{getStatusIcon(item.status)}
<Badge variant={item.status}>
{item.status === "confirmed" && "예약 확정"}
{item.status === "completed" && "서비스 완료"}
{item.status === "cancelled" && "예약 취소"}
</Badge>
</div>
<span className="text-sm text-muted-foreground">
{item.date}
</span>
</div>
<div>
<div className="font-semibold text-lg">{item.service}</div>
<div className="text-sm text-muted-foreground flex items-center gap-xs mt-xs">
<Clock size={14} />
{item.time}
</div>
</div>
<div className="flex items-center justify-between">
<span className="font-medium text-primary">{item.price}</span>
{item.rating && (
<div className="flex items-center gap-xs">
{[...Array(5)].map((_, i) => (
<Star
key={i}
size={14}
className={
i < item.rating
? "fill-yellow-400 text-yellow-400"
: "text-gray-300"
}
/>
))}
</div>
)}
</div>
{item.note && (
<div className="text-sm text-muted-foreground pt-sm border-t">
{item.note}
</div>
)}
</div>
</ListItem>
))}
</List>
</div>
);
}관련 컴포넌트
- Foundation List - 기본 버전 참조
- iCignal List - iCignal 버전 참조
- Badge - 상태 표시
- Button - 액션 버튼
- Card - 리스트 컨테이너
- Avatar - 프로필 이미지
Last updated on