Popover
React overlayClick-triggered floating panel anchored to a trigger element. Supports optional localStorage persistence and configurable dismissal (click-outside, escape, manual). Built on @radix-ui/react-popover with collision detection.
Also available for Vue ->Installation
$ pnpm dlx shadcn@latest add https://react.uipkge.dev/r/react/popover.json$ npx shadcn@latest add https://react.uipkge.dev/r/react/popover.json$ yarn dlx shadcn@latest add https://react.uipkge.dev/r/react/popover.json$ bunx shadcn@latest add https://react.uipkge.dev/r/react/popover.json
Or with the named registry:
npx shadcn@latest add @uipkge-react/popover
Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
persist a string uses that key verbatim, `false` (default) disables. | string | boolean | false | optional |
closeBehavior Controls how the popover may be dismissed. | PopoverCloseBehavior | auto | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
PopoverContextValue interface PopoverContextValue {
closeBehavior: PopoverCloseBehavior
} Dependencies
Used by
Files (2)
-
components/ui/popover/popover.tsx 4.8 kB
'use client' import * as React from 'react' import * as PopoverPrimitive from '@radix-ui/react-popover' import { cn } from '@/lib/utils' export type PopoverCloseBehavior = 'auto' | 'click-outside' | 'esc' | 'manual' | 'none' interface PopoverContextValue { closeBehavior: PopoverCloseBehavior } const PopoverContext = React.createContext<PopoverContextValue>({ closeBehavior: 'auto' }) export interface PopoverProps extends React.ComponentProps<typeof PopoverPrimitive.Root> { /** Persist open state in localStorage. `true` uses an auto-generated key, * a string uses that key verbatim, `false` (default) disables. */ persist?: string | boolean /** Controls how the popover may be dismissed. */ closeBehavior?: PopoverCloseBehavior } function Popover({ persist = false, closeBehavior = 'auto', open, defaultOpen, onOpenChange, children, ...props }: PopoverProps) { const autoId = React.useId() const storageKey = React.useMemo(() => { if (persist === false) return null if (persist === true) return `uipkge-popover-${autoId}` return persist }, [persist, autoId]) const isControlled = open !== undefined const [localOpen, setLocalOpen] = React.useState<boolean>(defaultOpen ?? false) // Hydrate from localStorage on mount. React.useEffect(() => { if (!storageKey || typeof localStorage === 'undefined') return if (localStorage.getItem(storageKey) === '1') { setLocalOpen(true) if (!isControlled) onOpenChange?.(true) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [storageKey]) // Persist current open state. const effectiveOpen = isControlled ? open : localOpen React.useEffect(() => { if (!storageKey || typeof localStorage === 'undefined') return if (effectiveOpen) localStorage.setItem(storageKey, '1') else localStorage.removeItem(storageKey) }, [storageKey, effectiveOpen]) function handleOpenChange(value: boolean) { setLocalOpen(value) onOpenChange?.(value) } return ( <PopoverContext.Provider value={{ closeBehavior }}> <PopoverPrimitive.Root data-uipkge="" data-slot="popover" open={open} defaultOpen={defaultOpen} onOpenChange={handleOpenChange} {...props} > {children} </PopoverPrimitive.Root> </PopoverContext.Provider> ) } const PopoverTrigger = React.forwardRef< React.ElementRef<typeof PopoverPrimitive.Trigger>, React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger> >((props, ref) => ( <PopoverPrimitive.Trigger ref={ref} data-uipkge="" data-slot="popover-trigger" {...props} /> )) PopoverTrigger.displayName = PopoverPrimitive.Trigger.displayName const PopoverAnchor = React.forwardRef< React.ElementRef<typeof PopoverPrimitive.Anchor>, React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Anchor> >((props, ref) => ( <PopoverPrimitive.Anchor ref={ref} data-uipkge="" data-slot="popover-anchor" {...props} /> )) PopoverAnchor.displayName = PopoverPrimitive.Anchor.displayName const PopoverContent = React.forwardRef< React.ElementRef<typeof PopoverPrimitive.Content>, React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => { const { closeBehavior } = React.useContext(PopoverContext) function handlePointerDownOutside(e: Event) { if (closeBehavior === 'esc' || closeBehavior === 'manual' || closeBehavior === 'none') { e.preventDefault() } } function handleEscapeKeyDown(e: KeyboardEvent) { if (closeBehavior === 'click-outside' || closeBehavior === 'manual' || closeBehavior === 'none') { e.preventDefault() } } return ( <PopoverPrimitive.Portal> <PopoverPrimitive.Content ref={ref} data-uipkge="" data-slot="popover-content" align={align} sideOffset={sideOffset} onPointerDownOutside={handlePointerDownOutside} onEscapeKeyDown={handleEscapeKeyDown} className={cn( 'bg-popover text-popover-foreground motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe: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> ) }) PopoverContent.displayName = PopoverPrimitive.Content.displayName export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent } -
components/ui/popover/index.ts 0.1 kB
export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent, type PopoverProps, type PopoverCloseBehavior, } from './popover'
Raw manifest: https://react.uipkge.dev/r/react/popover.json