Search
๐Ÿ’ก

Popover

Created
2026/01/22 09:55
Tags
2026/01/22 10:27

Context

๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•  ๋•Œ ๋ณด์กฐ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์ด ๋•Œ Popover ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณด์กฐ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•˜๋Š”๋ฐ, shadcn/ui์˜ Popover๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ ์œ„ํ•ด ์ด ๊ธ€์„ ์ž‘์„ฑํ•œ๋‹ค.

Agenda

โ€ข
shadcn/ui์˜ Popover ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์ดํ•ดํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•จ

Contents

๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•  ๋•Œ ๋ณด์กฐ ์ •๋ณด๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ๋‹ค. Overlay๋ฅผ ๋™๋ฐ˜ํ•˜์—ฌ ํ™”๋ฉด ์ „์ฒด๋ฅผ ๋‘˜๋Ÿฌ์‹ธ๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๊ฐ€๋ฒผ์šด ๋ ˆ์ด์–ด๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ ๋ง์ด๋‹ค. shadcn/ui๋Š” ์ด๋Ÿฐ ์ƒํ™ฉ์„ ์œ„ํ•ด Popover ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
Popover
const Popover = ({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) => { return <PopoverPrimitive.Root data-slot='popover' {...props} />; };
TypeScript
๋ณต์‚ฌ
Popover ์ปดํฌ๋„ŒํŠธ์ด๋‹ค. radix-ui์˜ Popover - Root ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๋Š” Wrapper ํ˜•ํƒœ๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค.
PopoverTrigger
const PopoverTrigger = ({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) => { return <PopoverPrimitive.Trigger data-slot='popover-trigger' {...props} />; };
TypeScript
๋ณต์‚ฌ
PopoverTrigger ์ปดํฌ๋„ŒํŠธ๋Š” Popover์˜ ์—ด๋ฆผ / ๋‹ซํž˜ ์ƒํƒœ๋ฅผ ํ† ๊ธ€ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.
PopoverContent
const PopoverContent = ({ className, align = 'center', sideOffset = 4, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>) => { return ( <PopoverPrimitive.Portal> <PopoverPrimitive.Content data-slot='popover-content' align={align} sideOffset={sideOffset} className={cn( 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden', className, )} {...props} /> </PopoverPrimitive.Portal> ); };
TypeScript
๋ณต์‚ฌ
Popover์˜ ์ปจํ…์ธ ๊ฐ€ ํ‘œ์‹œ ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ์ด๋‹ค. align์€ center, start, end ๊ฐ’ ์ค‘ ํ•˜๋‚˜๋ฅผ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋‹ค. className์— ๋“ค์–ด์žˆ๋Š” ์Šคํƒ€์ผ๋ง ๊ฐ’์€ ๋งค์šฐ ๊ธด๋ฐ, ๊ฑฐ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ๊ด€๋ จ ๋œ ๊ฐ’๋“ค์ด๋‹ค.
PopoverAnchor
const PopoverAnchor = ({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) => { return <PopoverPrimitive.Anchor data-slot='popover-anchor' {...props} />; };
TypeScript
๋ณต์‚ฌ
pnpm dlx shadcn@latest add popover๋ฅผ ํ†ตํ•ด popover๋ฅผ ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ ๊ฐ™์ด ์ƒ์„ฑ ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ์ด๋‹ค. ์–ด๋А ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ธ์ง€ ๊ถ๊ธˆํ•˜์—ฌ ๊ณต์‹ ์‚ฌ์ดํŠธ์—์„œ ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ์ฐพ์•„๋ณด๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ ๋‚˜์˜ค์งˆ ์•Š๋Š”๋‹ค.
๋А๋‚Œ์ƒ Trigger๋ฅผ ์„ ํƒํ•˜๋ฉด Trigger ๊ธฐ์ค€์œผ๋กœ Popover๊ฐ€ ํ‘œ์‹œ๋˜๋Š”๋ฐ Anchor๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ทธ Anchor๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‚˜์˜ค๋Š” ๊ฒƒ ๊ฐ™์•„ ์‹คํ—˜์„ ํ•ด๋ณด์•˜๋‹ค.
<Popover> <PopoverTrigger asChild> <Button>Trigger</Button> </PopoverTrigger> <PopoverAnchor> <div className='relative border-4'> <span>Anchor</span> </div> </PopoverAnchor> <PopoverContent align='start'>Description</PopoverContent> </Popover>
TypeScript
๋ณต์‚ฌ
Trigger๋ฅผ ํด๋ฆญํ•˜๋ฉด Anchor๊ฐ€ ์—†์—ˆ์„ ๋•Œ Trigger ๋ฐ”๋กœ ์•„๋ž˜์— Popover๊ฐ€ ๋‚˜์˜ค์ง€๋งŒ, Anchor๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๊ทธ Anchor๋ฅผ ๊ธฐ์ค€์œผ๋กœ Popover๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.