Cleanup
This commit is contained in:
@@ -7,14 +7,18 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {Dimensions, View} from 'react-native'
|
import {Dimensions, View} from 'react-native'
|
||||||
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useOnGesture} from '#/components/hooks/useOnGesture'
|
import {useOnGesture} from '#/components/hooks/useOnGesture'
|
||||||
// import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
import {TIP_SIZE} from '#/components/Tooltip/const'
|
import {TIP_SIZE} from '#/components/Tooltip/const'
|
||||||
|
|
||||||
|
const HALF_TIP = TIP_SIZE / 2
|
||||||
|
const TIP_VISUAL_OFFSET = TIP_SIZE / 3
|
||||||
|
|
||||||
type TooltipContextType = {
|
type TooltipContextType = {
|
||||||
|
position: 'top' | 'bottom'
|
||||||
ready: boolean
|
ready: boolean
|
||||||
onVisibleChange: (visible: boolean) => void
|
onVisibleChange: (visible: boolean) => void
|
||||||
}
|
}
|
||||||
@@ -32,6 +36,7 @@ type TargetContextType = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TooltipContext = createContext<TooltipContextType>({
|
const TooltipContext = createContext<TooltipContextType>({
|
||||||
|
position: 'bottom',
|
||||||
ready: false,
|
ready: false,
|
||||||
onVisibleChange: () => {},
|
onVisibleChange: () => {},
|
||||||
})
|
})
|
||||||
@@ -43,10 +48,12 @@ const TargetContext = createContext<TargetContextType>({
|
|||||||
|
|
||||||
export function Outer({
|
export function Outer({
|
||||||
children,
|
children,
|
||||||
|
position = 'bottom',
|
||||||
visible: requestVisible,
|
visible: requestVisible,
|
||||||
onVisibleChange,
|
onVisibleChange,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
position?: 'top' | 'bottom'
|
||||||
visible: boolean
|
visible: boolean
|
||||||
onVisibleChange: (visible: boolean) => void
|
onVisibleChange: (visible: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
@@ -94,8 +101,8 @@ export function Outer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ctx = useMemo(
|
const ctx = useMemo(
|
||||||
() => ({ready, onVisibleChange}),
|
() => ({position, ready, onVisibleChange}),
|
||||||
[ready, onVisibleChange],
|
[position, ready, onVisibleChange],
|
||||||
)
|
)
|
||||||
const targetCtx = useMemo(
|
const targetCtx = useMemo(
|
||||||
() => ({targetMeasurements, targetRef}),
|
() => ({targetMeasurements, targetRef}),
|
||||||
@@ -124,7 +131,7 @@ export function Target({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Content({children}: {children: React.ReactNode}) {
|
export function Content({children}: {children: React.ReactNode}) {
|
||||||
const {ready, onVisibleChange} = useContext(TooltipContext)
|
const {position, ready, onVisibleChange} = useContext(TooltipContext)
|
||||||
const {targetMeasurements} = useContext(TargetContext)
|
const {targetMeasurements} = useContext(TargetContext)
|
||||||
const requestClose = useCallback(() => {
|
const requestClose = useCallback(() => {
|
||||||
onVisibleChange(false)
|
onVisibleChange(false)
|
||||||
@@ -135,6 +142,7 @@ export function Content({children}: {children: React.ReactNode}) {
|
|||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<Bubble
|
<Bubble
|
||||||
|
position={position}
|
||||||
/*
|
/*
|
||||||
* Gotta pass these in here. Inside the Bubble, we're Potal-ed outside
|
* Gotta pass these in here. Inside the Bubble, we're Potal-ed outside
|
||||||
* the context providers.
|
* the context providers.
|
||||||
@@ -149,10 +157,12 @@ export function Content({children}: {children: React.ReactNode}) {
|
|||||||
|
|
||||||
function Bubble({
|
function Bubble({
|
||||||
children,
|
children,
|
||||||
|
position,
|
||||||
requestClose,
|
requestClose,
|
||||||
targetMeasurements,
|
targetMeasurements,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
position: TooltipContextType['position']
|
||||||
requestClose: () => void
|
requestClose: () => void
|
||||||
targetMeasurements: Exclude<
|
targetMeasurements: Exclude<
|
||||||
TargetContextType['targetMeasurements'],
|
TargetContextType['targetMeasurements'],
|
||||||
@@ -160,7 +170,7 @@ function Bubble({
|
|||||||
>
|
>
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
// const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const [bubbleMeasurements, setBubbleMeasurements] = useState<
|
const [bubbleMeasurements, setBubbleMeasurements] = useState<
|
||||||
| {
|
| {
|
||||||
width: number
|
width: number
|
||||||
@@ -179,39 +189,74 @@ function Bubble({
|
|||||||
tipLeft: 0,
|
tipLeft: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
const win = Dimensions.get('window')
|
const {width: ww, height: wh} = Dimensions.get('window')
|
||||||
const {width: ww, height: _wh} = win
|
const maxTop = insets.top
|
||||||
// const maxTop = insets.top
|
const maxBottom = wh - insets.bottom
|
||||||
// const maxBottom = wh - insets.bottom
|
|
||||||
const {width: cw, height: ch} = bubbleMeasurements
|
const {width: cw, height: ch} = bubbleMeasurements
|
||||||
const minLeft = a.px_xl.paddingLeft
|
const minLeft = a.px_xl.paddingLeft
|
||||||
const maxLeft = ww - minLeft
|
const maxLeft = ww - minLeft
|
||||||
|
|
||||||
|
let computedPosition: 'top' | 'bottom' = position
|
||||||
let top = targetMeasurements.y + targetMeasurements.height
|
let top = targetMeasurements.y + targetMeasurements.height
|
||||||
let left = Math.max(
|
let left = Math.max(
|
||||||
minLeft,
|
minLeft,
|
||||||
targetMeasurements.x + targetMeasurements.width / 2 - cw / 2,
|
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) {
|
if (left + cw > maxLeft) {
|
||||||
left -= left + cw - maxLeft
|
left -= left + cw - maxLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
let tipLeft =
|
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 {
|
return {
|
||||||
|
computedPosition,
|
||||||
top,
|
top,
|
||||||
bottom: top + ch,
|
bottom,
|
||||||
left,
|
left,
|
||||||
right: left + cw,
|
right: left + cw,
|
||||||
tipTop,
|
tipTop,
|
||||||
tipLeft,
|
tipLeft,
|
||||||
}
|
}
|
||||||
}, [targetMeasurements, bubbleMeasurements])
|
}, [position, targetMeasurements, bubbleMeasurements, insets])
|
||||||
|
|
||||||
const requestCloseWrapped = useCallback(() => {
|
const requestCloseWrapped = useCallback(() => {
|
||||||
setBubbleMeasurements(undefined)
|
setBubbleMeasurements(undefined)
|
||||||
@@ -265,7 +310,23 @@ function Bubble({
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<View
|
<View
|
||||||
style={[a.px_md, a.py_sm, a.rounded_sm, t.atoms.bg, t.atoms.shadow_sm]}
|
style={[
|
||||||
|
a.px_md,
|
||||||
|
a.py_sm,
|
||||||
|
a.rounded_sm,
|
||||||
|
t.atoms.bg,
|
||||||
|
t.atoms.shadow_md,
|
||||||
|
{
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
// provide more shadow beneath tip
|
||||||
|
height:
|
||||||
|
TIP_VISUAL_OFFSET *
|
||||||
|
(coords.computedPosition === 'bottom' ? -1 : 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
onLayout={e => {
|
onLayout={e => {
|
||||||
setBubbleMeasurements({
|
setBubbleMeasurements({
|
||||||
width: e.nativeEvent.layout.width,
|
width: e.nativeEvent.layout.width,
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ import {
|
|||||||
} from '#/components/KnownFollowers'
|
} from '#/components/KnownFollowers'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
|
import * as Tooltip from '#/components/Tooltip'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
|
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
|
||||||
import {EditProfileDialog} from './EditProfileDialog'
|
import {EditProfileDialog} from './EditProfileDialog'
|
||||||
import {ProfileHeaderHandle} from './Handle'
|
import {ProfileHeaderHandle} from './Handle'
|
||||||
import {ProfileHeaderMetrics} from './Metrics'
|
import {ProfileHeaderMetrics} from './Metrics'
|
||||||
import {ProfileHeaderShell} from './Shell'
|
import {ProfileHeaderShell} from './Shell'
|
||||||
import * as Tooltip from '#/components/Tooltip'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
profile: AppBskyActorDefs.ProfileViewDetailed
|
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||||
@@ -202,16 +202,37 @@ let ProfileHeaderStandard = ({
|
|||||||
) : !profile.viewer?.blockedBy ? (
|
) : !profile.viewer?.blockedBy ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
label='test'
|
label="test"
|
||||||
size='small'
|
size="small"
|
||||||
variant='solid'
|
variant="solid"
|
||||||
color='secondary'
|
color="secondary"
|
||||||
shape='round'
|
shape="round"
|
||||||
onPress={() => setVisible(!visible)}
|
onPress={() => setVisible(!visible)}>
|
||||||
>
|
|
||||||
<ButtonIcon position="left" icon={Plus} />
|
<ButtonIcon position="left" icon={Plus} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{hasSession && (
|
||||||
|
<>
|
||||||
|
<Tooltip.Outer
|
||||||
|
position="top"
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={setVisible}>
|
||||||
|
<Tooltip.Target>
|
||||||
|
{({ref}) => (
|
||||||
|
<View ref={ref}>
|
||||||
|
<MessageProfileButton profile={profile} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Tooltip.Target>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<Text selectable>
|
||||||
|
The quick brown fox jumps over the lazy dog
|
||||||
|
</Text>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Outer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
testID={profile.viewer?.following ? 'unfollowBtn' : 'followBtn'}
|
testID={profile.viewer?.following ? 'unfollowBtn' : 'followBtn'}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -239,24 +260,6 @@ let ProfileHeaderStandard = ({
|
|||||||
)}
|
)}
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
{hasSession && (
|
|
||||||
<>
|
|
||||||
<Tooltip.Outer visible={visible} onVisibleChange={setVisible}>
|
|
||||||
<Tooltip.Target>
|
|
||||||
{({ref}) => (
|
|
||||||
<View ref={ref}>
|
|
||||||
<MessageProfileButton profile={profile} />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Tooltip.Target>
|
|
||||||
<Tooltip.Content>
|
|
||||||
<Text>The quick brown fox jumps over the lazy dog</Text>
|
|
||||||
</Tooltip.Content>
|
|
||||||
</Tooltip.Outer>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<ProfileMenu profile={profile} />
|
<ProfileMenu profile={profile} />
|
||||||
|
|||||||
Reference in New Issue
Block a user