diff options
Diffstat (limited to 'src/components/RichText.tsx')
-rw-r--r-- | src/components/RichText.tsx | 103 |
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> + ) +} |