From 017a26164b58dfe888d6da4a6fe7c061e371a7d9 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 28 May 2025 12:17:49 +0300 Subject: [PATCH] attempt to upgrade Links --- src/view/com/util/Link.tsx | 61 ++++++++++++++++++------------ src/view/shell/desktop/LeftNav.tsx | 16 ++++---- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index 489fbc59cf..c92c41bddb 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -1,20 +1,24 @@ -import React, {ComponentProps, memo, useMemo} from 'react' +import {memo, useCallback, useMemo} from 'react' import { - GestureResponderEvent, + type GestureResponderEvent, Platform, Pressable, - StyleProp, - TextProps, - TextStyle, - TouchableOpacity, + type StyleProp, + type TextProps, + type TextStyle, + type TouchableOpacity, View, - ViewStyle, + type ViewStyle, } from 'react-native' import {sanitizeUrl} from '@braintree/sanitize-url' -import {StackActions, useLinkProps} from '@react-navigation/native' +import { + StackActions, + useLinkBuilder, + useLinkProps, +} from '@react-navigation/native' import { - DebouncedNavigationProp, + type DebouncedNavigationProp, useNavigationDeduped, } from '#/lib/hooks/useNavigationDeduped' import {useOpenLink} from '#/lib/hooks/useOpenLink' @@ -24,7 +28,7 @@ import { isExternalUrl, linkRequiresWarning, } from '#/lib/strings/url-helpers' -import {TypographyVariant} from '#/lib/ThemeContext' +import {type TypographyVariant} from '#/lib/ThemeContext' import {isAndroid, isWeb} from '#/platform/detection' import {emitSoftReset} from '#/state/events' import {useModalControls} from '#/state/modals' @@ -38,7 +42,7 @@ type Event = | React.MouseEvent | GestureResponderEvent -interface Props extends ComponentProps { +interface Props extends React.ComponentProps { testID?: string style?: StyleProp href?: string @@ -77,7 +81,7 @@ export const Link = memo(function Link({ const anchorHref = asAnchor ? sanitizeUrl(href) : undefined const openLink = useOpenLink() - const onPress = React.useCallback( + const onPress = useCallback( (e?: Event) => { onBeforePress?.() if (typeof href === 'string') { @@ -164,9 +168,9 @@ export const TextLink = memo(function TextLink({ text, numberOfLines, lineHeight, - dataSet, + dataSet: dataSetProp, title, - onPress, + onPress: onPressProp, onBeforePress, disableMismatchWarning, navigationAction, @@ -187,7 +191,12 @@ export const TextLink = memo(function TextLink({ anchorNoUnderline?: boolean onBeforePress?: () => void } & 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 {openModal, closeModal} = useModalControls() const openLink = useOpenLink() @@ -196,12 +205,15 @@ export const TextLink = memo(function TextLink({ console.error('Unable to detect mismatching label') } - if (anchorNoUnderline) { - dataSet = dataSet ?? {} - dataSet.noUnderline = 1 - } + const dataSet = useMemo(() => { + const ds = {...dataSetProp} + if (anchorNoUnderline) { + ds.noUnderline = 1 + } + return ds + }, [dataSetProp, anchorNoUnderline]) - props.onPress = React.useCallback( + const onPress = useCallback( (e?: Event) => { const requiresWarning = !disableMismatchWarning && @@ -224,10 +236,10 @@ export const TextLink = memo(function TextLink({ return } onBeforePress?.() - if (onPress) { + if (onPressProp) { e?.preventDefault?.() - // @ts-ignore function signature differs by platform -prf - return onPress() + // @ts-expect-error function signature differs by platform -prf + return onPressProp() } return onPressInner( closeModal, @@ -240,7 +252,7 @@ export const TextLink = memo(function TextLink({ }, [ onBeforePress, - onPress, + onPressProp, closeModal, openModal, navigation, @@ -273,6 +285,7 @@ export const TextLink = memo(function TextLink({ title={title} // @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 + onPress={onPress} {...props} {...orgProps}> {text} diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index f6c852ca12..52df66d709 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,10 +1,10 @@ -import React from 'react' +import {useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' import {type AppBskyActorDefs} from '@atproto/api' import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import { - useLinkProps, + useLinkTo, useNavigation, useNavigationState, } from '@react-navigation/native' @@ -326,7 +326,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { const {_} = useLingui() const {currentAccount} = useSession() const {leftNavMinimal} = useLayoutBreakpoints() - const [pathName] = React.useMemo(() => router.matchPath(href), [href]) + const [pathName] = useMemo(() => router.matchPath(href), [href]) const currentRouteInfo = useNavigationState(state => { if (!state) { return {name: 'Home'} @@ -339,8 +339,8 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { (currentRouteInfo.params as CommonNavigatorParams['Profile']).name === currentAccount?.handle : isTab(currentRouteInfo.name, pathName) - const {onPress} = useLinkProps({to: href}) - const onPressWrapped = React.useCallback( + const linkTo = useLinkTo() + const onPressWrapped = useCallback( (e: React.MouseEvent) => { if (e.ctrlKey || e.metaKey || e.altKey) { return @@ -349,10 +349,10 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { if (isCurrent) { emitSoftReset() } else { - onPress() + linkTo(href) } }, - [onPress, isCurrent], + [linkTo, href, isCurrent], ) return ( @@ -468,7 +468,7 @@ function ComposeBtn() { const {openComposer} = useOpenComposer() const {_} = useLingui() const {leftNavMinimal} = useLayoutBreakpoints() - const [isFetchingHandle, setIsFetchingHandle] = React.useState(false) + const [isFetchingHandle, setIsFetchingHandle] = useState(false) const fetchHandle = useFetchHandle() const getProfileHandle = async () => {