about summary refs log tree commit diff
path: root/src/components/RichText.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/RichText.tsx')
-rw-r--r--src/components/RichText.tsx103
1 files changed, 102 insertions, 1 deletions
diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx
index c72fcabdd..22391cb24 100644
--- a/src/components/RichText.tsx
+++ b/src/components/RichText.tsx
@@ -1,11 +1,16 @@
 import React from 'react'
 import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api'
+import {useLingui} from '@lingui/react'
+import {msg} from '@lingui/macro'
 
-import {atoms as a, TextStyleProp, flatten} from '#/alf'
+import {atoms as a, TextStyleProp, flatten, useTheme, web, native} from '#/alf'
 import {InlineLink} from '#/components/Link'
 import {Text, TextProps} from '#/components/Typography'
 import {toShortUrl} from 'lib/strings/url-helpers'
 import {getAgent} from '#/state/session'
+import {TagMenu, useTagMenuControl} from '#/components/TagMenu'
+import {isNative} from '#/platform/detection'
+import {useInteractionState} from '#/components/hooks/useInteractionState'
 
 const WORD_WRAP = {wordWrap: 1}
 
@@ -17,6 +22,8 @@ export function RichText({
   disableLinks,
   resolveFacets = false,
   selectable,
+  enableTags = false,
+  authorHandle,
 }: TextStyleProp &
   Pick<TextProps, 'selectable'> & {
     value: RichTextAPI | string
@@ -24,6 +31,8 @@ export function RichText({
     numberOfLines?: number
     disableLinks?: boolean
     resolveFacets?: boolean
+    enableTags?: boolean
+    authorHandle?: string
   }) {
   const detected = React.useRef(false)
   const [richText, setRichText] = React.useState<RichTextAPI>(() =>
@@ -85,6 +94,7 @@ export function RichText({
   for (const segment of richText.segments()) {
     const link = segment.link
     const mention = segment.mention
+    const tag = segment.tag
     if (
       mention &&
       AppBskyRichtextFacet.validateMention(mention).success &&
@@ -118,6 +128,21 @@ export function RichText({
           </InlineLink>,
         )
       }
+    } else if (
+      !disableLinks &&
+      enableTags &&
+      tag &&
+      AppBskyRichtextFacet.validateTag(tag).success
+    ) {
+      els.push(
+        <RichTextTag
+          key={key}
+          text={segment.text}
+          style={styles}
+          selectable={selectable}
+          authorHandle={authorHandle}
+        />,
+      )
     } else {
       els.push(segment.text)
     }
@@ -136,3 +161,79 @@ export function RichText({
     </Text>
   )
 }
+
+function RichTextTag({
+  text: tag,
+  style,
+  selectable,
+  authorHandle,
+}: {
+  text: string
+  selectable?: boolean
+  authorHandle?: string
+} & TextStyleProp) {
+  const t = useTheme()
+  const {_} = useLingui()
+  const control = useTagMenuControl()
+  const {
+    state: hovered,
+    onIn: onHoverIn,
+    onOut: onHoverOut,
+  } = useInteractionState()
+  const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+  const {
+    state: pressed,
+    onIn: onPressIn,
+    onOut: onPressOut,
+  } = useInteractionState()
+
+  const open = React.useCallback(() => {
+    control.open()
+  }, [control])
+
+  /*
+   * N.B. On web, this is wrapped in another pressable comopnent with a11y
+   * labels, etc. That's why only some of these props are applied here.
+   */
+
+  return (
+    <React.Fragment>
+      <TagMenu control={control} tag={tag} authorHandle={authorHandle}>
+        <Text
+          selectable={selectable}
+          {...native({
+            accessibilityLabel: _(msg`Hashtag: ${tag}`),
+            accessibilityHint: _(msg`Click here to open tag menu for ${tag}`),
+            accessibilityRole: isNative ? 'button' : undefined,
+            onPress: open,
+            onPressIn: onPressIn,
+            onPressOut: onPressOut,
+          })}
+          {...web({
+            onMouseEnter: onHoverIn,
+            onMouseLeave: onHoverOut,
+          })}
+          // @ts-ignore
+          onFocus={onFocus}
+          onBlur={onBlur}
+          style={[
+            style,
+            {
+              pointerEvents: 'auto',
+              color: t.palette.primary_500,
+            },
+            web({
+              cursor: 'pointer',
+            }),
+            (hovered || focused || pressed) && {
+              ...web({outline: 0}),
+              textDecorationLine: 'underline',
+              textDecorationColor: t.palette.primary_500,
+            },
+          ]}>
+          {tag}
+        </Text>
+      </TagMenu>
+    </React.Fragment>
+  )
+}