import type { Insets } from '~/components/primitives/types';
import * as React from 'react';
import { Dimensions, type LayoutRectangle, type ScaledSize, type ViewStyle } from 'react-native';
const HIDDEN_CONTENT: ViewStyle = {
type UseRelativePositionArgs = Omit<
'triggerPosition' | 'contentLayout' | 'dimensions'
triggerPosition: LayoutPosition | null;
contentLayout: LayoutRectangle | null;
disablePositioningStyle?: boolean;
export function useRelativePosition({
}: UseRelativePositionArgs) {
const dimensions = Dimensions.get('screen');
return React.useMemo(() => {
if (disablePositioningStyle) {
if (!triggerPosition || !contentLayout) {
export interface LayoutPosition {
interface GetPositionArgs {
avoidCollisions: boolean;
triggerPosition: LayoutPosition;
contentLayout: LayoutRectangle;
interface GetSidePositionArgs extends GetPositionArgs {
function getSidePosition({
}: GetSidePositionArgs) {
const insetTop = insets?.top ?? 0;
const insetBottom = insets?.bottom ?? 0;
const positionTop = triggerPosition?.pageY - sideOffset - contentLayout.height;
const positionBottom = triggerPosition.pageY + triggerPosition.height + sideOffset;
top: side === 'top' ? positionTop : positionBottom,
Math.max(insetTop, positionTop),
dimensions.height - insetBottom - contentLayout.height
top: Math.min(dimensions.height - insetBottom - contentLayout.height, positionBottom),
interface GetAlignPositionArgs extends GetPositionArgs {
align: 'start' | 'center' | 'end';
function getAlignPosition({
}: GetAlignPositionArgs) {
const insetLeft = insets?.left ?? 0;
const insetRight = insets?.right ?? 0;
const maxContentWidth = dimensions.width - insetLeft - insetRight;
const contentWidth = Math.min(contentLayout.width, maxContentWidth);
let left = getLeftPosition(
const doesCollide = left < insetLeft || left + contentWidth > dimensions.width - insetRight;
const spaceLeft = left - insetLeft;
const spaceRight = dimensions.width - insetRight - (left + contentWidth);
if (spaceLeft > spaceRight && spaceLeft >= contentWidth) {
} else if (spaceRight >= contentWidth) {
left = dimensions.width - insetRight - contentWidth;
const centeredPosition = Math.max(
(dimensions.width - contentWidth - insetRight) / 2
return { left, maxWidth: maxContentWidth };
function getLeftPosition(
align: 'start' | 'center' | 'end',
if (align === 'center') {
left = triggerPageX + triggerWidth / 2 - contentWidth / 2;
left = triggerPageX + triggerWidth - contentWidth;
Math.min(left + alignOffset, dimensions.width - contentWidth - insetRight)
type GetContentStyleArgs = GetPositionArgs & GetSidePositionArgs & GetAlignPositionArgs;
function getContentStyle({
}: GetContentStyleArgs) {
{ position: 'absolute' } as const,