attempt to upgrade Links

This commit is contained in:
Samuel Newman
2025-05-28 12:17:49 +03:00
parent 0e0110aa13
commit 017a26164b
2 changed files with 45 additions and 32 deletions
+37 -24
View File
@@ -1,20 +1,24 @@
import React, {ComponentProps, memo, useMemo} from 'react' import {memo, useCallback, useMemo} from 'react'
import { import {
GestureResponderEvent, type GestureResponderEvent,
Platform, Platform,
Pressable, Pressable,
StyleProp, type StyleProp,
TextProps, type TextProps,
TextStyle, type TextStyle,
TouchableOpacity, type TouchableOpacity,
View, View,
ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import {sanitizeUrl} from '@braintree/sanitize-url' import {sanitizeUrl} from '@braintree/sanitize-url'
import {StackActions, useLinkProps} from '@react-navigation/native' import {
StackActions,
useLinkBuilder,
useLinkProps,
} from '@react-navigation/native'
import { import {
DebouncedNavigationProp, type DebouncedNavigationProp,
useNavigationDeduped, useNavigationDeduped,
} from '#/lib/hooks/useNavigationDeduped' } from '#/lib/hooks/useNavigationDeduped'
import {useOpenLink} from '#/lib/hooks/useOpenLink' import {useOpenLink} from '#/lib/hooks/useOpenLink'
@@ -24,7 +28,7 @@ import {
isExternalUrl, isExternalUrl,
linkRequiresWarning, linkRequiresWarning,
} from '#/lib/strings/url-helpers' } from '#/lib/strings/url-helpers'
import {TypographyVariant} from '#/lib/ThemeContext' import {type TypographyVariant} from '#/lib/ThemeContext'
import {isAndroid, isWeb} from '#/platform/detection' import {isAndroid, isWeb} from '#/platform/detection'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
@@ -38,7 +42,7 @@ type Event =
| React.MouseEvent<HTMLAnchorElement, MouseEvent> | React.MouseEvent<HTMLAnchorElement, MouseEvent>
| GestureResponderEvent | GestureResponderEvent
interface Props extends ComponentProps<typeof TouchableOpacity> { interface Props extends React.ComponentProps<typeof TouchableOpacity> {
testID?: string testID?: string
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
href?: string href?: string
@@ -77,7 +81,7 @@ export const Link = memo(function Link({
const anchorHref = asAnchor ? sanitizeUrl(href) : undefined const anchorHref = asAnchor ? sanitizeUrl(href) : undefined
const openLink = useOpenLink() const openLink = useOpenLink()
const onPress = React.useCallback( const onPress = useCallback(
(e?: Event) => { (e?: Event) => {
onBeforePress?.() onBeforePress?.()
if (typeof href === 'string') { if (typeof href === 'string') {
@@ -164,9 +168,9 @@ export const TextLink = memo(function TextLink({
text, text,
numberOfLines, numberOfLines,
lineHeight, lineHeight,
dataSet, dataSet: dataSetProp,
title, title,
onPress, onPress: onPressProp,
onBeforePress, onBeforePress,
disableMismatchWarning, disableMismatchWarning,
navigationAction, navigationAction,
@@ -187,7 +191,12 @@ export const TextLink = memo(function TextLink({
anchorNoUnderline?: boolean anchorNoUnderline?: boolean
onBeforePress?: () => void onBeforePress?: () => void
} & TextProps) { } & TextProps) {
const {...props} = useLinkProps({to: sanitizeUrl(href)}) const {buildAction} = useLinkBuilder()
const sanitized = sanitizeUrl(href)
const {onPress: _, ...props} = useLinkProps({
href: sanitized,
action: buildAction(sanitized),
})
const navigation = useNavigationDeduped() const navigation = useNavigationDeduped()
const {openModal, closeModal} = useModalControls() const {openModal, closeModal} = useModalControls()
const openLink = useOpenLink() const openLink = useOpenLink()
@@ -196,12 +205,15 @@ export const TextLink = memo(function TextLink({
console.error('Unable to detect mismatching label') console.error('Unable to detect mismatching label')
} }
if (anchorNoUnderline) { const dataSet = useMemo(() => {
dataSet = dataSet ?? {} const ds = {...dataSetProp}
dataSet.noUnderline = 1 if (anchorNoUnderline) {
} ds.noUnderline = 1
}
return ds
}, [dataSetProp, anchorNoUnderline])
props.onPress = React.useCallback( const onPress = useCallback(
(e?: Event) => { (e?: Event) => {
const requiresWarning = const requiresWarning =
!disableMismatchWarning && !disableMismatchWarning &&
@@ -224,10 +236,10 @@ export const TextLink = memo(function TextLink({
return return
} }
onBeforePress?.() onBeforePress?.()
if (onPress) { if (onPressProp) {
e?.preventDefault?.() e?.preventDefault?.()
// @ts-ignore function signature differs by platform -prf // @ts-expect-error function signature differs by platform -prf
return onPress() return onPressProp()
} }
return onPressInner( return onPressInner(
closeModal, closeModal,
@@ -240,7 +252,7 @@ export const TextLink = memo(function TextLink({
}, },
[ [
onBeforePress, onBeforePress,
onPress, onPressProp,
closeModal, closeModal,
openModal, openModal,
navigation, navigation,
@@ -273,6 +285,7 @@ export const TextLink = memo(function TextLink({
title={title} title={title}
// @ts-ignore web only -prf // @ts-ignore web only -prf
hrefAttrs={hrefAttrs} // hack to get open in new tab to work on safari. without this, safari will open in a new window hrefAttrs={hrefAttrs} // hack to get open in new tab to work on safari. without this, safari will open in a new window
onPress={onPress}
{...props} {...props}
{...orgProps}> {...orgProps}>
{text} {text}
+8 -8
View File
@@ -1,10 +1,10 @@
import React from 'react' import {useCallback, useMemo, useState} from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api' import {type AppBskyActorDefs} from '@atproto/api'
import {msg, plural, Trans} from '@lingui/macro' import {msg, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import { import {
useLinkProps, useLinkTo,
useNavigation, useNavigation,
useNavigationState, useNavigationState,
} from '@react-navigation/native' } from '@react-navigation/native'
@@ -326,7 +326,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {leftNavMinimal} = useLayoutBreakpoints() const {leftNavMinimal} = useLayoutBreakpoints()
const [pathName] = React.useMemo(() => router.matchPath(href), [href]) const [pathName] = useMemo(() => router.matchPath(href), [href])
const currentRouteInfo = useNavigationState(state => { const currentRouteInfo = useNavigationState(state => {
if (!state) { if (!state) {
return {name: 'Home'} return {name: 'Home'}
@@ -339,8 +339,8 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
(currentRouteInfo.params as CommonNavigatorParams['Profile']).name === (currentRouteInfo.params as CommonNavigatorParams['Profile']).name ===
currentAccount?.handle currentAccount?.handle
: isTab(currentRouteInfo.name, pathName) : isTab(currentRouteInfo.name, pathName)
const {onPress} = useLinkProps({to: href}) const linkTo = useLinkTo()
const onPressWrapped = React.useCallback( const onPressWrapped = useCallback(
(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => { (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (e.ctrlKey || e.metaKey || e.altKey) { if (e.ctrlKey || e.metaKey || e.altKey) {
return return
@@ -349,10 +349,10 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
if (isCurrent) { if (isCurrent) {
emitSoftReset() emitSoftReset()
} else { } else {
onPress() linkTo(href)
} }
}, },
[onPress, isCurrent], [linkTo, href, isCurrent],
) )
return ( return (
@@ -468,7 +468,7 @@ function ComposeBtn() {
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const {_} = useLingui() const {_} = useLingui()
const {leftNavMinimal} = useLayoutBreakpoints() const {leftNavMinimal} = useLayoutBreakpoints()
const [isFetchingHandle, setIsFetchingHandle] = React.useState(false) const [isFetchingHandle, setIsFetchingHandle] = useState(false)
const fetchHandle = useFetchHandle() const fetchHandle = useFetchHandle()
const getProfileHandle = async () => { const getProfileHandle = async () => {