Skip to Content

Popover

개요

Popover는 트리거 요소 근처에 추가 정보를 표시하는 컴포넌트입니다. Cals에서는 예약 상세 정보 미리보기, 시간대 정보, 서비스 설명 등을 표시하는 데 사용됩니다.

언제 사용하는가

  • 예약 상세 정보 미리보기
  • 시간대별 예약 정보
  • 서비스 설명 및 가격 정보
  • 고객 정보 요약
  • 빠른 액션 메뉴

언제 사용하지 말아야 하는가

  • 중요한 정보 → Dialog 사용
  • 짧은 설명 → Tooltip 사용
  • 영구적인 정보 → Alert 또는 Card 사용
  • 복잡한 폼 → Dialog 사용

설치

npx @vortex/cli add popover

기본 사용법

import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; export default function PopoverDemo() { return ( <Popover> <PopoverTrigger asChild> <Button variant="outline">예약 정보</Button> </PopoverTrigger> <PopoverContent> <div className="space-y-2"> <h4 className="font-semibold">예약 상세</h4> <p className="text-sm text-muted-foreground">2024년 1월 15일 14:00</p> </div> </PopoverContent> </Popover> ); }

Variants

Popover는 단일 스타일이지만, 용도에 따라 내용을 다르게 구성합니다.

Information Popover

정보를 표시하는 Popover입니다.

<Popover> <PopoverTrigger asChild> <Button variant="ghost" size="icon"> <Info className="h-4 w-4" /> </Button> </PopoverTrigger> <PopoverContent className="w-80"> <div className="space-y-2"> <h4 className="font-semibold">예약 안내</h4> <p className="text-sm text-muted-foreground"> 예약은 최소 24시간 전에 해주시기 바랍니다. 당일 예약은 전화로 문의해주세요. </p> </div> </PopoverContent> </Popover>

Action Popover

액션 버튼을 포함하는 Popover입니다.

<Popover> <PopoverTrigger asChild> <Button variant="outline">빠른 액션</Button> </PopoverTrigger> <PopoverContent className="w-48 p-2"> <div className="space-y-1"> <Button variant="ghost" size="sm" className="w-full justify-start"> 예약 수정 </Button> <Button variant="ghost" size="sm" className="w-full justify-start"> 예약 취소 </Button> <Button variant="ghost" size="sm" className="w-full justify-start"> 상세 보기 </Button> </div> </PopoverContent> </Popover>

Form Popover

간단한 폼을 포함하는 Popover입니다.

<Popover> <PopoverTrigger asChild> <Button variant="outline">메모 추가</Button> </PopoverTrigger> <PopoverContent className="w-80"> <div className="space-y-4"> <div className="space-y-2"> <h4 className="font-semibold">예약 메모</h4> <p className="text-sm text-muted-foreground"> 이 예약에 대한 메모를 추가하세요. </p> </div> <div className="space-y-2"> <Textarea placeholder="메모를 입력하세요" /> </div> <div className="flex justify-end gap-2"> <Button variant="outline" size="sm"> 취소 </Button> <Button size="sm">저장</Button> </div> </div> </PopoverContent> </Popover>

Cals 브랜딩

브랜드 컬러

Cals의 Primary Pink를 활용한 브랜드 Popover 스타일입니다.

<Popover> <PopoverTrigger asChild> <Button className="bg-cals-primary hover:bg-cals-primary/90"> Cals 정보 </Button> </PopoverTrigger> <PopoverContent className="w-80 border-cals-primary"> <div className="space-y-2"> <h4 className="font-semibold text-cals-primary">Cals 예약 시스템</h4> <p className="text-sm text-muted-foreground"> 간편하고 효율적인 예약 관리 시스템 </p> </div> </PopoverContent> </Popover>

예약 상태 컬러

예약 상태별 Popover 스타일을 제공합니다.

// Available - 예약 가능 <Popover> <PopoverTrigger asChild> <Button variant="outline" className="border-green-500 text-green-700"> 예약 가능 </Button> </PopoverTrigger> <PopoverContent className="w-80 border-green-500"> <div className="space-y-2"> <h4 className="font-semibold text-green-900">예약 가능</h4> <p className="text-sm text-muted-foreground"> 해당 시간대에 예약하실 수 있습니다. </p> <Button size="sm" className="bg-green-500 hover:bg-green-600 w-full"> 예약하기 </Button> </div> </PopoverContent> </Popover> // Pending - 승인 대기 <Popover> <PopoverTrigger asChild> <Button variant="outline" className="border-orange-500 text-orange-700"> 승인 대기 </Button> </PopoverTrigger> <PopoverContent className="w-80 border-orange-500"> <div className="space-y-2"> <h4 className="font-semibold text-orange-900">승인 대기</h4> <p className="text-sm text-muted-foreground"> 예약 요청이 접수되었습니다. 승인까지 최대 24시간 소요됩니다. </p> <div className="flex gap-2"> <Button size="sm" variant="outline" className="flex-1"> 거절 </Button> <Button size="sm" className="bg-orange-500 hover:bg-orange-600 flex-1"> 승인 </Button> </div> </div> </PopoverContent> </Popover> // Confirmed - 예약 확정 <Popover> <PopoverTrigger asChild> <Button variant="outline" className="border-blue-500 text-blue-700"> 예약 확정 </Button> </PopoverTrigger> <PopoverContent className="w-80 border-blue-500"> <div className="space-y-3"> <h4 className="font-semibold text-blue-900">예약 확정</h4> <div className="space-y-1"> <p className="text-sm font-medium">날짜</p> <p className="text-sm text-muted-foreground">2024년 1월 15일 14:00</p> </div> <div className="space-y-1"> <p className="text-sm font-medium">고객명</p> <p className="text-sm text-muted-foreground">홍길동</p> </div> <div className="space-y-1"> <p className="text-sm font-medium">서비스</p> <p className="text-sm text-muted-foreground">컨설팅 (60분)</p> </div> <Button size="sm" variant="outline" className="w-full"> 상세 보기 </Button> </div> </PopoverContent> </Popover> // Cancelled - 예약 취소 <Popover> <PopoverTrigger asChild> <Button variant="outline" className="border-red-500 text-red-700"> 예약 취소 </Button> </PopoverTrigger> <PopoverContent className="w-80 border-red-500"> <div className="space-y-2"> <h4 className="font-semibold text-red-900">예약 취소</h4> <p className="text-sm text-muted-foreground"> 이 예약은 취소되었습니다. 취소 수수료 5,000원이 부과됩니다. </p> <div className="pt-2 border-t"> <p className="text-xs text-muted-foreground"> 취소일: 2024년 1월 10일 10:30 </p> </div> </div> </PopoverContent> </Popover>

브랜드 비교표

속성FoundationiCignalCals
Primary ColorNeutral GrayCorporate Blue #0066ccPrimary Pink #e91e63
Use Case범용 정보기업 데이터예약 상세 정보
TriggerButton/IconIcon (통일)Status Badge/Button
ContentSimpleDetailed (차트)Reservation Details
WidthDefault (256px)Wide (400px)Medium (320px)
ActionsOptionalRequiredQuick Actions

Props API

Popover

PropTypeDefaultDescription
openboolean-Popover 열림 상태 (controlled)
onOpenChange(open: boolean) => void-열림 상태 변경 핸들러
defaultOpenbooleanfalse초기 열림 상태 (uncontrolled)
modalbooleanfalse모달 동작 활성화

PopoverTrigger

PropTypeDefaultDescription
asChildbooleanfalse자식 요소를 트리거로 사용

PopoverContent

PropTypeDefaultDescription
align"start" | "center" | "end""center"정렬 위치
side"top" | "right" | "bottom" | "left""bottom"표시 위치
sideOffsetnumber4트리거와의 간격 (px)
classNamestring-커스텀 CSS 클래스

접근성

  • Role: role="dialog" 자동 적용
  • ARIA: aria-labelledby, aria-describedby 연결
  • Focus Management: 열릴 때 내부로 포커스 이동
  • ESC Key: ESC 키로 닫기
  • Outside Click: 외부 클릭으로 닫기
  • Keyboard Navigation: Tab으로 내부 요소 이동
  • Return Focus: 닫힐 때 트리거로 포커스 복귀

예제

예약 상세 정보

import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Calendar, Clock, User, Phone } from "lucide-react"; export default function ReservationDetailPopover() { return ( <Popover> <PopoverTrigger asChild> <Button variant="outline" className="border-blue-500 text-blue-700"> 예약 #1234 </Button> </PopoverTrigger> <PopoverContent className="w-96 border-blue-500"> <div className="space-y-4"> <div className="flex items-center justify-between"> <h4 className="font-semibold text-blue-900">예약 상세</h4> <Badge className="bg-blue-500">확정</Badge> </div> <div className="space-y-3"> <div className="flex items-center gap-2"> <Calendar className="h-4 w-4 text-muted-foreground" /> <div> <p className="text-sm font-medium">날짜 및 시간</p> <p className="text-sm text-muted-foreground"> 2024년 1월 15일 14:00 - 15:00 </p> </div> </div> <div className="flex items-center gap-2"> <User className="h-4 w-4 text-muted-foreground" /> <div> <p className="text-sm font-medium">고객 정보</p> <p className="text-sm text-muted-foreground">홍길동</p> </div> </div> <div className="flex items-center gap-2"> <Phone className="h-4 w-4 text-muted-foreground" /> <div> <p className="text-sm font-medium">연락처</p> <p className="text-sm text-muted-foreground">010-1234-5678</p> </div> </div> <div className="flex items-center gap-2"> <Clock className="h-4 w-4 text-muted-foreground" /> <div> <p className="text-sm font-medium">서비스</p> <p className="text-sm text-muted-foreground">컨설팅 (60분)</p> </div> </div> </div> <div className="pt-2 border-t"> <Button size="sm" variant="outline" className="w-full"> 상세 보기 </Button> </div> </div> </PopoverContent> </Popover> ); }

시간대별 예약 정보

import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; export default function TimeSlotPopover() { return ( <div className="space-x-2"> {/* Available */} <Popover> <PopoverTrigger asChild> <Button variant="outline" size="sm" className="border-green-500 text-green-700" > 14:00 </Button> </PopoverTrigger> <PopoverContent className="w-80 border-green-500"> <div className="space-y-3"> <div className="flex items-center justify-between"> <h4 className="font-semibold text-green-900">14:00 - 15:00</h4> <Badge className="bg-green-500">예약 가능</Badge> </div> <p className="text-sm text-muted-foreground"> 이 시간대는 예약 가능합니다. </p> <div className="space-y-1"> <p className="text-sm font-medium">서비스</p> <p className="text-sm text-muted-foreground">컨설팅 (60분)</p> </div> <div className="space-y-1"> <p className="text-sm font-medium">가격</p> <p className="text-sm text-muted-foreground">50,000원</p> </div> <Button size="sm" className="bg-green-500 hover:bg-green-600 w-full" > 예약하기 </Button> </div> </PopoverContent> </Popover> {/* Confirmed */} <Popover> <PopoverTrigger asChild> <Button variant="outline" size="sm" className="border-blue-500 text-blue-700" > 15:00 </Button> </PopoverTrigger> <PopoverContent className="w-80 border-blue-500"> <div className="space-y-3"> <div className="flex items-center justify-between"> <h4 className="font-semibold text-blue-900">15:00 - 16:00</h4> <Badge className="bg-blue-500">예약됨</Badge> </div> <p className="text-sm text-muted-foreground"> 이 시간대는 이미 예약되었습니다. </p> <div className="space-y-1"> <p className="text-sm font-medium">고객명</p> <p className="text-sm text-muted-foreground">홍길동</p> </div> <div className="space-y-1"> <p className="text-sm font-medium">서비스</p> <p className="text-sm text-muted-foreground">컨설팅 (60분)</p> </div> </div> </PopoverContent> </Popover> </div> ); }

빠른 액션 메뉴

import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; import { MoreVertical, Edit, Trash2, Eye, Copy } from "lucide-react"; export default function QuickActionPopover() { return ( <Popover> <PopoverTrigger asChild> <Button variant="ghost" size="icon"> <MoreVertical className="h-4 w-4" /> </Button> </PopoverTrigger> <PopoverContent className="w-48 p-2" align="end"> <div className="space-y-1"> <Button variant="ghost" size="sm" className="w-full justify-start"> <Eye className="h-4 w-4 mr-2" /> 상세 보기 </Button> <Button variant="ghost" size="sm" className="w-full justify-start"> <Edit className="h-4 w-4 mr-2" /> 수정 </Button> <Button variant="ghost" size="sm" className="w-full justify-start"> <Copy className="h-4 w-4 mr-2" /> 복제 </Button> <div className="my-1 border-t" /> <Button variant="ghost" size="sm" className="w-full justify-start text-red-600 hover:text-red-700 hover:bg-red-50" > <Trash2 className="h-4 w-4 mr-2" /> 삭제 </Button> </div> </PopoverContent> </Popover> ); }

관련 컴포넌트

Last updated on