about summary refs log tree commit diff
path: root/src/components/Link.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/Link.tsx')
-rw-r--r--src/components/Link.tsx134
1 files changed, 67 insertions, 67 deletions
diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index afd30b5ee..7d0e83332 100644
--- a/src/components/Link.tsx
+++ b/src/components/Link.tsx
@@ -1,21 +1,13 @@
 import React from 'react'
-import {
-  GestureResponderEvent,
-  Linking,
-  TouchableWithoutFeedback,
-} from 'react-native'
-import {
-  useLinkProps,
-  useNavigation,
-  StackActions,
-} from '@react-navigation/native'
+import {GestureResponderEvent} from 'react-native'
+import {useLinkProps, StackActions} from '@react-navigation/native'
 import {sanitizeUrl} from '@braintree/sanitize-url'
 
 import {useInteractionState} from '#/components/hooks/useInteractionState'
 import {isWeb} from '#/platform/detection'
 import {useTheme, web, flatten, TextStyleProp, atoms as a} from '#/alf'
 import {Button, ButtonProps} from '#/components/Button'
-import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
+import {AllNavigatorParams} from '#/lib/routes/types'
 import {
   convertBskyAppUrlIfNeeded,
   isExternalUrl,
@@ -23,7 +15,9 @@ import {
 } from '#/lib/strings/url-helpers'
 import {useModalControls} from '#/state/modals'
 import {router} from '#/routes'
-import {Text} from '#/components/Typography'
+import {Text, TextProps} from '#/components/Typography'
+import {useOpenLink} from 'state/preferences/in-app-browser'
+import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
 
 /**
  * Only available within a `Link`, since that inherits from `Button`.
@@ -52,14 +46,15 @@ type BaseLinkProps = Pick<
    *
    * Note: atm this only works for `InlineLink`s with a string child.
    */
-  warnOnMismatchingTextChild?: boolean
+  disableMismatchWarning?: boolean
 
   /**
-   * Callback for when the link is pressed.
+   * Callback for when the link is pressed. Prevent default and return `false`
+   * to exit early and prevent navigation.
    *
    * DO NOT use this for navigation, that's what the `to` prop is for.
    */
-  onPress?: (e: GestureResponderEvent) => void
+  onPress?: (e: GestureResponderEvent) => void | false
 
   /**
    * Web-only attribute. Sets `download` attr on web.
@@ -71,25 +66,28 @@ export function useLink({
   to,
   displayText,
   action = 'push',
-  warnOnMismatchingTextChild,
+  disableMismatchWarning,
   onPress: outerOnPress,
 }: BaseLinkProps & {
   displayText: string
 }) {
-  const navigation = useNavigation<NavigationProp>()
+  const navigation = useNavigationDeduped()
   const {href} = useLinkProps<AllNavigatorParams>({
     to:
       typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
   })
   const isExternal = isExternalUrl(href)
   const {openModal, closeModal} = useModalControls()
+  const openLink = useOpenLink()
 
   const onPress = React.useCallback(
     (e: GestureResponderEvent) => {
-      outerOnPress?.(e)
+      const exitEarlyIfFalse = outerOnPress?.(e)
+
+      if (exitEarlyIfFalse === false) return
 
       const requiresWarning = Boolean(
-        warnOnMismatchingTextChild &&
+        !disableMismatchWarning &&
           displayText &&
           isExternal &&
           linkRequiresWarning(href, displayText),
@@ -107,7 +105,7 @@ export function useLink({
         e.preventDefault()
 
         if (isExternal) {
-          Linking.openURL(href)
+          openLink(href)
         } else {
           /**
            * A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
@@ -125,7 +123,7 @@ export function useLink({
             href.startsWith('http') ||
             href.startsWith('mailto')
           ) {
-            Linking.openURL(href)
+            openLink(href)
           } else {
             closeModal() // close any active modals
 
@@ -146,15 +144,16 @@ export function useLink({
       }
     },
     [
-      href,
-      isExternal,
-      warnOnMismatchingTextChild,
-      navigation,
-      action,
+      outerOnPress,
+      disableMismatchWarning,
       displayText,
-      closeModal,
+      isExternal,
+      href,
       openModal,
-      outerOnPress,
+      openLink,
+      closeModal,
+      action,
+      navigation,
     ],
   )
 
@@ -165,7 +164,7 @@ export function useLink({
   }
 }
 
-export type LinkProps = Omit<BaseLinkProps, 'warnOnMismatchingTextChild'> &
+export type LinkProps = Omit<BaseLinkProps, 'disableMismatchWarning'> &
   Omit<ButtonProps, 'onPress' | 'disabled' | 'label'>
 
 /**
@@ -217,17 +216,19 @@ export function Link({
 }
 
 export type InlineLinkProps = React.PropsWithChildren<
-  BaseLinkProps & TextStyleProp
+  BaseLinkProps & TextStyleProp & Pick<TextProps, 'selectable'>
 >
 
 export function InlineLink({
   children,
   to,
   action = 'push',
-  warnOnMismatchingTextChild,
+  disableMismatchWarning,
   style,
   onPress: outerOnPress,
   download,
+  selectable,
+  label,
   ...rest
 }: InlineLinkProps) {
   const t = useTheme()
@@ -236,7 +237,7 @@ export function InlineLink({
     to,
     displayText: stringChildren ? children : '',
     action,
-    warnOnMismatchingTextChild,
+    disableMismatchWarning,
     onPress: outerOnPress,
   })
   const {
@@ -250,46 +251,45 @@ export function InlineLink({
     onIn: onPressIn,
     onOut: onPressOut,
   } = useInteractionState()
-  const flattenedStyle = flatten(style)
+  const flattenedStyle = flatten(style) || {}
 
   return (
-    <TouchableWithoutFeedback
-      accessibilityRole="button"
+    <Text
+      selectable={selectable}
+      accessibilityHint=""
+      accessibilityLabel={label || href}
+      {...rest}
+      style={[
+        {color: t.palette.primary_500},
+        (hovered || focused || pressed) && {
+          ...web({outline: 0}),
+          textDecorationLine: 'underline',
+          textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
+        },
+        flattenedStyle,
+      ]}
+      role="link"
       onPress={download ? undefined : onPress}
       onPressIn={onPressIn}
       onPressOut={onPressOut}
       onFocus={onFocus}
-      onBlur={onBlur}>
-      <Text
-        label={href}
-        {...rest}
-        style={[
-          {color: t.palette.primary_500},
-          (hovered || focused || pressed) && {
-            outline: 0,
-            textDecorationLine: 'underline',
-            textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
-          },
-          flattenedStyle,
-        ]}
-        role="link"
-        onMouseEnter={onHoverIn}
-        onMouseLeave={onHoverOut}
-        accessibilityRole="link"
-        href={href}
-        {...web({
-          hrefAttrs: {
-            target: download ? undefined : isExternal ? 'blank' : undefined,
-            rel: isExternal ? 'noopener noreferrer' : undefined,
-            download,
-          },
-          dataSet: {
-            // default to no underline, apply this ourselves
-            noUnderline: '1',
-          },
-        })}>
-        {children}
-      </Text>
-    </TouchableWithoutFeedback>
+      onBlur={onBlur}
+      onMouseEnter={onHoverIn}
+      onMouseLeave={onHoverOut}
+      accessibilityRole="link"
+      href={href}
+      {...web({
+        hrefAttrs: {
+          target: download ? undefined : isExternal ? 'blank' : undefined,
+          rel: isExternal ? 'noopener noreferrer' : undefined,
+          download,
+        },
+        dataSet: {
+          // default to no underline, apply this ourselves
+          noUnderline: '1',
+        },
+      })}>
+      {children}
+    </Text>
   )
 }