{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "popover",
  "type": "registry:ui",
  "title": "Popover",
  "description": "Grows out of its trigger along a seamless arrow, flips at edges, opens on hover.",
  "categories": [
    "dialogs"
  ],
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://www.stealth.pm/r/tokens.json",
    "https://www.stealth.pm/r/cn.json",
    "https://www.stealth.pm/r/icons.json"
  ],
  "files": [
    {
      "path": "components/ui/popover.tsx",
      "content": "\"use client\";\nimport { Popover as BasePopover } from \"@base-ui/react/popover\";\nimport { cn } from \"@/lib/cn\";\nimport { X } from \"@/lib/icons\";\n\ntype Side = \"top\" | \"bottom\" | \"left\" | \"right\";\ntype Align = \"start\" | \"center\" | \"end\";\n\nexport type PopoverProps = BasePopover.Root.Props;\n\n/** Owns the open state. Uncontrolled with `defaultOpen`, controlled with `open` + `onOpenChange`. */\nexport function Popover(props: PopoverProps) {\n  return <BasePopover.Root {...props} />;\n}\n\nexport type PopoverTriggerProps = Omit<BasePopover.Trigger.Props, \"className\"> & {\n  className?: string;\n  /** \"bare\" drops the button chrome and keeps only focus and press feedback, for icon or text triggers you style yourself. */\n  variant?: \"secondary\" | \"ghost\" | \"bare\";\n  size?: \"sm\" | \"md\";\n};\n\nexport function PopoverTrigger({ variant = \"secondary\", size = \"md\", className, ...rest }: PopoverTriggerProps) {\n  return (\n    <BasePopover.Trigger\n      data-variant={variant}\n      data-size={size}\n      className={cn(\n        \"relative inline-flex shrink-0 select-none items-center justify-center font-medium tracking-[-0.005em]\",\n        \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-fg-3\",\n        \"transition-[background-color,border-color,color,scale] duration-150 active:scale-[0.97] active:duration-75\",\n        \"data-disabled:pointer-events-none data-disabled:opacity-50\",\n        variant !== \"bare\" && (size === \"sm\" ? \"h-7 gap-1.5 rounded-md px-2 text-[12px]\" : \"h-8 gap-2 rounded-lg px-2.5 text-[12.5px]\"),\n        // The trigger stays visibly pressed for as long as its popover is open.\n        variant === \"secondary\" &&\n          \"border border-line-2 bg-raised text-fg shadow-[var(--shadow)] hover:border-fg-4 hover:bg-hover data-popup-open:border-fg-4 data-popup-open:bg-hover\",\n        variant === \"ghost\" && \"text-fg-2 hover:bg-hover hover:text-fg data-popup-open:bg-hover data-popup-open:text-fg\",\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n\nconst widths = { auto: \"w-max\", sm: \"w-64\", md: \"w-80\", lg: \"w-96\" } as const;\n\nexport type PopoverContentProps = Omit<BasePopover.Popup.Props, \"className\"> & {\n  className?: string;\n  side?: Side;\n  align?: Align;\n  /** Gap between trigger and popup in px. Defaults to 10 with the arrow, 6 without. */\n  sideOffset?: number;\n  alignOffset?: number;\n  /** Space kept from the viewport edge before the popup flips or shifts. */\n  collisionPadding?: number;\n  /** Draw the arrow that points at the trigger. */\n  arrow?: boolean;\n  /** Fixed widths keep forms from reflowing as you type; \"auto\" fits the content. */\n  size?: keyof typeof widths;\n  /** Where to portal the popup. Defaults to document.body. */\n  container?: BasePopover.Portal.Props[\"container\"];\n};\n\nexport function PopoverContent({\n  side = \"bottom\",\n  align = \"center\",\n  sideOffset,\n  alignOffset = 0,\n  collisionPadding = 8,\n  arrow = true,\n  size = \"md\",\n  container,\n  className,\n  children,\n  ...rest\n}: PopoverContentProps) {\n  return (\n    <BasePopover.Portal container={container}>\n      <BasePopover.Positioner\n        side={side}\n        align={align}\n        sideOffset={sideOffset ?? (arrow ? 10 : 6)}\n        alignOffset={alignOffset}\n        collisionPadding={collisionPadding}\n        arrowPadding={14}\n        className=\"z-(--z-popover)\"\n      >\n        <BasePopover.Popup\n          className={cn(\n            \"relative flex max-w-[var(--available-width)] flex-col gap-3 rounded-xl border border-line-2 bg-raised p-4 text-[13px] text-fg shadow-pop outline-none\",\n            widths[size],\n            // Grows out of the trigger: the origin is the arrow tip, and it travels 4px away from the trigger as it scales up.\n            \"origin-[var(--transform-origin)] transition-[opacity,scale,translate,filter] duration-200 ease-out-expo\",\n            \"data-starting-style:scale-[0.96] data-starting-style:opacity-0 data-starting-style:blur-[2px]\",\n            \"data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=top]:data-starting-style:translate-y-1\",\n            \"data-[side=left]:data-starting-style:translate-x-1 data-[side=right]:data-starting-style:-translate-x-1\",\n            // Leaving is quicker and simpler: a short fade with a hint of scale, no travel.\n            \"data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-ending-style:duration-150 data-ending-style:ease-out-quart\",\n            // Escape and other keyboard dismissals close on the same frame.\n            \"data-instant:transition-none\",\n            \"motion-reduce:data-starting-style:translate-0 motion-reduce:data-starting-style:scale-100 motion-reduce:data-starting-style:blur-none motion-reduce:data-ending-style:scale-100\",\n            className,\n          )}\n          {...rest}\n        >\n          {arrow && <PopoverArrow />}\n          {children}\n        </BasePopover.Popup>\n      </BasePopover.Positioner>\n    </BasePopover.Portal>\n  );\n}\n\n// One curved arrow drawn as fill plus stroke, so it joins the popup's hairline border\n// without a seam. Its base overlaps the border by 1px to hide the line underneath.\nfunction PopoverArrow() {\n  return (\n    <BasePopover.Arrow\n      className={cn(\n        \"pointer-events-none flex h-2.5 w-5\",\n        \"data-[side=bottom]:-top-[9px]\",\n        \"data-[side=top]:-bottom-[9px] data-[side=top]:rotate-180\",\n        \"data-[side=left]:-right-3.5 data-[side=left]:rotate-90\",\n        \"data-[side=right]:-left-3.5 data-[side=right]:-rotate-90\",\n      )}\n    >\n      <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" aria-hidden className=\"block overflow-visible\">\n        <path d=\"M0 10c3.6 0 5.7-1.4 7.4-5.2L8.2 3c.8-1.6 2.8-1.6 3.6 0l.8 1.8c1.7 3.8 3.8 5.2 7.4 5.2z\" className=\"fill-raised\" />\n        <path d=\"M0 10c3.6 0 5.7-1.4 7.4-5.2L8.2 3c.8-1.6 2.8-1.6 3.6 0l.8 1.8c1.7 3.8 3.8 5.2 7.4 5.2\" className=\"stroke-line-2\" vectorEffect=\"non-scaling-stroke\" />\n      </svg>\n    </BasePopover.Arrow>\n  );\n}\n\nexport type PopoverHeaderProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  title: React.ReactNode;\n  description?: React.ReactNode;\n  /** Show the close button. On by default so touch and screen reader users always have a way out. */\n  closable?: boolean;\n  closeLabel?: string;\n};\n\nexport function PopoverHeader({ title, description, closable = true, closeLabel = \"Close\", className, children, ...rest }: PopoverHeaderProps) {\n  return (\n    <div className={cn(\"flex items-start gap-3\", className)} {...rest}>\n      <div className=\"min-w-0 flex-1\">\n        <PopoverTitle>{title}</PopoverTitle>\n        {description && <PopoverDescription className=\"mt-0.5\">{description}</PopoverDescription>}\n        {children}\n      </div>\n      {closable && <PopoverClose label={closeLabel} className=\"-mr-1.5 -mt-1\" />}\n    </div>\n  );\n}\n\nexport type PopoverTitleProps = Omit<BasePopover.Title.Props, \"className\"> & { className?: string };\n\nexport function PopoverTitle({ className, ...rest }: PopoverTitleProps) {\n  return <BasePopover.Title className={cn(\"text-[14px] font-medium leading-5 tracking-[-0.015em] text-fg [overflow-wrap:anywhere]\", className)} {...rest} />;\n}\n\nexport type PopoverDescriptionProps = Omit<BasePopover.Description.Props, \"className\"> & { className?: string };\n\nexport function PopoverDescription({ className, ...rest }: PopoverDescriptionProps) {\n  return <BasePopover.Description className={cn(\"text-[12.5px] leading-[18px] text-fg-2 text-pretty\", className)} {...rest} />;\n}\n\nexport type PopoverCloseProps = Omit<BasePopover.Close.Props, \"className\"> & {\n  className?: string;\n  /** Accessible name of the icon-only close button. */\n  label?: string;\n};\n\n/** Icon-only close. Pass children to render your own button content instead of the X. */\nexport function PopoverClose({ label = \"Close\", className, children, ...rest }: PopoverCloseProps) {\n  const iconOnly = children === undefined;\n  return (\n    <BasePopover.Close\n      aria-label={iconOnly ? label : undefined}\n      className={cn(\n        \"relative inline-flex shrink-0 items-center justify-center rounded-md text-fg-3 outline-none\",\n        \"transition-[background-color,color,scale] duration-150 hover:bg-hover hover:text-fg active:scale-[0.92] active:duration-75\",\n        \"focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-fg-3\",\n        iconOnly && \"size-7 before:absolute before:-inset-2 before:content-[''] pointer-fine:before:hidden\",\n        className,\n      )}\n      {...rest}\n    >\n      {iconOnly ? <X size={14} /> : children}\n    </BasePopover.Close>\n  );\n}\n\nexport type PopoverFooterProps = React.ComponentProps<\"div\">;\n\n/** A hairline-separated strip along the bottom edge, for secondary actions and link settings. */\nexport function PopoverFooter({ className, ...rest }: PopoverFooterProps) {\n  return <div className={cn(\"-mx-4 -mb-4 mt-1 flex items-center gap-2 rounded-b-xl border-t border-line px-4 py-2.5\", className)} {...rest} />;\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://www.stealth.pm/ui/popover"
}
