{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "popover-placement",
  "title": "Placement popover",
  "description": "Styled popover: the Placement set. 5 decorative popover variations (TopPopover, BottomPopover, LeftPopover, RightPopover, CornerPopover) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/popover-placement.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Move } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Popover placement family: 5 direction-focused anchored panels. The same mechanism\n   as the essentials popover (a relative inline-flex wrapper + the trigger toggling\n   on CLICK, closing on an outside click + Escape, NO radix or portal, data-slot\n   preserved) but the panel is positioned in DIFFERENT DIRECTIONS relative to the\n   trigger: top, bottom, left, right and corner. Each panel enters with a slight\n   slide from that direction (motion x/y). In the horizontal placements the vertical\n   centering is done with my-auto rather than a transform; that way the centering\n   survives even when only a fade remains under reduced motion. Color comes ONLY\n   from tokens, via alpha color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst panelSize: Record<StyledSize, string> = {\n  sm: \"w-56\",\n  md: \"w-64\",\n  lg: \"w-72\",\n  xl: \"w-80\",\n}\n\ninterface PopoverProps {\n  size?: StyledSize\n  trigger?: React.ReactNode\n  children?: React.ReactNode\n  className?: string\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (o: boolean) => void\n}\n\nconst panelBase =\n  \"absolute z-50 rounded-xl border border-border bg-popover p-4 text-sm text-popover-foreground shadow-lg outline-none\"\n\nconst triggerBtn =\n  \"inline-flex h-9 shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border border-border bg-secondary px-4 text-sm font-medium text-secondary-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\n/* Controlled/uncontrolled acik durum yonetimi. */\nfunction usePopoverState(props: PopoverProps) {\n  const { open, defaultOpen, onOpenChange } = props\n  const isControlled = open !== undefined\n  const [internal, setInternal] = React.useState(defaultOpen ?? false)\n  const isOpen = isControlled ? open : internal\n\n  const setOpen = React.useCallback(\n    (next: boolean) => {\n      if (!isControlled) setInternal(next)\n      onOpenChange?.(next)\n    },\n    [isControlled, onOpenChange]\n  )\n\n  return { isOpen, setOpen }\n}\n\ninterface PanelMotion {\n  initial: Record<string, number | string>\n  animate: Record<string, number | string>\n  exit: Record<string, number | string>\n  transition?: Record<string, unknown>\n}\n\n/* Shared shell: a relative wrapper plus trigger plus an AnimatePresence panel. panelClassName carries the placement direction (position plus origin), panelMotion the entry and exit from that direction. */\nfunction PopoverShell({\n  props,\n  panelClassName,\n  panelMotion,\n  children,\n}: {\n  props: PopoverProps\n  panelClassName?: string\n  panelMotion: PanelMotion\n  children: React.ReactNode\n}) {\n  const { size = \"md\", trigger, className } = props\n  const { isOpen, setOpen } = usePopoverState(props)\n  const reduce = useReducedMotion()\n  const wrapperRef = React.useRef<HTMLDivElement>(null)\n\n  React.useEffect(() => {\n    if (!isOpen) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") setOpen(false)\n    }\n    const onPointer = (e: PointerEvent) => {\n      const node = wrapperRef.current\n      if (node && e.target instanceof Node && !node.contains(e.target)) {\n        setOpen(false)\n      }\n    }\n    window.addEventListener(\"keydown\", onKey)\n    window.addEventListener(\"pointerdown\", onPointer)\n    return () => {\n      window.removeEventListener(\"keydown\", onKey)\n      window.removeEventListener(\"pointerdown\", onPointer)\n    }\n  }, [isOpen, setOpen])\n\n  const fade = { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n  const motionProps = reduce ? fade : panelMotion\n\n  return (\n    <div ref={wrapperRef} data-slot=\"styled-popover\" className=\"relative inline-flex\">\n      <span\n        data-slot=\"styled-popover-trigger\"\n        className=\"inline-flex\"\n        onClick={() => setOpen(!isOpen)}\n      >\n        {trigger ?? (\n          <button type=\"button\" aria-haspopup=\"dialog\" aria-expanded={isOpen} className={triggerBtn}>\n            Open\n          </button>\n        )}\n      </span>\n\n      <AnimatePresence>\n        {isOpen ? (\n          <motion.div\n            role=\"dialog\"\n            className={cn(panelBase, panelSize[size], panelClassName, className)}\n            initial={motionProps.initial}\n            animate={motionProps.animate}\n            exit={motionProps.exit}\n            transition={panelMotion.transition ?? { type: \"spring\", stiffness: 340, damping: 26 }}\n          >\n            {children}\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\n/* Yon basligi: kucuk lucide oku + etiket, ardindan govde (children). */\nfunction Placement({ icon, label, children }: { icon: React.ReactNode; label: string; children: React.ReactNode }) {\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <div className=\"flex items-center gap-2 text-xs font-medium text-muted-foreground [&_svg]:size-4 [&_i]:text-base [&_i]:leading-none\">\n        {icon}\n        {label}\n      </div>\n      <div className=\"text-popover-foreground\">{children ?? \"A placement popover anchored to the trigger.\"}</div>\n    </div>\n  )\n}\n\n/* Top: positioned ABOVE the trigger, entering with an upward slide. */\nexport function TopPopover(props: PopoverProps) {\n  return (\n    <PopoverShell\n      props={props}\n      panelClassName=\"bottom-full left-0 mb-2 origin-bottom\"\n      panelMotion={{\n        initial: { opacity: 0, y: 6, scale: 0.96 },\n        animate: { opacity: 1, y: 0, scale: 1 },\n        exit: { opacity: 0, y: 6, scale: 0.96 },\n      }}\n    >\n      <Placement icon={<ArrowUp />} label=\"Top\">\n        {props.children}\n      </Placement>\n    </PopoverShell>\n  )\n}\n\n/* Bottom: positioned BELOW the trigger, entering with a downward slide. */\nexport function BottomPopover(props: PopoverProps) {\n  return (\n    <PopoverShell\n      props={props}\n      panelClassName=\"top-full left-0 mt-2 origin-top\"\n      panelMotion={{\n        initial: { opacity: 0, y: -6, scale: 0.96 },\n        animate: { opacity: 1, y: 0, scale: 1 },\n        exit: { opacity: 0, y: -6, scale: 0.96 },\n      }}\n    >\n      <Placement icon={<ArrowDown />} label=\"Bottom\">\n        {props.children}\n      </Placement>\n    </PopoverShell>\n  )\n}\n\n/* Left: positioned to the LEFT of the trigger, vertically centred with my-auto (no transform), entering with a right-to-left slide. */\nexport function LeftPopover(props: PopoverProps) {\n  return (\n    <PopoverShell\n      props={props}\n      panelClassName=\"right-full top-0 bottom-0 my-auto h-fit mr-2 origin-right\"\n      panelMotion={{\n        initial: { opacity: 0, x: 6, scale: 0.96 },\n        animate: { opacity: 1, x: 0, scale: 1 },\n        exit: { opacity: 0, x: 6, scale: 0.96 },\n      }}\n    >\n      <Placement icon={<ArrowLeft />} label=\"Left\">\n        {props.children}\n      </Placement>\n    </PopoverShell>\n  )\n}\n\n/* Right: positioned to the RIGHT of the trigger, vertically centred with my-auto (no transform), entering with a left-to-right slide. */\nexport function RightPopover(props: PopoverProps) {\n  return (\n    <PopoverShell\n      props={props}\n      panelClassName=\"left-full top-0 bottom-0 my-auto h-fit ml-2 origin-left\"\n      panelMotion={{\n        initial: { opacity: 0, x: -6, scale: 0.96 },\n        animate: { opacity: 1, x: 0, scale: 1 },\n        exit: { opacity: 0, x: -6, scale: 0.96 },\n      }}\n    >\n      <Placement icon={<ArrowRight />} label=\"Right\">\n        {props.children}\n      </Placement>\n    </PopoverShell>\n  )\n}\n\n/* Corner: corner or diagonal - positioned at the TOP RIGHT of the trigger, entering diagonally from the bottom left. */\nexport function CornerPopover(props: PopoverProps) {\n  return (\n    <PopoverShell\n      props={props}\n      panelClassName=\"bottom-full left-full mb-2 ml-2 origin-bottom-left\"\n      panelMotion={{\n        initial: { opacity: 0, x: -6, y: 6, scale: 0.96 },\n        animate: { opacity: 1, x: 0, y: 0, scale: 1 },\n        exit: { opacity: 0, x: -6, y: 6, scale: 0.96 },\n      }}\n    >\n      <Placement icon={<Move />} label=\"Corner\">\n        {props.children}\n      </Placement>\n    </PopoverShell>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/popover-placement.tsx"
    }
  ],
  "categories": [
    "styled",
    "popover"
  ],
  "type": "registry:component"
}