{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "popover",
  "title": "Popover",
  "description": "A popover component.",
  "registryDependencies": [
    "@cubby-ui/elevated"
  ],
  "files": [
    {
      "path": "registry/default/popover/popover.tsx",
      "content": "import * as React from \"react\";\nimport { Popover as BasePopover } from \"@base-ui/react/popover\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  solidSurface,\n  type SurfaceLevel,\n} from \"@/registry/default/lib/elevated\";\n\nfunction Popover<Payload = unknown>({\n  ...props\n}: BasePopover.Root.Props<Payload>) {\n  return <BasePopover.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverPortal({ ...props }: BasePopover.Portal.Props) {\n  return <BasePopover.Portal data-slot=\"popover-portal\" {...props} />;\n}\n\nfunction PopoverTrigger({ ...props }: BasePopover.Trigger.Props) {\n  return <BasePopover.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverClose({ ...props }: BasePopover.Close.Props) {\n  return <BasePopover.Close data-slot=\"popover-close\" {...props} />;\n}\n\nfunction PopoverArrow({ ...props }: BasePopover.Arrow.Props) {\n  return <BasePopover.Arrow data-slot=\"popover-arrow\" {...props} />;\n}\n\nfunction PopoverPositioner({ ...props }: BasePopover.Positioner.Props) {\n  return <BasePopover.Positioner data-slot=\"popover-positioner\" {...props} />;\n}\n\nfunction PopoverViewport({ className, ...props }: BasePopover.Viewport.Props) {\n  return (\n    <BasePopover.Viewport\n      data-slot=\"popover-viewport\"\n      // Same height and overflow policy as the viewport inside PopoverContent,\n      // which see for why both the percentage and the cap are needed. Padding\n      // is left to the caller; hand-composed popups own their own spacing.\n      className={cn(\n        \"relative h-full max-h-(--available-height) w-full overflow-clip\",\n        \"not-data-transitioning:overflow-y-auto\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverPopup({\n  className,\n  level = 3,\n  shadowLevel = 3,\n  ...props\n}: BasePopover.Popup.Props & {\n  level?: SurfaceLevel;\n  shadowLevel?: SurfaceLevel;\n}) {\n  return (\n    <BasePopover.Popup\n      data-slot=\"popover-popup\"\n      data-level={level}\n      className={cn(\n        \"text-popover-foreground relative max-h-(--available-height) max-w-(--available-width) origin-(--transform-origin) rounded-xl outline-none\",\n        solidSurface(level, shadowLevel),\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverBackdrop({ className, ...props }: BasePopover.Backdrop.Props) {\n  return (\n    <BasePopover.Backdrop\n      data-slot=\"popover-backdrop\"\n      className={cn(\n        \"ease-out-expo fixed inset-0 z-30 min-h-dvh bg-black/40 transition-all duration-150 supports-[-webkit-touch-callout:none]:absolute\",\n        \"backdrop-blur-sm data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverTitle({ className, ...props }: BasePopover.Title.Props) {\n  return (\n    <BasePopover.Title\n      data-slot=\"popover-title\"\n      className={cn(\"text-sm font-semibold\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverDescription({\n  className,\n  ...props\n}: BasePopover.Description.Props) {\n  return (\n    <BasePopover.Description\n      data-slot=\"popover-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Base UI sizes the positioner only on open or trigger change. Copying the\n * popup's width onto it lets Floating UI re-place it. Height stays pinned:\n * following it makes a popover near the bottom edge flip back and forth.\n */\nfunction followPopupWidth(positioner: HTMLElement | null): (() => void) | void {\n  // The popup sits between Base UI's focus guards, so find it by slot.\n  const popup = positioner?.querySelector(\n    ':scope > [data-slot=\"popover-content\"]',\n  );\n  if (!positioner || !(popup instanceof HTMLElement)) return;\n  const observer = new ResizeObserver(() => {\n    positioner.style.setProperty(\n      \"--positioner-width\",\n      `${popup.offsetWidth}px`,\n    );\n  });\n  observer.observe(popup);\n  return () => observer.disconnect();\n}\n\nfunction PopoverContent({\n  children,\n  className,\n  side = \"bottom\",\n  align = \"center\",\n  sideOffset = 8,\n  alignOffset = 0,\n  collisionBoundary,\n  collisionPadding = 10,\n  sticky = false,\n  positionMethod = \"absolute\",\n  anchor,\n  viewportClassName,\n  followContentWidth = false,\n  arrow = false,\n  arrowPadding,\n  container,\n  level = 3,\n  shadowLevel = 3,\n  ...props\n}: BasePopover.Popup.Props & {\n  side?: BasePopover.Positioner.Props[\"side\"];\n  align?: BasePopover.Positioner.Props[\"align\"];\n  sideOffset?: BasePopover.Positioner.Props[\"sideOffset\"];\n  alignOffset?: BasePopover.Positioner.Props[\"alignOffset\"];\n  collisionBoundary?: BasePopover.Positioner.Props[\"collisionBoundary\"];\n  collisionPadding?: BasePopover.Positioner.Props[\"collisionPadding\"];\n  sticky?: BasePopover.Positioner.Props[\"sticky\"];\n  positionMethod?: BasePopover.Positioner.Props[\"positionMethod\"];\n  /** Positions against this element instead of the trigger. */\n  anchor?: BasePopover.Positioner.Props[\"anchor\"];\n  /** Classes for the inner viewport, e.g. to change its padding. */\n  viewportClassName?: string;\n  /** Re-places the popover when its content changes width; sizes to content. */\n  followContentWidth?: boolean;\n  arrow?: boolean;\n  arrowPadding?: number;\n  container?: HTMLElement | undefined;\n  /** Surface elevation level for the popup bg (1-8). Bump when nesting inside a Dialog or other elevated container. Defaults to 3. */\n  level?: SurfaceLevel;\n  /** Shadow weight (1-8). Pinned to 3 by default so the popover reads the same regardless of nesting depth. */\n  shadowLevel?: SurfaceLevel;\n}) {\n  return (\n    <PopoverPortal container={container}>\n      <BasePopover.Positioner\n        data-slot=\"popover-positioner\"\n        side={side}\n        align={align}\n        sideOffset={sideOffset}\n        alignOffset={alignOffset}\n        collisionBoundary={collisionBoundary}\n        collisionPadding={collisionPadding}\n        sticky={sticky}\n        positionMethod={positionMethod}\n        anchor={anchor}\n        arrowPadding={arrowPadding}\n        ref={followContentWidth ? followPopupWidth : undefined}\n        className=\"z-50 h-(--positioner-height) max-h-(--available-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none motion-reduce:transition-none\"\n      >\n        <BasePopover.Popup\n          data-slot=\"popover-content\"\n          data-level={level}\n          className={cn(\n            // Base styles\n            \"text-popover-foreground relative outline-none\",\n            \"h-(--popup-height,auto) w-(--popup-width,auto)\",\n            \"max-h-(--available-height) max-w-(--available-width)\",\n            \"origin-(--transform-origin) overflow-hidden rounded-xl\",\n            // Surface elevation — bg tracks `level`, shadow weight tracks `shadowLevel`\n            solidSurface(level, shadowLevel),\n            // Size/opacity transitions\n            \"transition-[width,height,scale,opacity] duration-[150ms,150ms,100ms,100ms] ease-[cubic-bezier(0.22,1,0.36,1),cubic-bezier(0.22,1,0.36,1),var(--ease-out-expo),var(--ease-out-expo)]\",\n            \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n            \"data-ending-style:scale-95 data-ending-style:opacity-0\",\n            // Chrome won't composite the scale while width/height transition,\n            // so text re-rasters and shifts as the entrance ends. Promoting\n            // the layer rasters it once.\n            \"will-change-transform\",\n            \"motion-reduce:will-change-auto\",\n            // Only the \"already open, something changed\" instant. Base UI waits on\n            // this transition before unmounting, so suppressing a close means no\n            // exit at all, and 'click' is inferred from `event.detail === 0`,\n            // which every right-click matches.\n            \"data-[instant=trigger-change]:transition-none\",\n            \"motion-reduce:transition-none\",\n            followContentWidth && \"w-max\",\n            className,\n          )}\n          {...props}\n        >\n          {arrow && (\n            <PopoverArrow className=\"transition-[left,right,top,bottom] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180 motion-reduce:transition-none\">\n              <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\">\n                <path\n                  d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V9H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n                  className=\"fill-(--popup-surface,var(--popover))\"\n                />\n                <path\n                  d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n                  className=\"fill-border/70\"\n                />\n              </svg>\n            </PopoverArrow>\n          )}\n          <BasePopover.Viewport\n            data-slot=\"popover-viewport\"\n            className={cn(\n              // `h-full` and the cap do different jobs; both are load-bearing. At\n              // rest --popup-height is `auto`, so the percentage is indefinite and\n              // only the cap bounds the viewport, which is what engages the\n              // overflow-y below. Mid-swap it is a definite px that transitions,\n              // and the percentage tracks it. Without that the viewport snaps to\n              // the incoming content's height on frame one, because Base UI takes\n              // the outgoing content out of flow with position:absolute, and\n              // overflow-clip cuts its extra rows instead of letting them fade.\n              // No `max-w`: `w-full` resolves against the popup's already-capped\n              // content box, so the width bound is inherited for free.\n              \"relative h-full max-h-(--available-height) w-full overflow-clip px-3 py-3 [--viewport-padding:0.75rem]\",\n              \"not-data-transitioning:overflow-y-auto\",\n              // Content width calculation (edge-to-edge minus padding)\n              \"**:data-current:w-[calc(var(--popup-width)-2*var(--viewport-padding))]\",\n              \"**:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-padding))]\",\n              // Non-directional crossfade, mirroring TransitionPanel's `fade`\n              // mode: the two halves dissolve in place rather than sliding past\n              // each other, so nothing implies travel and the popup's own\n              // width/height morph carries the movement on its own.\n              \"**:data-current:scale-100 **:data-current:opacity-100\",\n              \"**:data-previous:scale-100 **:data-previous:opacity-100\",\n              \"**:data-current:transition-[scale,opacity] **:data-current:duration-150 **:data-current:ease-[cubic-bezier(0.22,1,0.36,1)]\",\n              \"**:data-previous:transition-[scale,opacity] **:data-previous:duration-150 **:data-previous:ease-[cubic-bezier(0.22,1,0.36,1)]\",\n              // Both directions recede to 0.96, so the swap reads the same\n              // whichever trigger you came from.\n              \"**:data-current:data-starting-style:scale-[0.96] **:data-current:data-starting-style:opacity-0\",\n              \"**:data-previous:data-ending-style:scale-[0.96] **:data-previous:data-ending-style:opacity-0\",\n              // Value-matched to the popup's guard above so one policy governs the\n              // subtree. Targets the content: the viewport has no transitions of\n              // its own and transition-property does not inherit.\n              \"[[data-instant=trigger-change]_&_[data-current]]:transition-none [[data-instant=trigger-change]_&_[data-previous]]:transition-none\",\n              \"motion-reduce:**:data-current:transition-none motion-reduce:**:data-previous:transition-none\",\n              viewportClassName,\n            )}\n          >\n            {children}\n          </BasePopover.Viewport>\n        </BasePopover.Popup>\n      </BasePopover.Positioner>\n    </PopoverPortal>\n  );\n}\n\nconst createPopoverHandle = BasePopover.createHandle;\n\nexport {\n  Popover,\n  PopoverTrigger,\n  PopoverTitle,\n  PopoverDescription,\n  PopoverContent,\n  PopoverClose,\n  PopoverArrow,\n  PopoverPositioner,\n  PopoverPortal,\n  PopoverPopup,\n  PopoverBackdrop,\n  PopoverViewport,\n  createPopoverHandle,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/popover.tsx"
    }
  ],
  "type": "registry:ui"
}