Skip to Content

Switch

개요

Switch는 사용자가 설정을 On/Off로 토글할 수 있는 폼 컴포넌트입니다. Cals 예약 관리 시스템에서는 알림 설정, 자동 승인 설정, 예약 공개/비공개 전환 등 즉시 적용되는 설정에서 활용됩니다.

Cals 특화 기능:

  • 예약 상태별 색상 시스템으로 토글 상태 시각화
  • 알림 및 자동 승인 설정 최적화
  • 예약 공개/비공개 즉시 전환
  • 관리자 설정을 위한 다양한 토글 옵션

설치

npx @vortex/cli add switch --package cals

기본 사용법

import "@vortex/ui-cals/theme"; import { Switch } from "@vortex/ui-cals"; export default function NotificationSettings() { return ( <div className="flex items-center space-x-2"> <Switch id="sms-notification" /> <label htmlFor="sms-notification" className="text-sm font-medium"> SMS 알림 </label> </div> ); }

Variants

Default

기본 스타일의 Switch입니다.

<Switch />

With Label

레이블과 함께 사용하는 Switch입니다.

<div className="flex items-center space-x-2"> <Switch id="option1" /> <label htmlFor="option1" className="text-sm font-medium"> 레이블이 있는 스위치 </label> </div>

With Description

설명과 함께 사용하는 Switch입니다.

<div className="flex items-center justify-between max-w-sm"> <div> <label htmlFor="option2" className="text-sm font-medium"> 알림 설정 </label> <p className="text-xs text-gray-500">예약 관련 알림을 받습니다</p> </div> <Switch id="option2" /> </div>

Sizes

Small

작은 크기의 Switch입니다.

<Switch className="h-5 w-9" />

Medium (Default)

기본 크기의 Switch입니다.

<Switch className="h-6 w-11" />

Large

큰 크기의 Switch입니다.

<Switch className="h-7 w-14" />

States

Default (Off)

기본 상태의 Switch입니다.

<Switch />

Checked (On)

활성화된 Switch입니다.

<Switch checked />

Disabled

비활성화된 Switch입니다.

<Switch disabled />

Disabled Checked

비활성화되고 활성화된 Switch입니다.

<Switch checked disabled />

🆕 Cals 브랜딩

브랜드 컬러

Cals Switch는 기본적으로 Cals의 Primary 색상(Pink #e91e63)을 활성화 상태에서 사용합니다.

<div className="space-y-4"> {/* Primary (Pink) - 기본 활성화 */} <div className="flex items-center space-x-2"> <Switch id="primary" checked className="data-[state=checked]:bg-[#e91e63]" /> <label htmlFor="primary">Primary (Pink) - 기본 알림</label> </div> {/* Secondary (Blue) - 정보성 설정 */} <div className="flex items-center space-x-2"> <Switch id="secondary" checked className="data-[state=checked]:bg-[#03a9f4]" /> <label htmlFor="secondary">Secondary (Blue) - 이메일 알림</label> </div> {/* Accent (Purple) - 강조 설정 */} <div className="flex items-center space-x-2"> <Switch id="accent" checked className="data-[state=checked]:bg-[#9c27b0]" /> <label htmlFor="accent">Accent (Purple) - 프리미엄 기능</label> </div> </div>

예약 상태 컬러

Cals Switch는 예약 관리의 5가지 상태를 반영하여 설정의 시각적 피드백을 제공합니다.

<div className="space-y-3"> {/* Available - 활성화 가능 */} <div className="flex items-center justify-between max-w-sm"> <div> <label htmlFor="available" className="text-sm font-medium"> 예약 접수 활성화 </label> <p className="text-xs text-gray-500">새로운 예약을 받을 수 있습니다</p> </div> <Switch id="available" checked className="data-[state=checked]:bg-[#4caf50]" /> </div> {/* Pending - 대기 중 */} <div className="flex items-center justify-between max-w-sm"> <div> <label htmlFor="pending" className="text-sm font-medium text-gray-500"> 수동 승인 모드 </label> <p className="text-xs text-gray-500">예약 승인 대기 중</p> </div> <Switch id="pending" checked disabled className="data-[state=checked]:bg-[#ff9800]" /> </div> {/* Confirmed - 자동 승인 */} <div className="flex items-center justify-between max-w-sm"> <div> <label htmlFor="confirmed" className="text-sm font-medium"> 자동 승인 </label> <p className="text-xs text-gray-500">예약이 즉시 확정됩니다</p> </div> <Switch id="confirmed" checked className="data-[state=checked]:bg-[#03a9f4]" /> </div> {/* Cancelled - 비활성화 */} <div className="flex items-center justify-between max-w-sm"> <div> <label htmlFor="cancelled" className="text-sm font-medium text-gray-500"> 예약 접수 중단 </label> <p className="text-xs text-gray-500">예약을 받지 않습니다</p> </div> <Switch id="cancelled" className="data-[state=checked]:bg-[#f44336]" /> </div> {/* Completed - 완료 모드 */} <div className="flex items-center justify-between max-w-sm"> <div> <label htmlFor="completed" className="text-sm font-medium"> 완료 후 피드백 요청 </label> <p className="text-xs text-gray-500">서비스 완료 시 리뷰 요청</p> </div> <Switch id="completed" checked className="data-[state=checked]:bg-[#9c27b0]" /> </div> </div>

Cals 특화 사용 가이드

알림 설정

<div className="space-y-4 max-w-md"> <h3 className="text-sm font-semibold text-gray-900">알림 설정</h3> <div className="space-y-3"> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="sms" className="text-sm font-medium cursor-pointer"> SMS 알림 </label> <p className="text-xs text-gray-500 mt-0.5"> 예약 확정, 변경, 당일 알림을 문자로 받습니다 </p> </div> <Switch id="sms" defaultChecked className="data-[state=checked]:bg-[#03a9f4]" /> </div> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="email" className="text-sm font-medium cursor-pointer"> 이메일 알림 </label> <p className="text-xs text-gray-500 mt-0.5"> 예약 내역 및 영수증을 이메일로 받습니다 </p> </div> <Switch id="email" defaultChecked className="data-[state=checked]:bg-[#03a9f4]" /> </div> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="push" className="text-sm font-medium cursor-pointer"> 앱 푸시 알림 </label> <p className="text-xs text-gray-500 mt-0.5"> 모바일 앱에서 실시간 알림을 받습니다 </p> </div> <Switch id="push" className="data-[state=checked]:bg-[#03a9f4]" /> </div> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="marketing" className="text-sm font-medium cursor-pointer" > 마케팅 알림 </label> <p className="text-xs text-gray-500 mt-0.5"> 이벤트, 프로모션, 쿠폰 정보를 받습니다 </p> </div> <Switch id="marketing" className="data-[state=checked]:bg-[#9c27b0]" /> </div> </div> </div>

자동 승인 설정 (관리자)

<div className="space-y-4 max-w-md"> <h3 className="text-sm font-semibold text-gray-900">예약 관리 설정</h3> <div className="space-y-3"> <div className="flex items-center justify-between p-4 border-2 border-gray-200 rounded-lg"> <div className="flex-1"> <label htmlFor="auto-approve" className="text-sm font-medium cursor-pointer" > 자동 승인 </label> <p className="text-xs text-gray-500 mt-1"> 새로운 예약을 자동으로 승인합니다 </p> <div className="flex items-center gap-2 mt-2"> <span className="text-xs bg-[#4caf50] text-white px-2 py-0.5 rounded"> 빠른 처리 </span> <span className="text-xs text-gray-500">즉시 확정</span> </div> </div> <Switch id="auto-approve" defaultChecked className="data-[state=checked]:bg-[#03a9f4]" /> </div> <div className="flex items-center justify-between p-4 border-2 border-gray-200 rounded-lg"> <div className="flex-1"> <label htmlFor="waiting-list" className="text-sm font-medium cursor-pointer" > 대기 명단 활성화 </label> <p className="text-xs text-gray-500 mt-1"> 예약 취소 시 대기자에게 자동 안내 </p> </div> <Switch id="waiting-list" className="data-[state=checked]:bg-[#ff9800]" /> </div> <div className="flex items-center justify-between p-4 border-2 border-gray-200 rounded-lg"> <div className="flex-1"> <label htmlFor="double-booking" className="text-sm font-medium cursor-pointer" > 중복 예약 방지 </label> <p className="text-xs text-gray-500 mt-1"> 동일 시간대 중복 예약을 차단합니다 </p> <div className="flex items-center gap-2 mt-2"> <span className="text-xs bg-[#e91e63] text-white px-2 py-0.5 rounded"> 권장 </span> </div> </div> <Switch id="double-booking" defaultChecked className="data-[state=checked]:bg-[#e91e63]" /> </div> </div> </div>

공개/비공개 설정

<div className="space-y-4 max-w-md"> <h3 className="text-sm font-semibold text-gray-900">공개 설정</h3> <div className="space-y-3"> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="profile-public" className="text-sm font-medium cursor-pointer" > 프로필 공개 </label> <p className="text-xs text-gray-500 mt-0.5"> 다른 사용자가 프로필을 볼 수 있습니다 </p> </div> <Switch id="profile-public" defaultChecked className="data-[state=checked]:bg-[#e91e63]" /> </div> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="schedule-public" className="text-sm font-medium cursor-pointer" > 스케줄 공개 </label> <p className="text-xs text-gray-500 mt-0.5"> 예약 가능 시간을 공개합니다 </p> </div> <Switch id="schedule-public" defaultChecked className="data-[state=checked]:bg-[#4caf50]" /> </div> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="reviews-public" className="text-sm font-medium cursor-pointer" > 리뷰 공개 </label> <p className="text-xs text-gray-500 mt-0.5">고객 리뷰를 공개합니다</p> </div> <Switch id="reviews-public" defaultChecked className="data-[state=checked]:bg-[#9c27b0]" /> </div> </div> </div>

🆕 Foundation/iCignal과의 차이점

속성FoundationiCignalCals
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)
주요 사용범용 토글 설정필터 활성화/비활성화알림 설정, 자동 승인, 공개/비공개
활성화 스타일Blue backgroundBlue backgroundPink/Blue/Purple background (맥락별)
설정 그룹기본 그룹화분석 설정알림, 예약 관리, 공개 설정

사용 시나리오 비교

Foundation Switch: 범용 토글 설정, 기능 활성화/비활성화, 다크 모드 iCignal Switch: 데이터 필터 활성화, 실시간 업데이트, 차트 표시 옵션 Cals Switch: 알림 설정, 자동 승인, 공개/비공개, 예약 접수 활성화

Props API

PropTypeDefaultDescription
checkedbooleanfalse체크 상태 (제어 컴포넌트)
defaultCheckedbooleanfalse기본 체크 상태 (비제어 컴포넌트)
onCheckedChange(checked: boolean) => void-상태 변경 이벤트 핸들러
disabledbooleanfalse비활성화 여부
requiredbooleanfalse필수 여부
namestring-Form name 속성
valuestring'on'Form value 속성
idstring-HTML id 속성 (label 연결용)
classNamestring-추가 CSS 클래스

접근성

Switch 컴포넌트는 웹 접근성 표준을 준수합니다:

  • Radix UI 기반: WAI-ARIA 1.2 Switch 패턴 구현
  • 시맨틱 마크업: role="switch" 사용
  • 키보드 네비게이션:
    • Space: 스위치 토글
    • Tab: 다음 스위치로 이동
    • Shift + Tab: 이전 스위치로 이동
  • 스크린 리더: aria-checked 상태 안내
  • Label 연결: htmlForid를 통한 label-switch 연결
  • Focus 표시: 명확한 focus ring으로 현재 위치 표시
  • 상태 피드백: 시각적, 청각적 피드백 제공

접근성 예제

<div className="flex items-center justify-between"> <div> <label htmlFor="notifications" className="text-sm font-medium cursor-pointer" > 알림 받기 </label> <p id="notifications-description" className="text-xs text-gray-500"> 예약 관련 알림을 받습니다 </p> </div> <Switch id="notifications" aria-describedby="notifications-description" /> </div>

예제

알림 설정 폼

import { Switch } from "@vortex/ui-cals"; import { useState } from "react"; export default function NotificationSettings() { const [settings, setSettings] = useState({ sms: true, email: true, push: false, marketing: false, }); const notificationTypes = [ { id: "sms", label: "SMS 알림", description: "예약 확정, 변경, 당일 알림을 문자로 받습니다", color: "data-[state=checked]:bg-[#03a9f4]", category: "필수", }, { id: "email", label: "이메일 알림", description: "예약 내역 및 영수증을 이메일로 받습니다", color: "data-[state=checked]:bg-[#03a9f4]", category: "필수", }, { id: "push", label: "앱 푸시 알림", description: "모바일 앱에서 실시간 알림을 받습니다", color: "data-[state=checked]:bg-[#03a9f4]", category: "선택", }, { id: "marketing", label: "마케팅 알림", description: "이벤트, 프로모션, 쿠폰 정보를 받습니다", color: "data-[state=checked]:bg-[#9c27b0]", category: "선택", }, ]; return ( <div className="space-y-4 max-w-md"> <div className="flex items-center justify-between"> <h3 className="text-base font-semibold text-gray-900">알림 설정</h3> <button onClick={() => setSettings({ sms: false, email: false, push: false, marketing: false, }) } className="text-xs text-[#e91e63] hover:underline" > 모두 끄기 </button> </div> <div className="space-y-2"> {notificationTypes.map((type) => ( <div key={type.id} className={` p-3 border rounded-md transition-colors ${ settings[type.id as keyof typeof settings] ? "border-[#e91e63]/30 bg-pink-50/30" : "border-gray-200" } `} > <div className="flex items-start justify-between"> <div className="flex-1"> <div className="flex items-center gap-2"> <label htmlFor={type.id} className="text-sm font-medium cursor-pointer" > {type.label} </label> <span className={` text-xs px-2 py-0.5 rounded-full ${ type.category === "필수" ? "bg-[#e91e63] text-white" : "bg-gray-200 text-gray-600" } `} > {type.category} </span> </div> <p className="text-xs text-gray-500 mt-1">{type.description}</p> </div> <Switch id={type.id} checked={settings[type.id as keyof typeof settings]} onCheckedChange={(checked) => setSettings({ ...settings, [type.id]: checked }) } className={type.color} /> </div> </div> ))} </div> <div className="p-3 bg-blue-50 rounded-md border border-blue-200"> <p className="text-xs text-blue-800"> 💡 알림 설정은 언제든지 변경할 수 있습니다 </p> </div> </div> ); }

예약 관리 설정 (관리자)

import { Switch } from "@vortex/ui-cals"; import { useState } from "react"; import { CheckCircle, Clock, Users, Ban } from "lucide-react"; export default function ReservationManagementSettings() { const [settings, setSettings] = useState({ autoApprove: true, waitingList: false, doubleBookingPrevention: true, reminderNotification: true, }); const managementSettings = [ { id: "autoApprove", label: "자동 승인", description: "새로운 예약을 자동으로 승인합니다", badge: "빠른 처리", badgeColor: "bg-[#4caf50]", icon: CheckCircle, color: "data-[state=checked]:bg-[#03a9f4]", }, { id: "waitingList", label: "대기 명단 활성화", description: "예약 취소 시 대기자에게 자동 안내", badge: null, icon: Users, color: "data-[state=checked]:bg-[#ff9800]", }, { id: "doubleBookingPrevention", label: "중복 예약 방지", description: "동일 시간대 중복 예약을 차단합니다", badge: "권장", badgeColor: "bg-[#e91e63]", icon: Ban, color: "data-[state=checked]:bg-[#e91e63]", }, { id: "reminderNotification", label: "리마인더 알림", description: "예약 1시간 전 자동 알림 전송", badge: null, icon: Clock, color: "data-[state=checked]:bg-[#9c27b0]", }, ]; return ( <div className="space-y-4 max-w-md"> <h3 className="text-base font-semibold text-gray-900">예약 관리 설정</h3> <div className="space-y-3"> {managementSettings.map((setting) => { const Icon = setting.icon; return ( <div key={setting.id} className={` p-4 border-2 rounded-lg transition-all ${ settings[setting.id as keyof typeof settings] ? "border-[#e91e63] bg-pink-50" : "border-gray-200" } `} > <div className="flex items-start justify-between"> <div className="flex gap-3 flex-1"> <Icon className={` h-5 w-5 mt-0.5 ${ settings[setting.id as keyof typeof settings] ? "text-[#e91e63]" : "text-gray-400" } `} /> <div className="flex-1"> <div className="flex items-center gap-2"> <label htmlFor={setting.id} className="text-sm font-medium cursor-pointer" > {setting.label} </label> {setting.badge && ( <span className={`text-xs text-white px-2 py-0.5 rounded ${setting.badgeColor}`} > {setting.badge} </span> )} </div> <p className="text-xs text-gray-500 mt-1"> {setting.description} </p> </div> </div> <Switch id={setting.id} checked={settings[setting.id as keyof typeof settings]} onCheckedChange={(checked) => setSettings({ ...settings, [setting.id]: checked }) } className={setting.color} /> </div> </div> ); })} </div> <div className="p-4 bg-gray-50 rounded-lg"> <p className="text-sm font-medium text-gray-900 mb-2">현재 설정 요약</p> <ul className="text-xs text-gray-600 space-y-1"> <li> • 자동 승인:{" "} <span className={ settings.autoApprove ? "text-[#4caf50]" : "text-gray-400" } > {settings.autoApprove ? "활성화" : "비활성화"} </span> </li> <li> • 대기 명단:{" "} <span className={ settings.waitingList ? "text-[#ff9800]" : "text-gray-400" } > {settings.waitingList ? "활성화" : "비활성화"} </span> </li> <li> • 중복 예약 방지:{" "} <span className={ settings.doubleBookingPrevention ? "text-[#e91e63]" : "text-gray-400" } > {settings.doubleBookingPrevention ? "활성화" : "비활성화"} </span> </li> <li> • 리마인더:{" "} <span className={ settings.reminderNotification ? "text-[#9c27b0]" : "text-gray-400" } > {settings.reminderNotification ? "활성화" : "비활성화"} </span> </li> </ul> </div> </div> ); }

공개/비공개 설정

import { Switch } from "@vortex/ui-cals"; import { useState } from "react"; import { Eye, EyeOff } from "lucide-react"; export default function PrivacySettings() { const [privacy, setPrivacy] = useState({ profile: true, schedule: true, reviews: true, contact: false, }); const allPublic = Object.values(privacy).every((value) => value); return ( <div className="space-y-4 max-w-md"> <div className="flex items-center justify-between"> <h3 className="text-base font-semibold text-gray-900">공개 설정</h3> <div className="flex items-center gap-2"> {allPublic ? ( <Eye className="h-4 w-4 text-[#4caf50]" /> ) : ( <EyeOff className="h-4 w-4 text-gray-400" /> )} <span className="text-xs text-gray-600"> {allPublic ? "전체 공개" : "일부 비공개"} </span> </div> </div> <div className="space-y-2"> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="profile" className="text-sm font-medium cursor-pointer" > 프로필 공개 </label> <p className="text-xs text-gray-500 mt-0.5"> 다른 사용자가 프로필을 볼 수 있습니다 </p> </div> <Switch id="profile" checked={privacy.profile} onCheckedChange={(checked) => setPrivacy({ ...privacy, profile: checked }) } className="data-[state=checked]:bg-[#e91e63]" /> </div> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="schedule" className="text-sm font-medium cursor-pointer" > 스케줄 공개 </label> <p className="text-xs text-gray-500 mt-0.5"> 예약 가능 시간을 공개합니다 </p> </div> <Switch id="schedule" checked={privacy.schedule} onCheckedChange={(checked) => setPrivacy({ ...privacy, schedule: checked }) } className="data-[state=checked]:bg-[#4caf50]" /> </div> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="reviews" className="text-sm font-medium cursor-pointer" > 리뷰 공개 </label> <p className="text-xs text-gray-500 mt-0.5"> 고객 리뷰를 공개합니다 </p> </div> <Switch id="reviews" checked={privacy.reviews} onCheckedChange={(checked) => setPrivacy({ ...privacy, reviews: checked }) } className="data-[state=checked]:bg-[#9c27b0]" /> </div> <div className="flex items-center justify-between p-3 border border-gray-200 rounded-md"> <div className="flex-1"> <label htmlFor="contact" className="text-sm font-medium cursor-pointer" > 연락처 공개 </label> <p className="text-xs text-gray-500 mt-0.5"> 연락처를 다른 사용자에게 공개합니다 </p> </div> <Switch id="contact" checked={privacy.contact} onCheckedChange={(checked) => setPrivacy({ ...privacy, contact: checked }) } className="data-[state=checked]:bg-[#03a9f4]" /> </div> </div> <div className="p-3 bg-yellow-50 rounded-md border border-yellow-200"> <p className="text-xs text-yellow-800"> ⚠️ 프로필을 비공개로 설정하면 다른 사용자가 예약을 신청할 수 없습니다 </p> </div> </div> ); }

관련 컴포넌트

  • Input - 텍스트 입력 필드
  • Select - 드롭다운 선택 입력
  • Checkbox - 다중 선택 입력
  • Radio - 단일 선택 입력
  • Button - 폼 제출 버튼
Last updated on