{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "popover",
  "title": "Popover",
  "description": "Displays content in a portal relative to the triggering button element.",
  "dependencies": [
    "react-native-reanimated",
    "react-native-safe-area-context"
  ],
  "registryDependencies": [
    "@tetra-ui/portal",
    "@tetra-ui/slot"
  ],
  "files": [
    {
      "path": "ui/popover.tsx",
      "content": "import {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  BackHandler,\n  type DimensionValue,\n  type GestureResponderEvent,\n  type LayoutChangeEvent,\n  type LayoutRectangle,\n  Pressable,\n  useWindowDimensions,\n  type View,\n  type ViewStyle,\n} from \"react-native\";\nimport Animated, {\n  Easing,\n  Extrapolation,\n  interpolate,\n  type SharedValue,\n  useAnimatedStyle,\n  useSharedValue,\n  withTiming,\n} from \"react-native-reanimated\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { useUniwind } from \"uniwind\";\nimport { useRelativePosition } from \"@/hooks/use-relative-position\";\nimport { cn, mergeRefs } from \"@/lib/utils\";\nimport { Portal, PortalOverlay } from \"./portal\";\nimport { Slot } from \"./slot\";\n\n// Constants\nconst ANIMATION_DURATION = 200;\nconst ANIMATION_EASING = Easing.out(Easing.cubic);\nconst SCREEN_MARGIN = 12;\nconst FIT_MAX_WIDTH = 280;\nconst OVERLAY_OPACITY = { dark: 0.32, light: 0.12 } as const;\nconst AnimatedPressable = Animated.createAnimatedComponent(Pressable);\n\n// Types\ntype LayoutPosition = {\n  pageX: number;\n  pageY: number;\n  width: number;\n  height: number;\n};\n\ntype PopoverSide = \"top\" | \"bottom\" | \"left\" | \"right\";\ntype PopoverAlign = \"start\" | \"center\" | \"end\";\n\ntype PopoverContextProps = {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  visibilityProgress: SharedValue<number>;\n  triggerPosition?: LayoutPosition;\n  setTriggerPosition: (position?: LayoutPosition) => void;\n  contentLayout?: LayoutRectangle;\n  setContentLayout: (position?: LayoutRectangle) => void;\n};\n\ntype PopoverProps = Partial<PopoverContextProps> & {\n  children: React.ReactNode;\n};\n\ntype PopoverPortalProps = Partial<React.ComponentProps<typeof Portal>>;\n\ntype PopoverOverlayProps = {\n  closeOnPress?: boolean;\n  className?: string;\n};\n\ntype PopoverContentProps = React.ComponentProps<typeof View> & {\n  avoidCollisions?: boolean;\n  side?: PopoverSide;\n  sideOffset?: number;\n  align?: PopoverAlign;\n  alignOffset?: number;\n  width?: \"full\" | \"fit\" | \"auto\" | \"trigger\" | number | `${number}%`;\n};\n\ntype PopoverTriggerProps = React.ComponentPropsWithRef<typeof Pressable> & {\n  asChild?: boolean;\n};\n\ntype PopoverCloseProps = React.ComponentPropsWithRef<typeof Pressable> & {\n  asChild?: boolean;\n};\n\n// Context\nconst PopoverContext = createContext<PopoverContextProps | null>(null);\n\nexport const usePopover = () => {\n  const context = useContext(PopoverContext);\n  if (!context) {\n    throw new Error(\"usePopover must be used within a Popover\");\n  }\n  return context;\n};\n\n// Components\nexport const Popover = ({\n  open: openProp,\n  onOpenChange: onOpenChangeProp,\n  children,\n}: PopoverProps) => {\n  const [internalOpen, setInternalOpen] = useState(openProp ?? false);\n  const [contentLayout, setContentLayout] = useState<LayoutRectangle>();\n  const [triggerPosition, setTriggerPosition] = useState<LayoutPosition>();\n\n  const isControlled = openProp !== undefined;\n  const open = isControlled ? openProp : internalOpen;\n\n  const visibilityProgress = useSharedValue(open ? 1 : 0);\n\n  const onOpenChange = useCallback(\n    (nextOpen: boolean) => {\n      setInternalOpen(nextOpen);\n      onOpenChangeProp?.(nextOpen);\n    },\n    [onOpenChangeProp]\n  );\n\n  useEffect(() => {\n    visibilityProgress.value = withTiming(open ? 1 : 0, {\n      duration: ANIMATION_DURATION,\n      easing: ANIMATION_EASING,\n    });\n  }, [open, visibilityProgress]);\n\n  useEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    const subscription = BackHandler.addEventListener(\n      \"hardwareBackPress\",\n      () => {\n        onOpenChange(false);\n        return true;\n      }\n    );\n\n    return () => {\n      subscription.remove();\n    };\n  }, [open, onOpenChange]);\n\n  const ctx = useMemo(\n    () => ({\n      contentLayout,\n      onOpenChange,\n      open,\n      setContentLayout,\n      setTriggerPosition,\n      triggerPosition,\n      visibilityProgress,\n    }),\n    [open, triggerPosition, contentLayout, visibilityProgress, onOpenChange]\n  );\n\n  return (\n    <PopoverContext.Provider value={ctx}>{children}</PopoverContext.Provider>\n  );\n};\n\nexport const PopoverTrigger = ({\n  asChild,\n  ref: refProp,\n  onPress: onPressProp,\n  ...props\n}: PopoverTriggerProps) => {\n  const { open, onOpenChange, setTriggerPosition } = usePopover();\n  const ref = useRef<React.ComponentRef<typeof Pressable>>(null);\n\n  const mergedRefs = mergeRefs(ref, refProp);\n\n  const handlePress = useCallback(\n    (e: GestureResponderEvent) => {\n      onPressProp?.(e);\n\n      if (open) {\n        onOpenChange(false);\n        return;\n      }\n\n      ref.current?.measure((_x, _y, width, height, pageX, pageY) => {\n        setTriggerPosition({\n          height,\n          pageX,\n          pageY,\n          width,\n        });\n\n        onOpenChange(true);\n      });\n    },\n    [open, onOpenChange, onPressProp, setTriggerPosition]\n  );\n\n  const Comp = asChild ? Slot.Pressable : Pressable;\n\n  return (\n    <Comp\n      {...props}\n      collapsable={false}\n      onPress={handlePress}\n      ref={mergedRefs}\n    />\n  );\n};\n\nexport const PopoverClose = ({ asChild, ...props }: PopoverCloseProps) => {\n  const { onOpenChange } = usePopover();\n\n  const Comp = asChild ? Slot.Pressable : Pressable;\n\n  return (\n    <Comp\n      {...props}\n      onPress={() => {\n        onOpenChange(false);\n      }}\n    />\n  );\n};\n\nexport const PopoverPortal = ({\n  children,\n  name = \"popover-portal\",\n  ...portalProps\n}: PopoverPortalProps) => {\n  const ctx = usePopover();\n  const [mounted, setMounted] = useState(ctx.open);\n\n  useEffect(() => {\n    if (ctx.open) {\n      setMounted(true);\n      return;\n    }\n\n    const timeout = setTimeout(() => {\n      setMounted(false);\n    }, ANIMATION_DURATION);\n\n    return () => {\n      clearTimeout(timeout);\n    };\n  }, [ctx.open]);\n\n  if (!mounted) {\n    return null;\n  }\n\n  return (\n    <Portal name={name} {...portalProps}>\n      <PopoverContext.Provider value={ctx}>\n        <PortalOverlay>{children}</PortalOverlay>\n      </PopoverContext.Provider>\n    </Portal>\n  );\n};\n\nexport const PopoverOverlay = ({\n  closeOnPress = true,\n  className,\n}: PopoverOverlayProps) => {\n  const { onOpenChange, visibilityProgress } = usePopover();\n  const { theme } = useUniwind();\n  const overlayOpacity =\n    theme === \"dark\" ? OVERLAY_OPACITY.dark : OVERLAY_OPACITY.light;\n\n  const animatedStyle = useAnimatedStyle(() => {\n    return {\n      opacity: interpolate(\n        visibilityProgress.value,\n        [0, 1],\n        [0, overlayOpacity],\n        Extrapolation.CLAMP\n      ),\n    };\n  });\n\n  return (\n    <AnimatedPressable\n      className={cn(\"absolute inset-0 bg-black\", className)}\n      disabled={!closeOnPress}\n      onPress={() => onOpenChange(false)}\n      style={animatedStyle}\n    />\n  );\n};\n\nexport const PopoverContent = ({\n  children,\n  className,\n  onLayout: onLayoutProp,\n  style,\n  width = \"fit\",\n  avoidCollisions = true,\n  side = \"bottom\",\n  sideOffset = 8,\n  align = \"center\",\n  alignOffset = 0,\n  ...props\n}: PopoverContentProps) => {\n  const {\n    visibilityProgress,\n    triggerPosition,\n    setContentLayout,\n    contentLayout,\n  } = usePopover();\n  const insets = useSafeAreaInsets();\n  const dimensions = useWindowDimensions();\n\n  const positionInsets = useMemo(\n    () => ({\n      bottom: insets.bottom + SCREEN_MARGIN,\n      left: insets.left + SCREEN_MARGIN,\n      right: insets.right + SCREEN_MARGIN,\n      top: insets.top + SCREEN_MARGIN,\n    }),\n    [insets]\n  );\n\n  const onLayout = useCallback(\n    (event: LayoutChangeEvent) => {\n      setContentLayout(event.nativeEvent.layout);\n      onLayoutProp?.(event);\n    },\n    [setContentLayout, onLayoutProp]\n  );\n\n  const transformOrigin = getContentTransformOrigin(side, align);\n  const translateXSign = getTranslateSign(side, \"x\");\n  const translateYSign = getTranslateSign(side, \"y\");\n\n  const animatedStyle = useAnimatedStyle(() => {\n    const progress = visibilityProgress.value;\n    const opacity = interpolate(progress, [0, 1], [0, 1], Extrapolation.CLAMP);\n    const scale = interpolate(progress, [0, 1], [0.92, 1], Extrapolation.CLAMP);\n    const offset = interpolate(progress, [0, 1], [6, 0], Extrapolation.CLAMP);\n\n    return {\n      opacity,\n      transform: [\n        { translateX: offset * translateXSign },\n        { translateY: offset * translateYSign },\n        { scale },\n      ],\n      transformOrigin,\n    };\n  });\n\n  const positionStyle = useRelativePosition({\n    align,\n    alignOffset,\n    avoidCollisions,\n    contentLayout: contentLayout ?? null,\n    insets: positionInsets,\n    side,\n    sideOffset,\n    triggerPosition: triggerPosition ?? null,\n  });\n\n  const widthStyle = getContentWidthStyle(\n    width,\n    triggerPosition?.width,\n    Math.min(\n      FIT_MAX_WIDTH,\n      dimensions.width - positionInsets.left - positionInsets.right\n    )\n  );\n\n  if (!triggerPosition) {\n    return null;\n  }\n\n  return (\n    <Animated.View\n      {...props}\n      className={cn(\n        \"z-50 rounded-xl border border-border bg-popover p-3 shadow-lg\",\n        className\n      )}\n      collapsable={false}\n      onLayout={onLayout}\n      pointerEvents={contentLayout ? \"auto\" : \"none\"}\n      style={[\n        positionStyle,\n        widthStyle,\n        animatedStyle,\n        contentLayout ? null : { opacity: 0 },\n        style,\n      ]}\n    >\n      {children}\n    </Animated.View>\n  );\n};\n\n// Utils\nfunction getContentWidthStyle(\n  width: NonNullable<PopoverContentProps[\"width\"]>,\n  triggerWidth: number | undefined,\n  maxFitWidth: number\n): ViewStyle {\n  if (width === \"full\") {\n    return { width: \"100%\" };\n  }\n\n  if (width === \"trigger\") {\n    return { width: triggerWidth };\n  }\n\n  if (width === \"auto\") {\n    return { width: \"auto\" };\n  }\n\n  if (typeof width === \"number\") {\n    return { width };\n  }\n\n  if (width.endsWith(\"%\")) {\n    return { width: width as DimensionValue };\n  }\n\n  return {\n    alignSelf: \"flex-start\",\n    maxWidth: maxFitWidth,\n  };\n}\n\nfunction getContentTransformOrigin(side: PopoverSide, align: PopoverAlign) {\n  let x = \"center\";\n  if (side === \"left\") {\n    x = \"right\";\n  } else if (side === \"right\") {\n    x = \"left\";\n  } else if (align === \"start\") {\n    x = \"left\";\n  } else if (align === \"end\") {\n    x = \"right\";\n  }\n\n  let y = \"center\";\n  if (side === \"top\") {\n    y = \"bottom\";\n  } else if (side === \"bottom\") {\n    y = \"top\";\n  } else if (align === \"start\") {\n    y = \"top\";\n  } else if (align === \"end\") {\n    y = \"bottom\";\n  }\n\n  return `${x} ${y}`;\n}\n\nfunction getTranslateSign(side: PopoverSide, axis: \"x\" | \"y\") {\n  if (axis === \"x\") {\n    if (side === \"left\") {\n      return 1;\n    }\n    if (side === \"right\") {\n      return -1;\n    }\n    return 0;\n  }\n\n  if (side === \"top\") {\n    return 1;\n  }\n  if (side === \"bottom\") {\n    return -1;\n  }\n  return 0;\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "hooks/use-relative-position.ts",
      "content": "import { useMemo } from \"react\";\nimport { type LayoutRectangle, useWindowDimensions } from \"react-native\";\nimport type { EdgeInsets } from \"react-native-safe-area-context\";\n\ntype LayoutPosition = {\n  pageX: number;\n  pageY: number;\n  width: number;\n  height: number;\n};\n\ntype WindowSize = {\n  height: number;\n  width: number;\n};\n\ntype UseRelativePositionArgs = {\n  align?: \"start\" | \"center\" | \"end\";\n  avoidCollisions?: boolean;\n  contentLayout: LayoutRectangle | null;\n  alignOffset?: number;\n  insets?: EdgeInsets;\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n  sideOffset?: number;\n  triggerPosition: LayoutPosition | null;\n};\n\ntype PositionStyle = {\n  position: \"absolute\";\n  top?: number;\n  left?: number;\n  maxWidth?: number;\n  maxHeight?: number;\n  opacity?: number;\n};\n\ntype SidePositionParams = {\n  side: \"top\" | \"bottom\" | \"left\" | \"right\";\n  triggerPosition: LayoutPosition;\n  contentLayout: LayoutRectangle;\n  sideOffset: number;\n  insets?: EdgeInsets;\n  avoidCollisions: boolean;\n  dimensions: WindowSize;\n};\n\nfunction getVerticalSidePosition({\n  side,\n  triggerPosition,\n  contentLayout,\n  sideOffset,\n  insetTop,\n  insetBottom,\n  avoidCollisions,\n  dimensions,\n}: {\n  side: \"top\" | \"bottom\";\n  triggerPosition: LayoutPosition;\n  contentLayout: LayoutRectangle;\n  sideOffset: number;\n  insetTop: number;\n  insetBottom: number;\n  avoidCollisions: boolean;\n  dimensions: WindowSize;\n}): { top?: number } {\n  const positionTop = triggerPosition.pageY - sideOffset - contentLayout.height;\n  const positionBottom =\n    triggerPosition.pageY + triggerPosition.height + sideOffset;\n\n  if (!avoidCollisions) {\n    return {\n      top: side === \"top\" ? positionTop : positionBottom,\n    };\n  }\n\n  if (side === \"top\") {\n    return {\n      top: Math.min(\n        Math.max(insetTop, positionTop),\n        dimensions.height - insetBottom - contentLayout.height\n      ),\n    };\n  }\n\n  // For bottom placement, match reference implementation exactly\n  return {\n    top: Math.min(\n      dimensions.height - insetBottom - contentLayout.height,\n      positionBottom\n    ),\n  };\n}\n\nfunction getHorizontalSidePosition({\n  side,\n  triggerPosition,\n  contentLayout,\n  sideOffset,\n  insetLeft,\n  insetRight,\n  avoidCollisions,\n  dimensions,\n}: {\n  side: \"left\" | \"right\";\n  triggerPosition: LayoutPosition;\n  contentLayout: LayoutRectangle;\n  sideOffset: number;\n  insetLeft: number;\n  insetRight: number;\n  avoidCollisions: boolean;\n  dimensions: WindowSize;\n}): { left?: number } {\n  const maxContentWidth = dimensions.width - insetLeft - insetRight;\n  const contentWidth = Math.min(contentLayout.width, maxContentWidth);\n\n  const positionLeft = triggerPosition.pageX - sideOffset - contentWidth;\n  const positionRight =\n    triggerPosition.pageX + triggerPosition.width + sideOffset;\n\n  if (!avoidCollisions) {\n    return {\n      left: side === \"left\" ? positionLeft : positionRight,\n    };\n  }\n\n  const minLeft = insetLeft;\n  const maxLeft = dimensions.width - insetRight - contentWidth;\n\n  if (side === \"left\") {\n    return {\n      left: Math.max(minLeft, Math.min(maxLeft, positionLeft)),\n    };\n  }\n\n  return {\n    left: Math.max(minLeft, Math.min(maxLeft, positionRight)),\n  };\n}\n\nfunction getSidePosition({\n  side = \"bottom\",\n  triggerPosition,\n  contentLayout,\n  sideOffset = 0,\n  insets,\n  avoidCollisions = true,\n  dimensions,\n}: SidePositionParams): { top?: number; left?: number } {\n  const insetTop = insets?.top ?? 0;\n  const insetBottom = insets?.bottom ?? 0;\n  const insetLeft = insets?.left ?? 0;\n  const insetRight = insets?.right ?? 0;\n\n  // Handle vertical sides (top/bottom)\n  if (side === \"top\" || side === \"bottom\") {\n    return getVerticalSidePosition({\n      avoidCollisions,\n      contentLayout,\n      dimensions,\n      insetBottom,\n      insetTop,\n      side,\n      sideOffset,\n      triggerPosition,\n    });\n  }\n\n  // Handle horizontal sides (left/right)\n  return getHorizontalSidePosition({\n    avoidCollisions,\n    contentLayout,\n    dimensions,\n    insetLeft,\n    insetRight,\n    side,\n    sideOffset,\n    triggerPosition,\n  });\n}\n\ntype HorizontalAlignParams = {\n  align: \"start\" | \"center\" | \"end\";\n  triggerPageX: number;\n  triggerWidth: number;\n  contentWidth: number;\n  alignOffset: number;\n  insetLeft: number;\n  insetRight: number;\n  dimensions: WindowSize;\n};\n\nfunction getHorizontalAlignPosition({\n  align,\n  triggerPageX,\n  triggerWidth,\n  contentWidth,\n  alignOffset,\n  insetLeft,\n  insetRight,\n  dimensions,\n}: HorizontalAlignParams): number {\n  let left = 0;\n  if (align === \"start\") {\n    left = triggerPageX;\n  }\n  if (align === \"center\") {\n    left = triggerPageX + triggerWidth / 2 - contentWidth / 2;\n  }\n  if (align === \"end\") {\n    left = triggerPageX + triggerWidth - contentWidth;\n  }\n  return Math.max(\n    insetLeft,\n    Math.min(left + alignOffset, dimensions.width - contentWidth - insetRight)\n  );\n}\n\ntype VerticalAlignParams = {\n  align: \"start\" | \"center\" | \"end\";\n  triggerPageY: number;\n  triggerHeight: number;\n  contentHeight: number;\n  alignOffset: number;\n  insetTop: number;\n  insetBottom: number;\n  dimensions: WindowSize;\n};\n\nfunction getVerticalAlignPosition({\n  align,\n  triggerPageY,\n  triggerHeight,\n  contentHeight,\n  alignOffset,\n  insetTop,\n  insetBottom,\n  dimensions,\n}: VerticalAlignParams): number {\n  let top = 0;\n  if (align === \"start\") {\n    top = triggerPageY;\n  }\n  if (align === \"center\") {\n    top = triggerPageY + triggerHeight / 2 - contentHeight / 2;\n  }\n  if (align === \"end\") {\n    top = triggerPageY + triggerHeight - contentHeight;\n  }\n  return Math.max(\n    insetTop,\n    Math.min(top + alignOffset, dimensions.height - contentHeight - insetBottom)\n  );\n}\n\ntype AlignPositionParams = {\n  align: \"start\" | \"center\" | \"end\";\n  avoidCollisions: boolean;\n  triggerPosition: LayoutPosition;\n  contentLayout: LayoutRectangle;\n  alignOffset: number;\n  insets?: EdgeInsets;\n  side: \"top\" | \"bottom\" | \"left\" | \"right\";\n  dimensions: WindowSize;\n};\n\nfunction adjustHorizontalCollision({\n  left,\n  contentWidth,\n  insetLeft,\n  insetRight,\n  dimensions,\n}: {\n  left: number;\n  contentWidth: number;\n  insetLeft: number;\n  insetRight: number;\n  dimensions: WindowSize;\n}): number {\n  const spaceLeft = left - insetLeft;\n  const spaceRight = dimensions.width - insetRight - (left + contentWidth);\n\n  if (spaceLeft > spaceRight && spaceLeft >= contentWidth) {\n    return insetLeft;\n  }\n  if (spaceRight >= contentWidth) {\n    return dimensions.width - insetRight - contentWidth;\n  }\n  return Math.max(\n    insetLeft,\n    (dimensions.width - contentWidth - insetRight) / 2\n  );\n}\n\nfunction getHorizontalAlignWithCollision({\n  align,\n  triggerPosition,\n  contentWidth,\n  alignOffset,\n  insetLeft,\n  insetRight,\n  avoidCollisions,\n  dimensions,\n}: {\n  align: \"start\" | \"center\" | \"end\";\n  triggerPosition: LayoutPosition;\n  contentWidth: number;\n  alignOffset: number;\n  insetLeft: number;\n  insetRight: number;\n  avoidCollisions: boolean;\n  dimensions: WindowSize;\n}): number {\n  let left = getHorizontalAlignPosition({\n    align,\n    alignOffset,\n    contentWidth,\n    dimensions,\n    insetLeft,\n    insetRight,\n    triggerPageX: triggerPosition.pageX,\n    triggerWidth: triggerPosition.width,\n  });\n\n  if (avoidCollisions) {\n    const doesCollide =\n      left < insetLeft || left + contentWidth > dimensions.width - insetRight;\n    if (doesCollide) {\n      left = adjustHorizontalCollision({\n        contentWidth,\n        dimensions,\n        insetLeft,\n        insetRight,\n        left,\n      });\n    }\n  }\n\n  return left;\n}\n\nfunction adjustVerticalCollision({\n  idealTop,\n  contentHeight,\n  insetTop,\n  insetBottom,\n  dimensions,\n}: {\n  idealTop: number;\n  contentHeight: number;\n  insetTop: number;\n  insetBottom: number;\n  dimensions: WindowSize;\n}): number {\n  const spaceTop = idealTop - insetTop;\n  const spaceBottom =\n    dimensions.height - insetBottom - (idealTop + contentHeight);\n\n  if (spaceTop > spaceBottom && spaceTop >= contentHeight) {\n    return insetTop;\n  }\n  if (spaceBottom >= contentHeight) {\n    return dimensions.height - insetBottom - contentHeight;\n  }\n  return Math.max(\n    insetTop,\n    (dimensions.height - contentHeight - insetBottom) / 2\n  );\n}\n\nfunction getVerticalAlignWithCollision({\n  verticalAlign,\n  triggerPosition,\n  contentHeight,\n  alignOffset,\n  insetTop,\n  insetBottom,\n  avoidCollisions,\n  dimensions,\n}: {\n  verticalAlign: \"start\" | \"center\" | \"end\";\n  triggerPosition: LayoutPosition;\n  contentHeight: number;\n  alignOffset: number;\n  insetTop: number;\n  insetBottom: number;\n  avoidCollisions: boolean;\n  dimensions: WindowSize;\n}): number {\n  let top = getVerticalAlignPosition({\n    align: verticalAlign,\n    alignOffset,\n    contentHeight,\n    dimensions,\n    insetBottom,\n    insetTop,\n    triggerHeight: triggerPosition.height,\n    triggerPageY: triggerPosition.pageY,\n  });\n\n  if (avoidCollisions) {\n    const doesCollide =\n      top < insetTop || top + contentHeight > dimensions.height - insetBottom;\n    if (doesCollide) {\n      const triggerCenter = triggerPosition.pageY + triggerPosition.height / 2;\n      const idealTop = triggerCenter - contentHeight / 2;\n\n      if (\n        idealTop >= insetTop &&\n        idealTop + contentHeight <= dimensions.height - insetBottom\n      ) {\n        top = idealTop;\n      } else {\n        top = adjustVerticalCollision({\n          contentHeight,\n          dimensions,\n          idealTop,\n          insetBottom,\n          insetTop,\n        });\n      }\n    }\n  }\n\n  return top;\n}\n\nfunction getAlignPosition({\n  align = \"start\",\n  avoidCollisions = true,\n  triggerPosition,\n  contentLayout,\n  alignOffset = 0,\n  insets,\n  side = \"bottom\",\n  dimensions,\n}: AlignPositionParams): {\n  top?: number;\n  left?: number;\n  maxWidth?: number;\n  maxHeight?: number;\n} {\n  const insetTop = insets?.top ?? 0;\n  const insetBottom = insets?.bottom ?? 0;\n  const insetLeft = insets?.left ?? 0;\n  const insetRight = insets?.right ?? 0;\n\n  // For top/bottom sides, align horizontally\n  if (side === \"top\" || side === \"bottom\") {\n    const maxContentWidth = dimensions.width - insetLeft - insetRight;\n    const contentWidth = Math.min(contentLayout.width, maxContentWidth);\n\n    const left = getHorizontalAlignWithCollision({\n      align,\n      alignOffset,\n      avoidCollisions,\n      contentWidth,\n      dimensions,\n      insetLeft,\n      insetRight,\n      triggerPosition,\n    });\n\n    return { left, maxWidth: maxContentWidth };\n  }\n\n  // For left/right sides, align vertically and constrain width\n  // Default to \"center\" alignment for left/right sides for better UX\n  const verticalAlign =\n    (side === \"left\" || side === \"right\") && align === \"start\"\n      ? \"center\"\n      : align;\n\n  const maxContentHeight = dimensions.height - insetTop - insetBottom;\n  const maxContentWidth = dimensions.width - insetLeft - insetRight;\n  const contentHeight = Math.min(contentLayout.height, maxContentHeight);\n\n  const top = getVerticalAlignWithCollision({\n    alignOffset,\n    avoidCollisions,\n    contentHeight,\n    dimensions,\n    insetBottom,\n    insetTop,\n    triggerPosition,\n    verticalAlign,\n  });\n\n  return { maxHeight: maxContentHeight, maxWidth: maxContentWidth, top };\n}\n\nexport function useRelativePosition({\n  align = \"start\",\n  avoidCollisions = true,\n  triggerPosition,\n  contentLayout,\n  alignOffset = 0,\n  insets,\n  side = \"bottom\",\n  sideOffset = 0,\n}: UseRelativePositionArgs): PositionStyle {\n  const { height: windowHeight, width: windowWidth } = useWindowDimensions();\n\n  return useMemo(() => {\n    const dimensions = { height: windowHeight, width: windowWidth };\n    if (!triggerPosition) {\n      return {\n        left: -9999,\n        opacity: 0,\n        position: \"absolute\",\n        top: -9999,\n      };\n    }\n\n    if (!contentLayout) {\n      return {\n        left: triggerPosition.pageX,\n        opacity: 0,\n        position: \"absolute\",\n        top: triggerPosition.pageY,\n      };\n    }\n\n    const sidePosition = getSidePosition({\n      avoidCollisions,\n      contentLayout,\n      dimensions,\n      insets,\n      side,\n      sideOffset,\n      triggerPosition,\n    });\n\n    const alignPosition = getAlignPosition({\n      align,\n      alignOffset,\n      avoidCollisions,\n      contentLayout,\n      dimensions,\n      insets,\n      side,\n      triggerPosition,\n    });\n\n    return {\n      position: \"absolute\" as const,\n      ...sidePosition,\n      ...alignPosition,\n    };\n  }, [\n    align,\n    avoidCollisions,\n    side,\n    alignOffset,\n    insets,\n    triggerPosition,\n    contentLayout,\n    sideOffset,\n    windowHeight,\n    windowWidth,\n  ]);\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:ui"
}
