{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "popover",
  "type": "registry:ui",
  "title": "Popover",
  "description": "An accessible React popover for Next.js and Tailwind CSS: forms, settings and other rich content anchored to a button, built on Radix Popover.",
  "author": "FewTone <https://fewtone.com>",
  "dependencies": [
    "motion@^14.0.0",
    "radix-ui@^1.7.0"
  ],
  "registryDependencies": [
    "https://ui.fewtone.com/r/tokens.json",
    "https://ui.fewtone.com/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/fewtone/ui/popover.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { duration, ease } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\nimport { type AnimationPlaybackControls, animate, useReducedMotion } from \"motion/react\";\nimport { Popover as PopoverPrimitive } from \"radix-ui\";\nimport { type ComponentProps, useLayoutEffect, useRef } from \"react\";\n\n/*\n * Popover, built on Radix Popover: rich content anchored to a trigger, in a non-modal dialog. Focus moves\n * into it on open and back to the trigger on close; Escape and a click outside close it.\n * - The surface is Select's list: a bordered popover-coloured card with a soft shadow. It opens out of its\n *   trigger and closes back into it through the blur (the popover-in/out keyframes, scaling from Radix's\n *   transform origin), so it always grows from the side it's attached to.\n * - When what's inside changes size (more fields, a message, the next step), the popover glides to its new\n *   height instead of jumping: the height pattern in motion.md, an eased tween, clipped only while it\n *   moves. Instant with reduced motion; nothing moves on first paint.\n * - PopoverTitle names the dialog and PopoverDescription describes it (Radix links them).\n */\n\nfunction Popover(props: ComponentProps<typeof PopoverPrimitive.Root>) {\n  return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger(props: ComponentProps<typeof PopoverPrimitive.Trigger>) {\n  return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\n/** Positions the popover against another element than its trigger, for example a whole field */\nfunction PopoverAnchor(props: ComponentProps<typeof PopoverPrimitive.Anchor>) {\n  return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />;\n}\n\nfunction PopoverClose(props: ComponentProps<typeof PopoverPrimitive.Close>) {\n  return <PopoverPrimitive.Close data-slot=\"popover-close\" {...props} />;\n}\n\n/**\n * Glides the element to its new height whenever its content changes size. Only height moves, on the\n * height pattern's tween (a spring's overshoot would bounce); the content is clipped only while it moves.\n */\nfunction useGlidingHeight(ref: { current: HTMLElement | null }) {\n  const reduce = useReducedMotion() ?? false;\n\n  useLayoutEffect(() => {\n    const node = ref.current;\n    if (!node || reduce) return;\n    // Layout height: the opening scale is a transform, so it doesn't count\n    let height = node.offsetHeight;\n    let glide: AnimationPlaybackControls | undefined;\n    const release = () => {\n      node.style.height = \"\";\n      node.style.overflow = \"\";\n    };\n    const observer = new ResizeObserver(() => {\n      // While it glides the height is ours; a change underneath shows up once it lets go, and glides again\n      if (glide) return;\n      const next = node.offsetHeight;\n      if (next === height) return;\n      const from = height;\n      height = next;\n      // ResizeObserver runs after layout and before paint, so the new size is never shown before the glide\n      node.style.height = `${from}px`;\n      node.style.overflow = \"hidden\";\n      glide = animate(\n        node,\n        { height: [from, next] },\n        {\n          duration: duration.slow,\n          ease: ease.out,\n          onComplete: () => {\n            glide = undefined;\n            release();\n          },\n        },\n      );\n    });\n    observer.observe(node);\n    return () => {\n      observer.disconnect();\n      glide?.stop();\n      release();\n    };\n  }, [ref, reduce]);\n}\n\n/** The content itself. It mounts with each opening, so the height is measured fresh every time. */\nfunction PopoverSurface({\n  className,\n  align = \"center\",\n  sideOffset = 4,\n  ref,\n  ...props\n}: ComponentProps<typeof PopoverPrimitive.Content>) {\n  const node = useRef<HTMLDivElement>(null);\n  useGlidingHeight(node);\n\n  return (\n    <PopoverPrimitive.Content\n      data-slot=\"popover-content\"\n      ref={(element) => {\n        node.current = element;\n        if (typeof ref === \"function\") ref(element);\n        else if (ref) ref.current = element;\n      }}\n      align={align}\n      sideOffset={sideOffset}\n      className={cn(\n        \"z-(--z-popover) w-72 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-md outline-hidden\",\n        \"origin-(--radix-popover-content-transform-origin) data-[state=open]:animate-popover-in data-[state=closed]:animate-popover-out\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverContent(props: ComponentProps<typeof PopoverPrimitive.Content>) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverSurface {...props} />\n    </PopoverPrimitive.Portal>\n  );\n}\n\nfunction PopoverHeader({ className, ...props }: ComponentProps<\"div\">) {\n  return <div data-slot=\"popover-header\" className={cn(\"grid gap-1\", className)} {...props} />;\n}\n\n/** Names the popover for screen readers */\nfunction PopoverTitle({ className, ...props }: ComponentProps<typeof PopoverPrimitive.Title>) {\n  return (\n    <PopoverPrimitive.Title\n      data-slot=\"popover-title\"\n      className={cn(\"text-sm font-medium\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverDescription({ className, ...props }: ComponentProps<typeof PopoverPrimitive.Description>) {\n  return (\n    <PopoverPrimitive.Description\n      data-slot=\"popover-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Popover,\n  PopoverAnchor,\n  PopoverClose,\n  PopoverContent,\n  PopoverDescription,\n  PopoverHeader,\n  PopoverTitle,\n  PopoverTrigger,\n};\n"
    }
  ],
  "categories": [
    "overlays"
  ],
  "docs": "Usage and examples: https://ui.fewtone.com/components/popover"
}
