Popover

Popover는 트리거 요소 근처에 부가 정보나 상호작용 콘텐츠를 표시하는 오버레이 컴포넌트입니다. 툴팁, 메뉴, 폼, 상세 정보 등을 표시할 때 사용합니다.
'use client';

import { Button, Popover } from '@vapor-ui/core';

export default function DefaultPopover() {
    return (
        <div className="flex justify-center p-20">
            <Popover.Root>
                <Popover.Trigger render={<Button variant="outline" />}>팝오버 열기</Popover.Trigger>
                <Popover.Content>
                    <Popover.Title>알림</Popover.Title>
                    <Popover.Description>
                        새로운 메시지 3개와 알림 1개가 있습니다.
                    </Popover.Description>
                </Popover.Content>
            </Popover.Root>
        </div>
    );
}

Property


PositionerProps

Popover가 나타날 위치와 정렬을 설정할 수 있습니다. 기본값은 'bottom'입니다.

'use client';

import { Button, Popover } from '@vapor-ui/core';

export default function PopoverPositioning() {
    return (
        <div className="grid grid-cols-2 gap-8 p-20">
            <div className="space-y-6">
                <h3 className="text-sm font-medium text-foreground-hint">방향 설정</h3>
                <div className="grid grid-cols-2 gap-4">
                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            상단 팝오버
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ side: 'top' }}>
                            <Popover.Title>상단 팝오버</Popover.Title>
                            <Popover.Description>
                                트리거 위쪽에 표시되는 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>

                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            우측 팝오버
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ side: 'right' }}>
                            <Popover.Title>우측 팝오버</Popover.Title>
                            <Popover.Description>
                                트리거 오른쪽에 표시되는 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>

                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            하단 팝오버
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ side: 'bottom' }}>
                            <Popover.Title>하단 팝오버</Popover.Title>
                            <Popover.Description>
                                트리거 아래쪽에 표시되는 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>

                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            좌측 팝오버
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ side: 'left' }}>
                            <Popover.Title>좌측 팝오버</Popover.Title>
                            <Popover.Description>
                                트리거 왼쪽에 표시되는 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>
                </div>
            </div>

            <div className="space-y-6">
                <h3 className="text-sm font-medium text-foreground-hint">정렬 설정</h3>
                <div className="space-y-4">
                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            시작점 정렬
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ align: 'start' }}>
                            <Popover.Title>시작점 정렬</Popover.Title>
                            <Popover.Description>
                                트리거의 시작점에 정렬된 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>

                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            중앙 정렬
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ align: 'center' }}>
                            <Popover.Title>중앙 정렬</Popover.Title>
                            <Popover.Description>
                                트리거의 중앙에 정렬된 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>

                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            끝점 정렬
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ align: 'end' }}>
                            <Popover.Title>끝점 정렬</Popover.Title>
                            <Popover.Description>
                                트리거의 끝점에 정렬된 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>
                </div>
            </div>
        </div>
    );
}

Controlled State

Popover의 열림/닫힘 상태를 외부에서 제어할 수 있습니다.

'use client';

import { useState } from 'react';

import { Button, Popover } from '@vapor-ui/core';

export default function PopoverControlled() {
    const [isOpen, setIsOpen] = useState(false);

    return (
        <div className="flex flex-col items-center gap-4 p-20">
            <div className="flex gap-2">
                <Button variant="outline" onClick={() => setIsOpen(true)}>
                    팝오버 열기
                </Button>
                <Button variant="outline" onClick={() => setIsOpen(false)}>
                    팝오버 닫기
                </Button>
            </div>

            <p className="text-sm text-foreground-hint">현재 상태: {isOpen ? '열림' : '닫힘'}</p>

            <Popover.Root open={isOpen} onOpenChange={setIsOpen}>
                <Popover.Trigger render={<Button />}>제어되는 팝오버</Popover.Trigger>
                <Popover.Content>
                    <Popover.Title>제어되는 팝오버</Popover.Title>
                    <Popover.Description>
                        이 팝오버는 외부 상태에 의해 제어됩니다. 위의 버튼으로 열고 닫을 수
                        있습니다.
                    </Popover.Description>
                    <div className="mt-4 flex gap-2">
                        <Button size="sm" onClick={() => setIsOpen(false)}>
                            닫기
                        </Button>
                    </div>
                </Popover.Content>
            </Popover.Root>
        </div>
    );
}

Offset

Popover와 트리거 간의 거리를 세밀하게 조정할 수 있습니다.

'use client';

import { Button, Popover } from '@vapor-ui/core';

export default function PopoverOffset() {
    return (
        <div className="flex flex-col items-center gap-8 p-20">
            <div className="space-y-6">
                <h3 className="text-center text-sm font-medium text-foreground-hint">
                    오프셋 조정
                </h3>

                <div className="flex gap-4">
                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            기본 오프셋
                        </Popover.Trigger>
                        <Popover.Content>
                            <Popover.Title>기본 오프셋</Popover.Title>
                            <Popover.Description>
                                기본 8px 오프셋이 적용된 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>

                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            사이드 오프셋 16px
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ sideOffset: 16 }}>
                            <Popover.Title>사이드 오프셋 16px</Popover.Title>
                            <Popover.Description>
                                트리거로부터 16px 떨어진 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>

                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            정렬 오프셋 20px
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ alignOffset: 20 }}>
                            <Popover.Title>정렬 오프셋 20px</Popover.Title>
                            <Popover.Description>
                                정렬 축에서 20px 이동한 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>

                    <Popover.Root>
                        <Popover.Trigger render={<Button variant="outline" />}>
                            복합 오프셋
                        </Popover.Trigger>
                        <Popover.Content positionerProps={{ sideOffset: 24, alignOffset: -10 }}>
                            <Popover.Title>복합 오프셋</Popover.Title>
                            <Popover.Description>
                                사이드 24px, 정렬 -10px 오프셋이 적용된 팝오버입니다.
                            </Popover.Description>
                        </Popover.Content>
                    </Popover.Root>
                </div>
            </div>
        </div>
    );
}

Examples


Content Variations

팝오버는 간단한 텍스트부터 복잡한 상호작용 요소까지 다양한 콘텐츠를 담을 수 있습니다.

'use client';

import { Button, Popover } from '@vapor-ui/core';

export default function PopoverContent() {
    return (
        <div className="flex flex-wrap gap-4 p-20">
            <Popover.Root>
                <Popover.Trigger render={<Button variant="outline" />}>
                    간단한 텍스트
                </Popover.Trigger>
                <Popover.Content>간단한 팝오버 메시지입니다.</Popover.Content>
            </Popover.Root>

            <Popover.Root>
                <Popover.Trigger render={<Button variant="outline" />}>제목과 설명</Popover.Trigger>
                <Popover.Content>
                    <Popover.Title>알림</Popover.Title>
                    <Popover.Description>
                        새로운 업데이트가 있습니다. 확인해보세요.
                    </Popover.Description>
                </Popover.Content>
            </Popover.Root>

            <Popover.Root>
                <Popover.Trigger render={<Button variant="outline" />}>
                    상호작용 콘텐츠
                </Popover.Trigger>
                <Popover.Content>
                    <Popover.Title>설정</Popover.Title>
                    <Popover.Description>원하는 설정을 선택하세요.</Popover.Description>
                    <div className="mt-4 space-y-2">
                        <Button size="sm" className="w-full">
                            옵션 1
                        </Button>
                        <Button size="sm" variant="outline" className="w-full">
                            옵션 2
                        </Button>
                    </div>
                </Popover.Content>
            </Popover.Root>
        </div>
    );
}

Props Table


Popover.Root

Popover의 루트 컨테이너로, 전체 Popover 컴포넌트의 상태와 동작을 관리합니다.

PropDefaultType
open?
undefined
boolean
defaultOpen?
false
boolean
onOpenChange?
undefined
(open: boolean, event?: Event, reason?: string) => void
modal?
true
boolean{ trapFocus?: boolean }
openOnHover?
false
boolean
delay?
0
number{ open?: number; close?: number }

Popover.Trigger

Popover를 여는 트리거 요소입니다.

PropDefaultType
render?
undefined
React.ReactElement(props: TriggerProps, state: TriggerState) => React.ReactElement
disabled?
false
boolean

Popover.Content

Popover의 실제 콘텐츠를 담는 컨테이너입니다. Portal과 Positioner를 조합하여 구성됩니다.

PropDefaultType
portalProps?
undefined
PopoverPortalProps
positionerProps?
undefined
PopoverPositionerProps
className?
undefined
string

Popover.Portal

Popover를 DOM의 다른 위치에 렌더링하는 포털 컴포넌트입니다.

PropDefaultType
container?
document.body
HTMLElement() => HTMLElementnull
keepMounted?
false
boolean

Popover.Positioner

Popover의 위치를 설정하는 컴포넌트입니다.

PropDefaultType
side?
'bottom'
'top''right''bottom''left'
align?
'center'
'start''center''end'
sideOffset?
8
number(side: Side) => number
alignOffset?
0
number(side: Side) => number
collisionPadding?
8
number{ top?: number; right?: number; bottom?: number; left?: number }
collisionAvoidance?
true
boolean{ boundary?: HTMLElement; rootBoundary?: 'viewport' | 'document'; padding?: number }
arrowPadding?
4
number
sticky?
false
boolean'partial'

Popover.Popup

Popover의 실제 팝업 콘텐츠 영역입니다.

PropDefaultType
className?
undefined
string
style?
undefined
CSSProperties
render?
undefined
React.ReactElement(props: PopupProps, state: PopupState) => React.ReactElement

Popover.Title

Popover의 제목을 표시하는 컴포넌트입니다.

PropDefaultType
render?
undefined
React.ReactElement(props: TitleProps) => React.ReactElement

Popover.Description

Popover의 설명을 표시하는 컴포넌트입니다.

PropDefaultType
render?
undefined
React.ReactElement(props: DescriptionProps) => React.ReactElement

Popover.Close

Popover를 닫는 버튼 컴포넌트입니다.

PropDefaultType
render?
undefined
React.ReactElement(props: CloseProps) => React.ReactElement