Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions apps/mobile/src/components/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useEffect, useMemo, useState } from 'react'
import { Dimensions, Modal, Pressable, StyleSheet, Text, View } from 'react-native'

import { palette } from '../styles'
Expand All @@ -20,13 +20,55 @@ export type PopoverContent = {
title: string,
}

const POPOVER_PADDING = 10
const POPOVER_OFFSET_Y = 100
const POPOVER_ESTIMATED_WIDTH = 300 // maxWidth from styles
const POPOVER_ESTIMATED_HEIGHT = 250

export function Popover({
content,
isVisible,
onRequestClose,
}: PopoverProps) {
const screenHeight = Dimensions.get('window').height
const screenWidth = Dimensions.get('window').width
const [popoverWidth, setPopoverWidth] = useState(POPOVER_ESTIMATED_WIDTH)
const [popoverHeight, setPopoverHeight] = useState(POPOVER_ESTIMATED_HEIGHT)

// Reset dimensions when content changes to ensure accurate positioning
useEffect(() => {
if (content) {
setPopoverWidth(POPOVER_ESTIMATED_WIDTH)
setPopoverHeight(POPOVER_ESTIMATED_HEIGHT)
}
}, [content])

// Calculate position to ensure popover never overflows the screen
const position = useMemo(() => {
if (!content) return { top: 0, left: 0 }

// Calculate horizontal position
// Try to center the popover horizontally relative to the click position
const preferredLeft = content.clickPosition.x - popoverWidth / 2

// Ensure popover doesn't overflow on the right
const maxLeft = screenWidth - popoverWidth - POPOVER_PADDING
// Ensure popover doesn't overflow on the left
const minLeft = POPOVER_PADDING

// Clamp the left position
const left = Math.max(minLeft, Math.min(preferredLeft, maxLeft))

// Calculate vertical position
const preferredTop = content.clickPosition.y - POPOVER_OFFSET_Y
const maxTop = screenHeight - popoverHeight - POPOVER_PADDING
const minTop = POPOVER_PADDING

// Clamp the top position
const top = Math.max(minTop, Math.min(preferredTop, maxTop))

return { top, left }
}, [content, popoverWidth, popoverHeight, screenWidth, screenHeight])

return (
<Modal
Expand All @@ -43,11 +85,13 @@ export function Popover({
<Pressable
style={[
styles.container,
{
top: Math.max(10, Math.min(content.clickPosition.y - 100, screenHeight - 250)),
left: Math.max(10, Math.min(content.clickPosition.x - 100, screenWidth - 220)),
},
position,
]}
onLayout={(e) => {
const { width, height } = e.nativeEvent.layout
setPopoverWidth(width)
setPopoverHeight(height)
}}
onPress={(e) => e.stopPropagation()}
>
<Text style={styles.title}>
Expand Down