Popover
Popover
주어진 타겟 요소에 상대적으로 팝오버 섹션을 표시하는 컴포넌트예요.
출처: 문서
본문
사용법 (Usage)
Popover로 타겟 요소에 상대적인 팝오버를 표시해요.
import { Popover, Text, Button } from '@mantine/core';
function Demo() {
return (
<Popover width={200} position="bottom" withArrow>
<Popover.Target>
<Button>Toggle popover</Button>
</Popover.Target>
<Popover.Dropdown>
<Text size="xs">This is uncontrolled popover, it is opened when button is clicked</Text>
</Popover.Dropdown>
</Popover>
);
}
제어 방식 (Controlled)
opened와 onChange prop으로 Popover 상태를 제어할 수 있어요.
import { useState } from 'react';
import { Button, Popover } from '@mantine/core';
function Demo() {
const [opened, setOpened] = useState(false);
return (
<Popover opened={opened} onChange={setOpened}>
<Popover.Target>
<Button onClick={() => setOpened((o) => !o)}>Toggle popover</Button>
</Popover.Target>
<Popover.Dropdown>Dropdown</Popover.Dropdown>
</Popover>
);
}
마우스 이벤트를 사용하는 제어 예시:
import { useDisclosure } from '@mantine/hooks';
import { Popover, Text, Button } from '@mantine/core';
function Demo() {
const [opened, { close, open }] = useDisclosure(false);
return (
<Popover opened={opened}>
<Popover.Target>
<Button onMouseEnter={open} onMouseLeave={close}>Hover to see popover</Button>
</Popover.Target>
<Popover.Dropdown>
<Text size="xs">This popover is shown when user hovers the target element</Text>
</Popover.Dropdown>
</Popover>
);
}
포커스 트랩 (Focus trap)
Popover.Dropdown 안에서 대화형 요소(인풋, 버튼 등)를 사용해야 한다면 trapFocus prop을 설정해요.
import { Popover, Button, TextInput } from '@mantine/core';
function Demo() {
return (
<Popover width={200} trapFocus>
<Popover.Target><Button>Toggle popover</Button></Popover.Target>
<Popover.Dropdown>
<TextInput placeholder="Input inside popover" />
</Popover.Dropdown>
</Popover>
);
}
인라인 요소 (Inline elements)
inline 미들웨어를 활성화하면 인라인 요소와 Popover를 사용할 수 있어요.
import { Popover, Mark, Text } from '@mantine/core';
function Demo() {
return (
<Text>
Stantler's magnificent antlers were traded at high prices as works of art. As a result, this
Pokémon was hunted close to extinction by those who were after the priceless antlers.{' '}
<Popover middlewares={{ inline: true }} position="top">
<Popover.Target>
<Mark>When visiting a junkyard</Mark>
</Popover.Target>
<Popover.Dropdown>Inline dropdown</Popover.Dropdown>
</Popover>
, you may catch sight of it having an intense fight with Murkrow over shiny objects.
</Text>
);
}
같은 너비 (Same width)
width="target" prop을 설정하면 Popover 드롭다운이 타겟 요소와 같은 너비를 차지해요.
import { Popover, Text, Button } from '@mantine/core';
function Demo() {
return (
<Popover width="target">
<Popover.Target><Button>Toggle popover</Button></Popover.Target>
<Popover.Dropdown>
<Text size="xs">This popover has same width as target, it is useful when you are building input dropdowns</Text>
</Popover.Dropdown>
</Popover>
);
}
offset
offset prop을 숫자로 설정하면 타겟 요소에 상대적인 드롭다운 위치를 변경할 수 있어요. 이렇게 하면 메인 축에서만 드롭다운 오프셋을 제어할 수 있어요.
import { Popover, Button, Text } from '@mantine/core';
function Demo() {
return (
<Popover position="bottom" offset={20}>
<Popover.Target><Button>Popover target</Button></Popover.Target>
<Popover.Dropdown>
<Text size="xs">Change position and offset to configure dropdown offset relative to target</Text>
</Popover.Dropdown>
</Popover>
);
}
두 축 모두 오프셋을 제어하려면 mainAxis와 crossAxis 속성을 가진 객체를 전달해요.
import { Popover, Button, Text } from '@mantine/core';
function Demo() {
return (
<Popover offset={{ mainAxis: 10, crossAxis: 20 }}>
{/* ... */}
</Popover>
);
}
미들웨어 (Middlewares)
middlewares prop으로 Floating UI 미들웨어를 활성화/비활성화할 수 있어요.
- shift 미들웨어는 드롭다운을 뷰 안에 유지하도록 이동시켜요. 기본적으로 활성화돼 있어요.
- flip 미들웨어는 드롭다운을 뷰 안에 유지하도록 배치를 변경해요. 기본적으로 활성화돼 있어요.
- inline 미들웨어는 여러 줄에 걸친 인라인 참조 요소의 배치를 개선해요. 기본적으로 비활성화돼 있어요.
- size 미들웨어는 드롭다운 크기를 조작해요. 기본적으로 비활성화돼 있어요.
shift와 flip 미들웨어를 끄는 예시:
import { Popover } from '@mantine/core';
function Demo() {
return (
<Popover middlewares={{ shift: false, flip: false }}>
{/* Popover content */}
</Popover>
);
}
미들웨어 옵션 커스터마이즈 (Customize middleware options)
Floating UI 미들웨어 옵션을 커스터마이즈하려면 middlewares prop에 객체로 전달해요. 예를 들어 shift 미들웨어 패딩을 20px로 바꾸려면 다음 설정을 사용해요.
import { Popover } from '@mantine/core';
function Demo() {
return (
<Popover middlewares={{ shift: { padding: 20 } }}>
{/* Popover content */}
</Popover>
);
}
드롭다운 화살표 (Dropdown arrow)
withArrow prop을 설정하면 드롭다운에 화살표를 추가할 수 있어요. 화살표는 transform: rotate(45deg)로 회전된 div 요소예요.
arrowPosition prop은 Popover 컴포넌트에서 position이 *-start와 *-end 값으로 설정될 때 화살표가 타겟 요소에 상대적으로 어떻게 배치되는지 결정해요. 기본값은 center예요. 가능하면 화살표가 타겟 요소의 중앙에 배치돼요.
arrowPosition을 side로 변경하면 화살표가 타겟 요소의 측면에 배치되고, arrowOffset prop으로 화살표 오프셋을 제어할 수 있어요. arrowPosition이 center일 때는 arrowOffset prop이 무시돼요.
오버레이와 함께 (With overlay)
withOverlay prop을 설정하면 드롭다운 뒤에 오버레이를 추가할 수 있어요. overlayProps prop으로 Overlay 컴포넌트에 추가 설정을 전달할 수 있어요.
import { Popover, Avatar, Text, Group, Anchor, Stack } from '@mantine/core';
function Demo() {
return (
<Popover withOverlay overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} width={280} position="bottom">
<Popover.Target><Button variant="default">Toggle</Button></Popover.Target>
<Popover.Dropdown>
<Group wrap="nowrap">
<Avatar src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-8.png" size={60} radius="md" />
<div>
<Text fw={600}>Mantine</Text>
<Anchor href="https://x.com/mantinedev" size="xs" c="dimmed">@mantinedev</Anchor>
</div>
</Group>
<Text size="xs" mt="md" c="dimmed">Customizable React components and hooks library with focus on usability, accessibility and developer experience</Text>
<Group mt="sm" gap="xs">
<Text size="xs">0 Following</Text>
<Text size="xs">1,174 Followers</Text>
</Group>
</Popover.Dropdown>
</Popover>
);
}
분리 시 숨기기 (Hide detached)
hideDetached prop으로 타겟 요소가 스타일(display: none, visibility: hidden 등)로 숨겨지거나 DOM에서 제거되거나 뷰포트 밖으로 스크롤되었을 때 드롭다운이 어떻게 동작할지 설정할 수 있어요.
기본적으로 hideDetached는 활성화되어 있고, 드롭다운은 타겟 요소와 함께 숨겨져요. hideDetached={false}로 이 동작을 바꿀 수 있어요.
import { Box, Button, Group, Popover } from '@mantine/core';
function Demo() {
return (
<Group>
<Box>
<Popover>
<Popover.Target><Button>Toggle popover</Button></Popover.Target>
<Popover.Dropdown>This popover dropdown is hidden when detached</Popover.Dropdown>
</Popover>
</Box>
<Box>
<Popover hideDetached={false}>
<Popover.Target><Button>Toggle popover</Button></Popover.Target>
<Popover.Dropdown>This popover dropdown is visible when detached</Popover.Dropdown>
</Popover>
</Box>
</Group>
);
}
비활성 (Disabled)
disabled prop을 설정하면 Popover.Dropdown 렌더링을 막을 수 있어요.
import { Popover, Text, Button } from '@mantine/core';
function Demo() {
return (
<Popover disabled>
<Popover.Target><Button>Toggle popover</Button></Popover.Target>
<Popover.Dropdown>
<Text size="xs">Disabled popover dropdown is always hidden</Text>
</Popover.Dropdown>
</Popover>
);
}
외부 클릭 (Click outside)
기본적으로 Popover은 드롭다운 밖을 클릭하면 닫혀요. 이 동작을 비활성화하려면 closeOnClickOutside={false}를 설정해요.
clickOutsideEvents prop으로 외부 클릭 감지에 사용되는 이벤트를 설정할 수 있어요. 기본적으로 Popover은 mousedown과 touchstart 이벤트를 듣고 있어요. 이를 mouseup과 touchend 같은 다른 이벤트로 바꿀 수 있어요.
onDismiss
열림 상태를 제어해야 하지만 외부 클릭과 Escape 키로 팝오버를 닫고 싶다면 onDismiss prop을 사용해요.
import { useState } from 'react';
import { Button, Popover } from '@mantine/core';
function Demo() {
const [opened, setOpened] = useState(false);
return (
<Popover opened={opened} onDismiss={() => setOpened(false)}>
<Popover.Target>
<Button onClick={() => setOpened((o) => !o)}>Toggle popover</Button>
</Popover.Target>
<Popover.Dropdown>Dropdown</Popover.Dropdown>
</Popover>
);
}
초기 포커스 (Initial focus)
Popover는 FocusTrap 컴포넌트로 포커스를 관리해요. 초기 포커스를 받을 요소에 data-autofocus 속성을 추가해요.
import { Popover } from '@mantine/core';
function Demo() {
return (
<Popover width={200} opened position="bottom">
<Popover.Target><button>Target</button></Popover.Target>
<Popover.Dropdown>
<input data-autofocus placeholder="Initial focus" />
</Popover.Dropdown>
</Popover>
);
}
Popover.Target children
Popover.Target은 단일 자식으로 요소나 컴포넌트를 요구해요. 문자열, 프래그먼트, 숫자, 여러 요소/컴포넌트는 지원되지 않고 오류를 던져요. 커스텀 컴포넌트는 루트 요소 ref를 얻는 prop을 제공해야 해요. 모든 Mantine 컴포넌트는 기본적으로 ref를 지원해요.
import { Popover, Button } from '@mantine/core';
function Demo() {
return (
<>
<Popover.Target><button>Native button – ok</button></Popover.Target>
{/* OK */}
<Popover.Target><Button>Mantine component – ok</Button></Popover.Target>
{/* String, Number, Fragment, Multiple nodes – NOT OK, will throw error */}
</>
);
}
필수 ref prop (Required ref prop)
Popover.Target 안에서 렌더링되는 커스텀 컴포넌트는 ref prop을 지원해야 해요.
// Example of code that WILL NOT WORK
import { Popover } from '@mantine/core';
// ❌ ref is not forwarded to the root element
function MyComponent() {
return <div>My component</div>;
}
ref를 루트 요소에 전달하면 동작해요.
// Example of code that will work
import { Popover } from '@mantine/core';
// ✅ ref is forwarded to the root element
function MyComponent({ ref, ...others }: React.ComponentProps<'div'>) {
return <div {...others} ref={ref}>My component</div>;
}
컨텍스트 메뉴 (Context menu)
Popover.ContextMenu를 사용하면 우클릭 시 커서 위치에 드롭다운을 열 수 있어요. Popover.Target을 대체하고 contextmenu 이벤트에 응답해야 할 요소를 감싸요. 브라우저의 기본 컨텍스트 메뉴는 억제되고 Popover.Dropdown이 커서 위치에 배치돼요. 다시 우클릭하면 드롭다운이 새 좌표로 재배치돼요. Popover.Dropdown에는 어떤 콘텐츠든 넣을 수 있어요. disabled를 설정하면 브라우저 기본 컨텍스트 메뉴를 복원할 수 있어요.
import { Avatar, Button, Group, Paper, Popover, Stack, Text } from '@mantine/core';
function Demo() {
return (
<Paper withBorder radius="md" p="xl" maw={400} mx="auto">
<Text>Right-click anywhere inside this area</Text>
<Text size="sm" c="dimmed">A popover will open at the cursor position</Text>
<Popover.ContextMenu>
<Popover.Target>
<div>(right-click area)</div>
</Popover.Target>
<Popover.Dropdown w={280}>
<Group wrap="nowrap">
<Avatar size={44}>JD</Avatar>
<Stack gap={0}>
<Text fw={500}>Jane Doe</Text>
<Text size="xs" c="dimmed">[email protected]</Text>
</Stack>
</Group>
<Group mt="md">
<Button size="xs">Message</Button>
<Button size="xs" variant="default">Follow</Button>
</Group>
</Popover.Dropdown>
</Popover.ContextMenu>
</Paper>
);
}
터치 기기 (Touch devices)
contextmenu 이벤트를 발생시키지 않는 터치 기기(특히 iOS Safari)에서는 길게 누르기로 드롭다운이 열려요. longPressDelay prop으로 드롭다운이 열리기 전 요소를 눌러야 하는 시간을 제어할 수 있고, 기본값은 500ms예요. 터치 기기에서 드롭다운 아래에 네이티브 텍스트 선택 콜아웃이 나타나는 것을 막기 위해, Popover.ContextMenu은 감싼 요소에서 텍스트 선택(user-select: none)을 비활성화해요.
중첩 팝오버 (Nested popovers)
중첩 팝오버는 Portal 없이 children을 렌더링해야 해요. 보통 팝오버 콘텐츠를 렌더링하는 컴포넌트의 prop으로 포털을 비활성화해야 해요. 예를 들어 Select에는 comboboxProps={{ withinPortal: false }} prop이 있어요. 팝오버 콘텐츠를 렌더링하는 데 사용하는 컴포넌트의 문서에서 포털을 비활성화하는 방법을 확인해요. 포털이 비활성화되지 않으면 외부 클릭으로 모든 팝오버가 닫혀요.
접근성 (Accessibility)
Popover는 WAI-ARIA 권장사항을 따릅니다.
- 드롭다운 요소에는
role="dialog"와aria-labelledby="target-id"속성이 있어요 - 타겟 요소에는
aria-haspopup="dialog",aria-expanded,aria-controls="dropdown-id"속성이 있어요
비제어 Popover는 button 요소나 그것을 렌더링하는 컴포넌트(Button, ActionIcon 등)와 함께 쓸 때만 접근 가능해요. 다른 요소는 Space와 Enter 키 입력을 지원하지 않아요.
키보드 상호작용 (Keyboard interactions)
| Key | Description | Condition |
|---|---|---|
| Escape | 드롭다운 닫기 | 드롭다운 내부 포커스 |
| Space/Enter | 드롭다운 열기/닫기 | 타겟 요소 포커스 |