Select
개요
Select는 사용자가 여러 옵션 중 하나를 선택할 수 있는 드롭다운 컴포넌트입니다. Cals 예약 관리 시스템에서는 서비스 선택, 시간대 선택, 담당자 선택 등 예약 프로세스의 핵심 기능으로 활용됩니다.
Cals 특화 기능:
- 예약 상태별 색상 시스템으로 선택 옵션 시각화
- 서비스 카테고리 및 메뉴 선택 최적화
- 시간대 및 담당자 선택을 위한 구조화된 옵션
- 예약 가능 여부에 따른 동적 옵션 활성화/비활성화
설치
npx @vortex/cli add select --package cals기본 사용법
import "@vortex/ui-cals/theme";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@vortex/ui-cals";
export default function ServiceSelect() {
return (
<Select>
<SelectTrigger>
<SelectValue placeholder="서비스를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="haircut">컷 (30분)</SelectItem>
<SelectItem value="perm">펌 (90분)</SelectItem>
<SelectItem value="coloring">염색 (120분)</SelectItem>
</SelectContent>
</Select>
);
}Variants
Default
기본 스타일의 Select입니다.
<Select>
<SelectTrigger>
<SelectValue placeholder="옵션 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">옵션 1</SelectItem>
<SelectItem value="option2">옵션 2</SelectItem>
</SelectContent>
</Select>Outlined
아웃라인 스타일의 Select입니다.
<Select>
<SelectTrigger className="border-2">
<SelectValue placeholder="옵션 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">옵션 1</SelectItem>
<SelectItem value="option2">옵션 2</SelectItem>
</SelectContent>
</Select>Sizes
Small
작은 크기의 Select입니다.
<Select>
<SelectTrigger className="h-8 text-sm">
<SelectValue placeholder="Small" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">옵션 1</SelectItem>
</SelectContent>
</Select>Medium (Default)
기본 크기의 Select입니다.
<Select>
<SelectTrigger>
<SelectValue placeholder="Medium" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">옵션 1</SelectItem>
</SelectContent>
</Select>Large
큰 크기의 Select입니다.
<Select>
<SelectTrigger className="h-12 text-lg">
<SelectValue placeholder="Large" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">옵션 1</SelectItem>
</SelectContent>
</Select>States
Disabled
비활성화된 Select입니다.
<Select disabled>
<SelectTrigger>
<SelectValue placeholder="비활성화됨" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">옵션 1</SelectItem>
</SelectContent>
</Select>With Label
레이블이 있는 Select입니다.
<div>
<label className="text-sm font-medium text-gray-700">서비스 선택</label>
<Select>
<SelectTrigger className="mt-1">
<SelectValue placeholder="서비스를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="service1">헤어컷</SelectItem>
</SelectContent>
</Select>
</div>🆕 Cals 브랜딩
브랜드 컬러
Cals Select는 기본적으로 Cals의 Primary 색상(Pink #e91e63)을 focus 및 선택 상태에서 사용합니다.
<div className="space-y-4">
{/* Primary (Pink) - 기본 focus */}
<Select>
<SelectTrigger className="focus:border-[#e91e63] focus:ring-[#e91e63]">
<SelectValue placeholder="서비스 선택 (Primary)" />
</SelectTrigger>
<SelectContent>
<SelectItem value="haircut">헤어컷</SelectItem>
<SelectItem value="perm">펌</SelectItem>
</SelectContent>
</Select>
{/* Secondary (Blue) - 정보성 선택 */}
<Select>
<SelectTrigger className="focus:border-[#03a9f4] focus:ring-[#03a9f4]">
<SelectValue placeholder="시간대 선택 (Secondary)" />
</SelectTrigger>
<SelectContent>
<SelectItem value="morning">오전 (09:00-12:00)</SelectItem>
<SelectItem value="afternoon">오후 (13:00-18:00)</SelectItem>
</SelectContent>
</Select>
{/* Accent (Purple) - 강조 선택 */}
<Select>
<SelectTrigger className="focus:border-[#9c27b0] focus:ring-[#9c27b0]">
<SelectValue placeholder="담당자 선택 (Accent)" />
</SelectTrigger>
<SelectContent>
<SelectItem value="designer1">김디자이너</SelectItem>
<SelectItem value="designer2">이디자이너</SelectItem>
</SelectContent>
</Select>
</div>예약 상태 컬러
Cals Select는 예약 관리의 5가지 상태를 옵션에 반영하여 시각적 피드백을 제공합니다.
<Select>
<SelectTrigger className="focus:border-[#e91e63] focus:ring-[#e91e63]">
<SelectValue placeholder="예약 상태별 시간대 선택" />
</SelectTrigger>
<SelectContent>
{/* Available - 예약 가능 */}
<SelectItem value="09:00" className="text-[#4caf50]">
09:00 - 예약 가능
</SelectItem>
{/* Pending - 승인 대기 */}
<SelectItem value="10:00" className="text-[#ff9800]" disabled>
10:00 - 승인 대기 중
</SelectItem>
{/* Confirmed - 예약 확정 */}
<SelectItem value="11:00" className="text-[#03a9f4]" disabled>
11:00 - 예약 확정 (홍길동)
</SelectItem>
{/* Cancelled - 취소됨 */}
<SelectItem value="14:00" className="text-[#f44336]" disabled>
14:00 - 취소됨
</SelectItem>
{/* Available */}
<SelectItem value="15:00" className="text-[#4caf50]">
15:00 - 예약 가능
</SelectItem>
{/* Completed - 서비스 완료 */}
<SelectItem value="16:00" className="text-[#9c27b0]" disabled>
16:00 - 서비스 완료
</SelectItem>
</SelectContent>
</Select>Cals 특화 사용 가이드
서비스 선택 (카테고리별 그룹화)
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">
서비스 선택 <span className="text-[#e91e63]">*</span>
</label>
<Select>
<SelectTrigger className="focus:border-[#e91e63] focus:ring-[#e91e63]">
<SelectValue placeholder="서비스를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>기본 서비스</SelectLabel>
<SelectItem value="haircut">컷 (30분) - ₩30,000</SelectItem>
<SelectItem value="shampoo">샴푸 (20분) - ₩10,000</SelectItem>
</SelectGroup>
<SelectGroup>
<SelectLabel>프리미엄 서비스</SelectLabel>
<SelectItem value="perm">펌 (90분) - ₩120,000</SelectItem>
<SelectItem value="coloring">염색 (120분) - ₩150,000</SelectItem>
<SelectItem value="clinic">클리닉 (60분) - ₩80,000</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>시간대 선택 (예약 가능 여부 표시)
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">
예약 시간 <span className="text-[#e91e63]">*</span>
</label>
<Select>
<SelectTrigger className="focus:border-[#03a9f4] focus:ring-[#03a9f4]">
<SelectValue placeholder="시간을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="09:00">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-[#4caf50]"></span>
09:00 - 예약 가능
</span>
</SelectItem>
<SelectItem value="10:00" disabled>
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-[#ff9800]"></span>
10:00 - 승인 대기
</span>
</SelectItem>
<SelectItem value="11:00" disabled>
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-[#03a9f4]"></span>
11:00 - 예약 확정
</span>
</SelectItem>
<SelectItem value="14:00">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-[#4caf50]"></span>
14:00 - 예약 가능
</span>
</SelectItem>
</SelectContent>
</Select>
</div>담당자 선택
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">담당자 선택</label>
<Select>
<SelectTrigger className="focus:border-[#9c27b0] focus:ring-[#9c27b0]">
<SelectValue placeholder="담당자를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="designer1">
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-full bg-[#e91e63] flex items-center justify-center text-white text-xs">
김
</div>
<span>김디자이너 (원장)</span>
</div>
</SelectItem>
<SelectItem value="designer2">
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-full bg-[#03a9f4] flex items-center justify-center text-white text-xs">
이
</div>
<span>이디자이너 (실장)</span>
</div>
</SelectItem>
<SelectItem value="designer3">
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-full bg-[#9c27b0] flex items-center justify-center text-white text-xs">
박
</div>
<span>박디자이너 (디자이너)</span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>🆕 Foundation/iCignal과의 차이점
| 속성 | Foundation | iCignal | Cals |
|---|---|---|---|
| Primary Color | #3B82F6 (Blue) | #2196f3 (Blue) | #e91e63 (Pink) |
| Secondary Color | #10B981 (Green) | #4caf50 (Green) | #03a9f4 (Blue) |
| Accent Color | #8B5CF6 (Purple) | #ff5722 (Deep Orange) | #9c27b0 (Purple) |
| 사용 맥락 | 범용 웹 애플리케이션 | Analytics 대시보드 | 예약 관리 시스템 |
| 예약 상태 | 없음 | 없음 | 5가지 상태 (Available/Pending/Confirmed/Cancelled/Completed) |
| 주요 선택 | 범용 옵션 선택 | 데이터 필터링 | 서비스, 시간대, 담당자 선택 |
| 옵션 그룹화 | 기본 그룹화 | 카테고리 필터 | 서비스 카테고리, 시간대별 예약 가능 여부 |
| 상태 표시 | 없음 | 없음 | 예약 가능/불가능 상태 시각화 |
사용 시나리오 비교
Foundation Select: 범용 옵션 선택, 국가/언어 선택, 설정 변경 iCignal Select: 데이터 필터, 날짜 범위 선택, 차트 타입 선택 Cals Select: 서비스 선택, 시간대 선택, 담당자 선택, 예약 상태 필터
Props API
Select
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | string | - | 기본 선택 값 |
value | string | - | 제어 컴포넌트의 선택 값 |
onValueChange | (value: string) => void | - | 값 변경 이벤트 핸들러 |
disabled | boolean | false | 비활성화 여부 |
name | string | - | Form name 속성 |
required | boolean | false | 필수 선택 여부 |
SelectTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | 추가 CSS 클래스 |
disabled | boolean | false | 비활성화 여부 |
SelectContent
| Prop | Type | Default | Description |
|---|---|---|---|
position | 'popper' | 'item-aligned' | 'popper' | 드롭다운 위치 |
className | string | - | 추가 CSS 클래스 |
SelectItem
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | 옵션 값 (필수) |
disabled | boolean | false | 비활성화 여부 |
className | string | - | 추가 CSS 클래스 |
접근성
Select 컴포넌트는 웹 접근성 표준을 준수합니다:
- Radix UI 기반: WAI-ARIA 1.2 Select 패턴 구현
- 키보드 네비게이션:
Space/Enter: 드롭다운 열기/닫기 및 옵션 선택Arrow Up/Down: 옵션 간 이동Home/End: 첫 번째/마지막 옵션으로 이동Esc: 드롭다운 닫기A-Z: 타이핑으로 옵션 검색
- 스크린 리더:
role="combobox"및aria-expanded지원 - Focus 관리: 명확한 focus indicator 및 focus trap
- Label 연결:
aria-labelledby로 label과 연결 - Required 표시:
aria-required="true"지원
접근성 예제
<div>
<label id="service-label" className="text-sm font-medium text-gray-700">
서비스 선택{" "}
<span className="text-[#e91e63]" aria-label="필수">
*
</span>
</label>
<Select required>
<SelectTrigger aria-labelledby="service-label" aria-required="true">
<SelectValue placeholder="서비스를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="haircut">컷 (30분)</SelectItem>
<SelectItem value="perm">펌 (90분)</SelectItem>
</SelectContent>
</Select>
</div>예제
서비스 선택 폼
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@vortex/ui-cals";
export default function ServiceSelectionForm() {
return (
<div className="space-y-4 max-w-md">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
서비스 선택 <span className="text-[#e91e63]">*</span>
</label>
<Select>
<SelectTrigger className="focus:border-[#e91e63] focus:ring-[#e91e63]">
<SelectValue placeholder="서비스를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>기본 서비스</SelectLabel>
<SelectItem value="haircut">컷 (30분) - ₩30,000</SelectItem>
<SelectItem value="shampoo">샴푸 (20분) - ₩10,000</SelectItem>
<SelectItem value="blowdry">드라이 (20분) - ₩15,000</SelectItem>
</SelectGroup>
<SelectGroup>
<SelectLabel>프리미엄 서비스</SelectLabel>
<SelectItem value="perm">펌 (90분) - ₩120,000</SelectItem>
<SelectItem value="coloring">염색 (120분) - ₩150,000</SelectItem>
<SelectItem value="clinic">클리닉 (60분) - ₩80,000</SelectItem>
<SelectItem value="treatment">
트리트먼트 (40분) - ₩50,000
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
담당자 선택
</label>
<Select>
<SelectTrigger className="focus:border-[#9c27b0] focus:ring-[#9c27b0]">
<SelectValue placeholder="담당자를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="any">지정 안함</SelectItem>
<SelectItem value="designer1">김디자이너 (원장)</SelectItem>
<SelectItem value="designer2">이디자이너 (실장)</SelectItem>
<SelectItem value="designer3">박디자이너 (디자이너)</SelectItem>
</SelectContent>
</Select>
</div>
<button className="w-full bg-[#e91e63] text-white py-2 px-4 rounded-md hover:bg-[#c2185b]">
다음 단계
</button>
</div>
);
}예약 시간 선택 (상태 표시)
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@vortex/ui-cals";
export default function TimeSlotSelector() {
const timeSlots = [
{ time: "09:00", status: "available", customer: null },
{ time: "10:00", status: "pending", customer: "김철수" },
{ time: "11:00", status: "confirmed", customer: "이영희" },
{ time: "12:00", status: "available", customer: null },
{ time: "14:00", status: "cancelled", customer: null },
{ time: "15:00", status: "available", customer: null },
{ time: "16:00", status: "completed", customer: "박민수" },
{ time: "17:00", status: "available", customer: null },
];
const statusConfig = {
available: { color: "#4caf50", label: "예약 가능", disabled: false },
pending: { color: "#ff9800", label: "승인 대기", disabled: true },
confirmed: { color: "#03a9f4", label: "예약 확정", disabled: true },
cancelled: { color: "#f44336", label: "취소됨", disabled: false },
completed: { color: "#9c27b0", label: "완료됨", disabled: true },
};
return (
<div className="max-w-md">
<label className="block text-sm font-medium text-gray-700 mb-2">
예약 시간 선택 <span className="text-[#e91e63]">*</span>
</label>
<Select>
<SelectTrigger className="focus:border-[#03a9f4] focus:ring-[#03a9f4]">
<SelectValue placeholder="시간을 선택하세요" />
</SelectTrigger>
<SelectContent>
{timeSlots.map((slot) => {
const config = statusConfig[slot.status];
return (
<SelectItem
key={slot.time}
value={slot.time}
disabled={config.disabled}
className="flex items-center gap-2"
>
<div className="flex items-center gap-2">
<span
className="w-2 h-2 rounded-full"
style={{ backgroundColor: config.color }}
/>
<span className="font-medium">{slot.time}</span>
<span className="text-sm" style={{ color: config.color }}>
{config.label}
</span>
{slot.customer && (
<span className="text-sm text-gray-500">
({slot.customer})
</span>
)}
</div>
</SelectItem>
);
})}
</SelectContent>
</Select>
<div className="mt-3 flex gap-2 text-xs">
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-[#4caf50]" />
<span>예약 가능</span>
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-[#ff9800]" />
<span>승인 대기</span>
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-[#03a9f4]" />
<span>예약 확정</span>
</div>
</div>
</div>
);
}예약 필터 (다중 Select)
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@vortex/ui-cals";
export default function ReservationFilters() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
예약 상태
</label>
<Select>
<SelectTrigger className="focus:border-[#e91e63] focus:ring-[#e91e63]">
<SelectValue placeholder="전체" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">전체</SelectItem>
<SelectItem value="available" className="text-[#4caf50]">
예약 가능
</SelectItem>
<SelectItem value="pending" className="text-[#ff9800]">
승인 대기
</SelectItem>
<SelectItem value="confirmed" className="text-[#03a9f4]">
예약 확정
</SelectItem>
<SelectItem value="cancelled" className="text-[#f44336]">
예약 취소
</SelectItem>
<SelectItem value="completed" className="text-[#9c27b0]">
서비스 완료
</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
서비스 타입
</label>
<Select>
<SelectTrigger className="focus:border-[#03a9f4] focus:ring-[#03a9f4]">
<SelectValue placeholder="전체" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">전체</SelectItem>
<SelectItem value="basic">기본 서비스</SelectItem>
<SelectItem value="premium">프리미엄 서비스</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
담당자
</label>
<Select>
<SelectTrigger className="focus:border-[#9c27b0] focus:ring-[#9c27b0]">
<SelectValue placeholder="전체" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">전체</SelectItem>
<SelectItem value="designer1">김디자이너</SelectItem>
<SelectItem value="designer2">이디자이너</SelectItem>
<SelectItem value="designer3">박디자이너</SelectItem>
</SelectContent>
</Select>
</div>
</div>
);
}관련 컴포넌트
Last updated on