From 943acd16aac187ed4eb5f9cea67d6c507aab1ab5 Mon Sep 17 00:00:00 2001
From: Eric Bailey <git@esb.lol>
Date: Mon, 19 Feb 2024 10:08:21 -0600
Subject: Add `selectable` to new text components (#2899)

* Make new text selectable (broken)

* Fixes

* Fix bad conflict resolution

* Remove console
---
 src/components/Link.tsx | 77 +++++++++++++++++++++++--------------------------
 1 file changed, 36 insertions(+), 41 deletions(-)

(limited to 'src/components/Link.tsx')

diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index afd30b5ee..85c13270a 100644
--- a/src/components/Link.tsx
+++ b/src/components/Link.tsx
@@ -1,9 +1,5 @@
 import React from 'react'
-import {
-  GestureResponderEvent,
-  Linking,
-  TouchableWithoutFeedback,
-} from 'react-native'
+import {GestureResponderEvent, Linking} from 'react-native'
 import {
   useLinkProps,
   useNavigation,
@@ -23,7 +19,7 @@ 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'
 
 /**
  * Only available within a `Link`, since that inherits from `Button`.
@@ -217,7 +213,7 @@ export function Link({
 }
 
 export type InlineLinkProps = React.PropsWithChildren<
-  BaseLinkProps & TextStyleProp
+  BaseLinkProps & TextStyleProp & Pick<TextProps, 'selectable'>
 >
 
 export function InlineLink({
@@ -228,6 +224,7 @@ export function InlineLink({
   style,
   onPress: outerOnPress,
   download,
+  selectable,
   ...rest
 }: InlineLinkProps) {
   const t = useTheme()
@@ -253,43 +250,41 @@ export function InlineLink({
   const flattenedStyle = flatten(style)
 
   return (
-    <TouchableWithoutFeedback
-      accessibilityRole="button"
+    <Text
+      selectable={selectable}
+      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"
       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>
   )
 }
-- 
cgit 1.4.1


From df5a8f15427f8efeded692e21585e48df4907c3a Mon Sep 17 00:00:00 2001
From: Eric Bailey <git@esb.lol>
Date: Tue, 20 Feb 2024 11:03:04 -0600
Subject: Swap `RichText` (#2934)

* Switch to new RT

* Allow early exit from links

* Build in tracking to text atoms

* Clean up FeedSourceCard

* Clean up leading after new default

* Add deprecated notice
---
 src/alf/atoms.ts                             | 18 +++++++++++++++++-
 src/components/Link.tsx                      |  9 ++++++---
 src/components/RichText.tsx                  |  4 ++--
 src/view/com/feeds/FeedSourceCard.tsx        |  8 +++++---
 src/view/com/lists/ListCard.tsx              |  7 ++++---
 src/view/com/post-thread/PostThreadItem.tsx  | 15 ++++++---------
 src/view/com/posts/FeedItem.tsx              |  9 ++++-----
 src/view/com/profile/ProfileHeader.tsx       |  9 +++++----
 src/view/com/util/post-embeds/QuoteEmbed.tsx | 10 +++++-----
 src/view/com/util/text/RichText.tsx          |  3 +++
 src/view/screens/ProfileFeed.tsx             |  8 ++++----
 src/view/screens/ProfileList.tsx             |  8 ++++----
 12 files changed, 65 insertions(+), 43 deletions(-)

(limited to 'src/components/Link.tsx')

diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index f75e8ffe0..18f492d6e 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -176,43 +176,59 @@ export const atoms = {
   },
   text_2xs: {
     fontSize: tokens.fontSize._2xs,
+    letterSpacing: 0.25,
   },
   text_xs: {
     fontSize: tokens.fontSize.xs,
+    letterSpacing: 0.25,
   },
   text_sm: {
     fontSize: tokens.fontSize.sm,
+    letterSpacing: 0.25,
   },
   text_md: {
     fontSize: tokens.fontSize.md,
+    letterSpacing: 0.25,
   },
   text_lg: {
     fontSize: tokens.fontSize.lg,
+    letterSpacing: 0.25,
   },
   text_xl: {
     fontSize: tokens.fontSize.xl,
+    letterSpacing: 0.25,
   },
   text_2xl: {
     fontSize: tokens.fontSize._2xl,
+    letterSpacing: 0.25,
   },
   text_3xl: {
     fontSize: tokens.fontSize._3xl,
+    letterSpacing: 0.25,
   },
   text_4xl: {
     fontSize: tokens.fontSize._4xl,
+    letterSpacing: 0.25,
   },
   text_5xl: {
     fontSize: tokens.fontSize._5xl,
+    letterSpacing: 0.25,
   },
   leading_tight: {
     lineHeight: 1.15,
   },
   leading_snug: {
-    lineHeight: 1.25,
+    lineHeight: 1.3,
   },
   leading_normal: {
     lineHeight: 1.5,
   },
+  tracking_normal: {
+    letterSpacing: 0,
+  },
+  tracking_wide: {
+    letterSpacing: 0.25,
+  },
   font_normal: {
     fontWeight: tokens.fontWeight.normal,
   },
diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index 85c13270a..593b0863a 100644
--- a/src/components/Link.tsx
+++ b/src/components/Link.tsx
@@ -51,11 +51,12 @@ type BaseLinkProps = Pick<
   warnOnMismatchingTextChild?: 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.
@@ -82,7 +83,9 @@ export function useLink({
 
   const onPress = React.useCallback(
     (e: GestureResponderEvent) => {
-      outerOnPress?.(e)
+      const exitEarlyIfFalse = outerOnPress?.(e)
+
+      if (exitEarlyIfFalse === false) return
 
       const requiresWarning = Boolean(
         warnOnMismatchingTextChild &&
diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx
index 8aeef9ea1..c72fcabdd 100644
--- a/src/components/RichText.tsx
+++ b/src/components/RichText.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api'
 
-import {atoms as a, TextStyleProp} from '#/alf'
+import {atoms as a, TextStyleProp, flatten} from '#/alf'
 import {InlineLink} from '#/components/Link'
 import {Text, TextProps} from '#/components/Typography'
 import {toShortUrl} from 'lib/strings/url-helpers'
@@ -29,7 +29,7 @@ export function RichText({
   const [richText, setRichText] = React.useState<RichTextAPI>(() =>
     value instanceof RichTextAPI ? value : new RichTextAPI({text: value}),
   )
-  const styles = [a.leading_normal, style]
+  const styles = [a.leading_snug, flatten(style)]
 
   React.useEffect(() => {
     if (!resolveFacets) return
diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx
index 0de88b248..9bd7238df 100644
--- a/src/view/com/feeds/FeedSourceCard.tsx
+++ b/src/view/com/feeds/FeedSourceCard.tsx
@@ -2,7 +2,7 @@ import React from 'react'
 import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {Text} from '../util/text/Text'
-import {RichText} from '../util/text/RichText'
+import {RichText} from '#/components/RichText'
 import {usePalette} from 'lib/hooks/usePalette'
 import {s} from 'lib/styles'
 import {UserAvatar} from '../util/UserAvatar'
@@ -25,6 +25,7 @@ import {
 } from '#/state/queries/preferences'
 import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed'
 import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
+import {useTheme} from '#/alf'
 
 export function FeedSourceCard({
   feedUri,
@@ -82,6 +83,7 @@ export function FeedSourceCardLoaded({
   pinOnSave?: boolean
   showMinimalPlaceholder?: boolean
 }) {
+  const t = useTheme()
   const pal = usePalette('default')
   const {_} = useLingui()
   const navigation = useNavigation<NavigationProp>()
@@ -266,8 +268,8 @@ export function FeedSourceCardLoaded({
 
       {showDescription && feed.description ? (
         <RichText
-          style={[pal.textLight, styles.description]}
-          richText={feed.description}
+          style={[t.atoms.text_contrast_high, styles.description]}
+          value={feed.description}
           numberOfLines={3}
         />
       ) : null}
diff --git a/src/view/com/lists/ListCard.tsx b/src/view/com/lists/ListCard.tsx
index 5750faec1..19842eb54 100644
--- a/src/view/com/lists/ListCard.tsx
+++ b/src/view/com/lists/ListCard.tsx
@@ -3,7 +3,7 @@ import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
 import {AtUri, AppBskyGraphDefs, RichText} from '@atproto/api'
 import {Link} from '../util/Link'
 import {Text} from '../util/text/Text'
-import {RichText as RichTextCom} from '../util/text/RichText'
+import {RichText as RichTextCom} from '#/components/RichText'
 import {UserAvatar} from '../util/UserAvatar'
 import {s} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -12,6 +12,7 @@ import {sanitizeDisplayName} from 'lib/strings/display-names'
 import {sanitizeHandle} from 'lib/strings/handles'
 import {makeProfileLink} from 'lib/routes/links'
 import {Trans} from '@lingui/macro'
+import {atoms as a} from '#/alf'
 
 export const ListCard = ({
   testID,
@@ -119,9 +120,9 @@ export const ListCard = ({
       {descriptionRichText ? (
         <View style={styles.details}>
           <RichTextCom
-            style={[pal.text, s.flex1]}
+            style={[a.flex_1]}
             numberOfLines={20}
-            richText={descriptionRichText}
+            value={descriptionRichText}
           />
         </View>
       ) : undefined}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index f9bdaebed..2ef1b1447 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -11,7 +11,7 @@ import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn'
 import {Link, TextLink} from '../util/Link'
-import {RichText} from '../util/text/RichText'
+import {RichText} from '#/components/RichText'
 import {Text} from '../util/text/Text'
 import {PreviewableUserAvatar} from '../util/UserAvatar'
 import {s} from 'lib/styles'
@@ -44,6 +44,7 @@ import {ThreadPost} from '#/state/queries/post-thread'
 import {useSession} from 'state/session'
 import {WhoCanReply} from '../threadgate/WhoCanReply'
 import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
+import {atoms as a} from '#/alf'
 
 export function PostThreadItem({
   post,
@@ -326,10 +327,8 @@ let PostThreadItemLoaded = ({
                     styles.postTextLargeContainer,
                   ]}>
                   <RichText
-                    type="post-text-lg"
-                    richText={richText}
-                    lineHeight={1.3}
-                    style={s.flex1}
+                    value={richText}
+                    style={[a.flex_1, a.text_xl]}
                     selectable
                   />
                 </View>
@@ -522,10 +521,8 @@ let PostThreadItemLoaded = ({
                 {richText?.text ? (
                   <View style={styles.postTextContainer}>
                     <RichText
-                      type="post-text"
-                      richText={richText}
-                      style={[pal.text, s.flex1]}
-                      lineHeight={1.3}
+                      value={richText}
+                      style={[a.flex_1, a.text_md]}
                       numberOfLines={limitLines ? MAX_POST_LINES : undefined}
                     />
                   </View>
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 8d0f2bef2..6f64de181 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -20,7 +20,7 @@ import {PostCtrls} from '../util/post-ctrls/PostCtrls'
 import {PostEmbeds} from '../util/post-embeds'
 import {ContentHider} from '../util/moderation/ContentHider'
 import {PostAlerts} from '../util/moderation/PostAlerts'
-import {RichText} from '../util/text/RichText'
+import {RichText} from '#/components/RichText'
 import {PreviewableUserAvatar} from '../util/UserAvatar'
 import {s} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -36,6 +36,7 @@ import {FeedNameText} from '../util/FeedInfoText'
 import {useSession} from '#/state/session'
 import {Trans, msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
+import {atoms as a} from '#/alf'
 
 export function FeedItem({
   post,
@@ -347,11 +348,9 @@ let PostContent = ({
         <View style={styles.postTextContainer}>
           <RichText
             testID="postText"
-            type="post-text"
-            richText={richText}
-            lineHeight={1.3}
+            value={richText}
             numberOfLines={limitLines ? MAX_POST_LINES : undefined}
-            style={s.flex1}
+            style={[a.flex_1, a.text_md]}
           />
         </View>
       ) : undefined}
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 8fd50fad6..3e479d7b5 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -23,7 +23,7 @@ import * as Toast from '../util/Toast'
 import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
 import {Text} from '../util/text/Text'
 import {ThemedText} from '../util/text/ThemedText'
-import {RichText} from '../util/text/RichText'
+import {RichText} from '#/components/RichText'
 import {UserAvatar} from '../util/UserAvatar'
 import {UserBanner} from '../util/UserBanner'
 import {ProfileHeaderAlerts} from '../util/moderation/ProfileHeaderAlerts'
@@ -56,6 +56,7 @@ import {Shadow} from '#/state/cache/types'
 import {useRequireAuth} from '#/state/session'
 import {LabelInfo} from '../util/moderation/LabelInfo'
 import {useProfileShadow} from 'state/cache/profile-shadow'
+import {atoms as a} from '#/alf'
 
 let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
   const pal = usePalette('default')
@@ -608,12 +609,12 @@ let ProfileHeader = ({
               </Text>
             </View>
             {descriptionRT && !moderation.profile.blur ? (
-              <View pointerEvents="auto">
+              <View pointerEvents="auto" style={[styles.description]}>
                 <RichText
                   testID="profileHeaderDescription"
-                  style={[styles.description, pal.text]}
+                  style={[a.text_md]}
                   numberOfLines={15}
-                  richText={descriptionRT}
+                  value={descriptionRT}
                 />
               </View>
             ) : undefined}
diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx
index d9d84feb4..c128a6f00 100644
--- a/src/view/com/util/post-embeds/QuoteEmbed.tsx
+++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx
@@ -20,7 +20,8 @@ import {PostAlerts} from '../moderation/PostAlerts'
 import {makeProfileLink} from 'lib/routes/links'
 import {InfoCircleIcon} from 'lib/icons'
 import {Trans} from '@lingui/macro'
-import {RichText} from 'view/com/util/text/RichText'
+import {RichText} from '#/components/RichText'
+import {atoms as a} from '#/alf'
 
 export function MaybeQuoteEmbed({
   embed,
@@ -127,11 +128,10 @@ export function QuoteEmbed({
       ) : null}
       {richText ? (
         <RichText
-          richText={richText}
-          type="post-text"
-          style={pal.text}
+          value={richText}
+          style={[a.text_md]}
           numberOfLines={20}
-          noLinks
+          disableLinks
         />
       ) : null}
       {embed && <PostEmbeds embed={embed} moderation={{}} />}
diff --git a/src/view/com/util/text/RichText.tsx b/src/view/com/util/text/RichText.tsx
index e910127fe..b6d461224 100644
--- a/src/view/com/util/text/RichText.tsx
+++ b/src/view/com/util/text/RichText.tsx
@@ -10,6 +10,9 @@ import {usePalette} from 'lib/hooks/usePalette'
 
 const WORD_WRAP = {wordWrap: 1}
 
+/**
+ * @deprecated use `#/components/RichText`
+ */
 export function RichText({
   testID,
   type = 'md',
diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx
index be9eec816..212c10e74 100644
--- a/src/view/screens/ProfileFeed.tsx
+++ b/src/view/screens/ProfileFeed.tsx
@@ -17,7 +17,7 @@ import {TextLink} from 'view/com/util/Link'
 import {ListRef} from 'view/com/util/List'
 import {Button} from 'view/com/util/forms/Button'
 import {Text} from 'view/com/util/text/Text'
-import {RichText} from 'view/com/util/text/RichText'
+import {RichText} from '#/components/RichText'
 import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
 import {FAB} from 'view/com/util/fab/FAB'
 import {EmptyState} from 'view/com/util/EmptyState'
@@ -59,6 +59,7 @@ import {useComposerControls} from '#/state/shell/composer'
 import {truncateAndInvalidate} from '#/state/queries/util'
 import {isNative} from '#/platform/detection'
 import {listenSoftReset} from '#/state/events'
+import {atoms as a} from '#/alf'
 
 const SECTION_TITLES = ['Posts', 'About']
 
@@ -575,9 +576,8 @@ function AboutSection({
         {feedInfo.description ? (
           <RichText
             testID="listDescription"
-            type="lg"
-            style={pal.text}
-            richText={feedInfo.description}
+            style={[a.text_md]}
+            value={feedInfo.description}
           />
         ) : (
           <Text type="lg" style={[{fontStyle: 'italic'}, pal.textLight]}>
diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx
index 796464883..d86b569e2 100644
--- a/src/view/screens/ProfileList.tsx
+++ b/src/view/screens/ProfileList.tsx
@@ -14,7 +14,7 @@ import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
 import {CenteredView} from 'view/com/util/Views'
 import {EmptyState} from 'view/com/util/EmptyState'
 import {LoadingScreen} from 'view/com/util/LoadingScreen'
-import {RichText} from 'view/com/util/text/RichText'
+import {RichText} from '#/components/RichText'
 import {Button} from 'view/com/util/forms/Button'
 import {TextLink} from 'view/com/util/Link'
 import {ListRef} from 'view/com/util/List'
@@ -60,6 +60,7 @@ import {
 import {logger} from '#/logger'
 import {useAnalytics} from '#/lib/analytics/analytics'
 import {listenSoftReset} from '#/state/events'
+import {atoms as a} from '#/alf'
 
 const SECTION_TITLES_CURATE = ['Posts', 'About']
 const SECTION_TITLES_MOD = ['About']
@@ -742,9 +743,8 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
             {descriptionRT ? (
               <RichText
                 testID="listDescription"
-                type="lg"
-                style={pal.text}
-                richText={descriptionRT}
+                style={[a.text_md]}
+                value={descriptionRT}
               />
             ) : (
               <Text
-- 
cgit 1.4.1

