diff --git a/src/components/Tooltip/index.tsx b/src/components/Tooltip/index.tsx index 5892338369..e4524fbf0f 100644 --- a/src/components/Tooltip/index.tsx +++ b/src/components/Tooltip/index.tsx @@ -7,14 +7,18 @@ import { useState, } from 'react' import {Dimensions, View} from 'react-native' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import {atoms as a, useTheme} from '#/alf' import {useOnGesture} from '#/components/hooks/useOnGesture' -// import {useSafeAreaInsets} from 'react-native-safe-area-context' import {Portal} from '#/components/Portal' import {TIP_SIZE} from '#/components/Tooltip/const' +const HALF_TIP = TIP_SIZE / 2 +const TIP_VISUAL_OFFSET = TIP_SIZE / 3 + type TooltipContextType = { + position: 'top' | 'bottom' ready: boolean onVisibleChange: (visible: boolean) => void } @@ -32,6 +36,7 @@ type TargetContextType = { } const TooltipContext = createContext({ + position: 'bottom', ready: false, onVisibleChange: () => {}, }) @@ -43,10 +48,12 @@ const TargetContext = createContext({ export function Outer({ children, + position = 'bottom', visible: requestVisible, onVisibleChange, }: { children: React.ReactNode + position?: 'top' | 'bottom' visible: boolean onVisibleChange: (visible: boolean) => void }) { @@ -94,8 +101,8 @@ export function Outer({ } const ctx = useMemo( - () => ({ready, onVisibleChange}), - [ready, onVisibleChange], + () => ({position, ready, onVisibleChange}), + [position, ready, onVisibleChange], ) const targetCtx = useMemo( () => ({targetMeasurements, targetRef}), @@ -124,7 +131,7 @@ export function Target({ } export function Content({children}: {children: React.ReactNode}) { - const {ready, onVisibleChange} = useContext(TooltipContext) + const {position, ready, onVisibleChange} = useContext(TooltipContext) const {targetMeasurements} = useContext(TargetContext) const requestClose = useCallback(() => { onVisibleChange(false) @@ -135,6 +142,7 @@ export function Content({children}: {children: React.ReactNode}) { return ( void targetMeasurements: Exclude< TargetContextType['targetMeasurements'], @@ -160,7 +170,7 @@ function Bubble({ > }) { const t = useTheme() - // const insets = useSafeAreaInsets() + const insets = useSafeAreaInsets() const [bubbleMeasurements, setBubbleMeasurements] = useState< | { width: number @@ -179,39 +189,74 @@ function Bubble({ tipLeft: 0, } - const win = Dimensions.get('window') - const {width: ww, height: _wh} = win - // const maxTop = insets.top - // const maxBottom = wh - insets.bottom + const {width: ww, height: wh} = Dimensions.get('window') + const maxTop = insets.top + const maxBottom = wh - insets.bottom const {width: cw, height: ch} = bubbleMeasurements const minLeft = a.px_xl.paddingLeft const maxLeft = ww - minLeft + let computedPosition: 'top' | 'bottom' = position let top = targetMeasurements.y + targetMeasurements.height let left = Math.max( minLeft, targetMeasurements.x + targetMeasurements.width / 2 - cw / 2, ) - let tipTop = (TIP_SIZE / 2) * -1 + const tipTranslate = HALF_TIP * -1 + let tipTop = tipTranslate if (left + cw > maxLeft) { left -= left + cw - maxLeft } let tipLeft = - targetMeasurements.x - left + targetMeasurements.width / 2 - TIP_SIZE / 2 + targetMeasurements.x - left + targetMeasurements.width / 2 - HALF_TIP - top += TIP_SIZE / 3 + let bottom = top + ch + + function positionTop() { + top = top - ch - targetMeasurements.height + bottom = top + ch + tipTop = tipTop + ch + computedPosition = 'top' + } + + function positionBottom() { + top = targetMeasurements.y + targetMeasurements.height + bottom = top + ch + tipTop = tipTranslate + computedPosition = 'bottom' + } + + if (position === 'top') { + positionTop() + if (top < maxTop) { + positionBottom() + } + } else { + if (bottom > maxBottom) { + positionTop() + } + } + + if (computedPosition === 'bottom') { + top += TIP_VISUAL_OFFSET + bottom += TIP_VISUAL_OFFSET + } else { + top -= TIP_VISUAL_OFFSET + bottom -= TIP_VISUAL_OFFSET + } return { + computedPosition, top, - bottom: top + ch, + bottom, left, right: left + cw, tipTop, tipLeft, } - }, [targetMeasurements, bubbleMeasurements]) + }, [position, targetMeasurements, bubbleMeasurements, insets]) const requestCloseWrapped = useCallback(() => { setBubbleMeasurements(undefined) @@ -265,7 +310,23 @@ function Bubble({ ]} /> { setBubbleMeasurements({ width: e.nativeEvent.layout.width, diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index c3eebe2551..52fe70322e 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -34,13 +34,13 @@ import { } from '#/components/KnownFollowers' import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' +import * as Tooltip from '#/components/Tooltip' import {Text} from '#/components/Typography' import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton' import {EditProfileDialog} from './EditProfileDialog' import {ProfileHeaderHandle} from './Handle' import {ProfileHeaderMetrics} from './Metrics' import {ProfileHeaderShell} from './Shell' -import * as Tooltip from '#/components/Tooltip' interface Props { profile: AppBskyActorDefs.ProfileViewDetailed @@ -202,16 +202,37 @@ let ProfileHeaderStandard = ({ ) : !profile.viewer?.blockedBy ? ( <> + {hasSession && ( + <> + + + {({ref}) => ( + + + + )} + + + + The quick brown fox jumps over the lazy dog + + + + + )} + - - - {hasSession && ( - <> - - - {({ref}) => ( - - - - )} - - - The quick brown fox jumps over the lazy dog - - - - )} ) : null}