Add `selectable` to new text components (#2899)

* Make new text selectable (broken)

* Fixes

* Fix bad conflict resolution

* Remove console
zio/stable
Eric Bailey 2024-02-19 10:08:21 -06:00 committed by GitHub
parent 7390863a10
commit 943acd16aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 74 additions and 69 deletions

View File

@ -1,9 +1,5 @@
import React from 'react' import React from 'react'
import { import {GestureResponderEvent, Linking} from 'react-native'
GestureResponderEvent,
Linking,
TouchableWithoutFeedback,
} from 'react-native'
import { import {
useLinkProps, useLinkProps,
useNavigation, useNavigation,
@ -23,7 +19,7 @@ import {
} from '#/lib/strings/url-helpers' } from '#/lib/strings/url-helpers'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {router} from '#/routes' 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`. * Only available within a `Link`, since that inherits from `Button`.
@ -217,7 +213,7 @@ export function Link({
} }
export type InlineLinkProps = React.PropsWithChildren< export type InlineLinkProps = React.PropsWithChildren<
BaseLinkProps & TextStyleProp BaseLinkProps & TextStyleProp & Pick<TextProps, 'selectable'>
> >
export function InlineLink({ export function InlineLink({
@ -228,6 +224,7 @@ export function InlineLink({
style, style,
onPress: outerOnPress, onPress: outerOnPress,
download, download,
selectable,
...rest ...rest
}: InlineLinkProps) { }: InlineLinkProps) {
const t = useTheme() const t = useTheme()
@ -253,14 +250,8 @@ export function InlineLink({
const flattenedStyle = flatten(style) const flattenedStyle = flatten(style)
return ( return (
<TouchableWithoutFeedback
accessibilityRole="button"
onPress={download ? undefined : onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
onFocus={onFocus}
onBlur={onBlur}>
<Text <Text
selectable={selectable}
label={href} label={href}
{...rest} {...rest}
style={[ style={[
@ -273,6 +264,11 @@ export function InlineLink({
flattenedStyle, flattenedStyle,
]} ]}
role="link" role="link"
onPress={download ? undefined : onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
onFocus={onFocus}
onBlur={onBlur}
onMouseEnter={onHoverIn} onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut} onMouseLeave={onHoverOut}
accessibilityRole="link" accessibilityRole="link"
@ -290,6 +286,5 @@ export function InlineLink({
})}> })}>
{children} {children}
</Text> </Text>
</TouchableWithoutFeedback>
) )
} }

View File

@ -3,7 +3,7 @@ import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api'
import {atoms as a, TextStyleProp} from '#/alf' import {atoms as a, TextStyleProp} from '#/alf'
import {InlineLink} from '#/components/Link' import {InlineLink} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text, TextProps} from '#/components/Typography'
import {toShortUrl} from 'lib/strings/url-helpers' import {toShortUrl} from 'lib/strings/url-helpers'
import {getAgent} from '#/state/session' import {getAgent} from '#/state/session'
@ -16,13 +16,15 @@ export function RichText({
numberOfLines, numberOfLines,
disableLinks, disableLinks,
resolveFacets = false, resolveFacets = false,
}: TextStyleProp & { selectable,
}: TextStyleProp &
Pick<TextProps, 'selectable'> & {
value: RichTextAPI | string value: RichTextAPI | string
testID?: string testID?: string
numberOfLines?: number numberOfLines?: number
disableLinks?: boolean disableLinks?: boolean
resolveFacets?: boolean resolveFacets?: boolean
}) { }) {
const detected = React.useRef(false) const detected = React.useRef(false)
const [richText, setRichText] = React.useState<RichTextAPI>(() => const [richText, setRichText] = React.useState<RichTextAPI>(() =>
value instanceof RichTextAPI ? value : new RichTextAPI({text: value}), value instanceof RichTextAPI ? value : new RichTextAPI({text: value}),
@ -50,6 +52,7 @@ export function RichText({
if (text.length <= 5 && /^\p{Extended_Pictographic}+$/u.test(text)) { if (text.length <= 5 && /^\p{Extended_Pictographic}+$/u.test(text)) {
return ( return (
<Text <Text
selectable={selectable}
testID={testID} testID={testID}
style={[ style={[
{ {
@ -65,6 +68,7 @@ export function RichText({
} }
return ( return (
<Text <Text
selectable={selectable}
testID={testID} testID={testID}
style={styles} style={styles}
numberOfLines={numberOfLines} numberOfLines={numberOfLines}
@ -88,6 +92,7 @@ export function RichText({
) { ) {
els.push( els.push(
<InlineLink <InlineLink
selectable={selectable}
key={key} key={key}
to={`/profile/${mention.did}`} to={`/profile/${mention.did}`}
style={[...styles, {pointerEvents: 'auto'}]} style={[...styles, {pointerEvents: 'auto'}]}
@ -102,6 +107,7 @@ export function RichText({
} else { } else {
els.push( els.push(
<InlineLink <InlineLink
selectable={selectable}
key={key} key={key}
to={link.uri} to={link.uri}
style={[...styles, {pointerEvents: 'auto'}]} style={[...styles, {pointerEvents: 'auto'}]}
@ -120,6 +126,7 @@ export function RichText({
return ( return (
<Text <Text
selectable={selectable}
testID={testID} testID={testID}
style={styles} style={styles}
numberOfLines={numberOfLines} numberOfLines={numberOfLines}

View File

@ -1,7 +1,16 @@
import React from 'react' import React from 'react'
import {Text as RNText, TextStyle, TextProps} from 'react-native' import {Text as RNText, TextStyle, TextProps as RNTextProps} from 'react-native'
import {UITextView} from 'react-native-ui-text-view'
import {useTheme, atoms, web, flatten} from '#/alf' import {useTheme, atoms, web, flatten} from '#/alf'
import {isIOS} from '#/platform/detection'
export type TextProps = RNTextProps & {
/**
* Lets the user select text, to use the native copy and paste functionality.
*/
selectable?: boolean
}
/** /**
* Util to calculate lineHeight from a text size atom and a leading atom * Util to calculate lineHeight from a text size atom and a leading atom
@ -44,27 +53,24 @@ function normalizeTextStyles(styles: TextStyle[]) {
/** /**
* Our main text component. Use this most of the time. * Our main text component. Use this most of the time.
*/ */
export function Text({style, ...rest}: TextProps) { export function Text({style, selectable, ...rest}: TextProps) {
const t = useTheme() const t = useTheme()
const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)]) const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)])
return <RNText style={s} {...rest} /> return selectable && isIOS ? (
<UITextView style={s} {...rest} />
) : (
<RNText selectable={selectable} style={s} {...rest} />
)
} }
export function createHeadingElement({level}: {level: number}) { export function createHeadingElement({level}: {level: number}) {
return function HeadingElement({style, ...rest}: TextProps) { return function HeadingElement({style, ...rest}: TextProps) {
const t = useTheme()
const attr = const attr =
web({ web({
role: 'heading', role: 'heading',
'aria-level': level, 'aria-level': level,
}) || {} }) || {}
return ( return <Text {...attr} {...rest} style={style} />
<RNText
{...attr}
{...rest}
style={normalizeTextStyles([t.atoms.text, flatten(style)])}
/>
)
} }
} }
@ -78,21 +84,15 @@ export const H4 = createHeadingElement({level: 4})
export const H5 = createHeadingElement({level: 5}) export const H5 = createHeadingElement({level: 5})
export const H6 = createHeadingElement({level: 6}) export const H6 = createHeadingElement({level: 6})
export function P({style, ...rest}: TextProps) { export function P({style, ...rest}: TextProps) {
const t = useTheme()
const attr = const attr =
web({ web({
role: 'paragraph', role: 'paragraph',
}) || {} }) || {}
return ( return (
<RNText <Text
{...attr} {...attr}
{...rest} {...rest}
style={normalizeTextStyles([ style={[atoms.text_md, atoms.leading_normal, flatten(style)]}
atoms.text_md,
atoms.leading_normal,
t.atoms.text,
flatten(style),
])}
/> />
) )
} }

View File

@ -8,7 +8,9 @@ import {RichText} from '#/components/RichText'
export function Typography() { export function Typography() {
return ( return (
<View style={[a.gap_md]}> <View style={[a.gap_md]}>
<Text style={[a.text_5xl]}>atoms.text_5xl</Text> <Text selectable style={[a.text_5xl]}>
atoms.text_5xl
</Text>
<Text style={[a.text_4xl]}>atoms.text_4xl</Text> <Text style={[a.text_4xl]}>atoms.text_4xl</Text>
<Text style={[a.text_3xl]}>atoms.text_3xl</Text> <Text style={[a.text_3xl]}>atoms.text_3xl</Text>
<Text style={[a.text_2xl]}>atoms.text_2xl</Text> <Text style={[a.text_2xl]}>atoms.text_2xl</Text>
@ -24,6 +26,7 @@ export function Typography() {
value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`} value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
/> />
<RichText <RichText
selectable
resolveFacets resolveFacets
value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`} value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
style={[a.text_xl]} style={[a.text_xl]}