Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

* Make new text selectable (broken)

* Fixes

* Fix bad conflict resolution

* Remove console

authored by

Eric Bailey and committed by
GitHub
943acd16 7390863a

+74 -69
+36 -41
src/components/Link.tsx
··· 1 1 import React from 'react' 2 - import { 3 - GestureResponderEvent, 4 - Linking, 5 - TouchableWithoutFeedback, 6 - } from 'react-native' 2 + import {GestureResponderEvent, Linking} from 'react-native' 7 3 import { 8 4 useLinkProps, 9 5 useNavigation, ··· 23 19 } from '#/lib/strings/url-helpers' 24 20 import {useModalControls} from '#/state/modals' 25 21 import {router} from '#/routes' 26 - import {Text} from '#/components/Typography' 22 + import {Text, TextProps} from '#/components/Typography' 27 23 28 24 /** 29 25 * Only available within a `Link`, since that inherits from `Button`. ··· 217 213 } 218 214 219 215 export type InlineLinkProps = React.PropsWithChildren< 220 - BaseLinkProps & TextStyleProp 216 + BaseLinkProps & TextStyleProp & Pick<TextProps, 'selectable'> 221 217 > 222 218 223 219 export function InlineLink({ ··· 228 224 style, 229 225 onPress: outerOnPress, 230 226 download, 227 + selectable, 231 228 ...rest 232 229 }: InlineLinkProps) { 233 230 const t = useTheme() ··· 253 250 const flattenedStyle = flatten(style) 254 251 255 252 return ( 256 - <TouchableWithoutFeedback 257 - accessibilityRole="button" 253 + <Text 254 + selectable={selectable} 255 + label={href} 256 + {...rest} 257 + style={[ 258 + {color: t.palette.primary_500}, 259 + (hovered || focused || pressed) && { 260 + outline: 0, 261 + textDecorationLine: 'underline', 262 + textDecorationColor: flattenedStyle.color ?? t.palette.primary_500, 263 + }, 264 + flattenedStyle, 265 + ]} 266 + role="link" 258 267 onPress={download ? undefined : onPress} 259 268 onPressIn={onPressIn} 260 269 onPressOut={onPressOut} 261 270 onFocus={onFocus} 262 - onBlur={onBlur}> 263 - <Text 264 - label={href} 265 - {...rest} 266 - style={[ 267 - {color: t.palette.primary_500}, 268 - (hovered || focused || pressed) && { 269 - outline: 0, 270 - textDecorationLine: 'underline', 271 - textDecorationColor: flattenedStyle.color ?? t.palette.primary_500, 272 - }, 273 - flattenedStyle, 274 - ]} 275 - role="link" 276 - onMouseEnter={onHoverIn} 277 - onMouseLeave={onHoverOut} 278 - accessibilityRole="link" 279 - href={href} 280 - {...web({ 281 - hrefAttrs: { 282 - target: download ? undefined : isExternal ? 'blank' : undefined, 283 - rel: isExternal ? 'noopener noreferrer' : undefined, 284 - download, 285 - }, 286 - dataSet: { 287 - // default to no underline, apply this ourselves 288 - noUnderline: '1', 289 - }, 290 - })}> 291 - {children} 292 - </Text> 293 - </TouchableWithoutFeedback> 271 + onBlur={onBlur} 272 + onMouseEnter={onHoverIn} 273 + onMouseLeave={onHoverOut} 274 + accessibilityRole="link" 275 + href={href} 276 + {...web({ 277 + hrefAttrs: { 278 + target: download ? undefined : isExternal ? 'blank' : undefined, 279 + rel: isExternal ? 'noopener noreferrer' : undefined, 280 + download, 281 + }, 282 + dataSet: { 283 + // default to no underline, apply this ourselves 284 + noUnderline: '1', 285 + }, 286 + })}> 287 + {children} 288 + </Text> 294 289 ) 295 290 }
+15 -8
src/components/RichText.tsx
··· 3 3 4 4 import {atoms as a, TextStyleProp} from '#/alf' 5 5 import {InlineLink} from '#/components/Link' 6 - import {Text} from '#/components/Typography' 6 + import {Text, TextProps} from '#/components/Typography' 7 7 import {toShortUrl} from 'lib/strings/url-helpers' 8 8 import {getAgent} from '#/state/session' 9 9 ··· 16 16 numberOfLines, 17 17 disableLinks, 18 18 resolveFacets = false, 19 - }: TextStyleProp & { 20 - value: RichTextAPI | string 21 - testID?: string 22 - numberOfLines?: number 23 - disableLinks?: boolean 24 - resolveFacets?: boolean 25 - }) { 19 + selectable, 20 + }: TextStyleProp & 21 + Pick<TextProps, 'selectable'> & { 22 + value: RichTextAPI | string 23 + testID?: string 24 + numberOfLines?: number 25 + disableLinks?: boolean 26 + resolveFacets?: boolean 27 + }) { 26 28 const detected = React.useRef(false) 27 29 const [richText, setRichText] = React.useState<RichTextAPI>(() => 28 30 value instanceof RichTextAPI ? value : new RichTextAPI({text: value}), ··· 50 52 if (text.length <= 5 && /^\p{Extended_Pictographic}+$/u.test(text)) { 51 53 return ( 52 54 <Text 55 + selectable={selectable} 53 56 testID={testID} 54 57 style={[ 55 58 { ··· 65 68 } 66 69 return ( 67 70 <Text 71 + selectable={selectable} 68 72 testID={testID} 69 73 style={styles} 70 74 numberOfLines={numberOfLines} ··· 88 92 ) { 89 93 els.push( 90 94 <InlineLink 95 + selectable={selectable} 91 96 key={key} 92 97 to={`/profile/${mention.did}`} 93 98 style={[...styles, {pointerEvents: 'auto'}]} ··· 102 107 } else { 103 108 els.push( 104 109 <InlineLink 110 + selectable={selectable} 105 111 key={key} 106 112 to={link.uri} 107 113 style={[...styles, {pointerEvents: 'auto'}]} ··· 120 126 121 127 return ( 122 128 <Text 129 + selectable={selectable} 123 130 testID={testID} 124 131 style={styles} 125 132 numberOfLines={numberOfLines}
+19 -19
src/components/Typography.tsx
··· 1 1 import React from 'react' 2 - import {Text as RNText, TextStyle, TextProps} from 'react-native' 2 + import {Text as RNText, TextStyle, TextProps as RNTextProps} from 'react-native' 3 + import {UITextView} from 'react-native-ui-text-view' 3 4 4 5 import {useTheme, atoms, web, flatten} from '#/alf' 6 + import {isIOS} from '#/platform/detection' 7 + 8 + export type TextProps = RNTextProps & { 9 + /** 10 + * Lets the user select text, to use the native copy and paste functionality. 11 + */ 12 + selectable?: boolean 13 + } 5 14 6 15 /** 7 16 * Util to calculate lineHeight from a text size atom and a leading atom ··· 44 53 /** 45 54 * Our main text component. Use this most of the time. 46 55 */ 47 - export function Text({style, ...rest}: TextProps) { 56 + export function Text({style, selectable, ...rest}: TextProps) { 48 57 const t = useTheme() 49 58 const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)]) 50 - return <RNText style={s} {...rest} /> 59 + return selectable && isIOS ? ( 60 + <UITextView style={s} {...rest} /> 61 + ) : ( 62 + <RNText selectable={selectable} style={s} {...rest} /> 63 + ) 51 64 } 52 65 53 66 export function createHeadingElement({level}: {level: number}) { 54 67 return function HeadingElement({style, ...rest}: TextProps) { 55 - const t = useTheme() 56 68 const attr = 57 69 web({ 58 70 role: 'heading', 59 71 'aria-level': level, 60 72 }) || {} 61 - return ( 62 - <RNText 63 - {...attr} 64 - {...rest} 65 - style={normalizeTextStyles([t.atoms.text, flatten(style)])} 66 - /> 67 - ) 73 + return <Text {...attr} {...rest} style={style} /> 68 74 } 69 75 } 70 76 ··· 78 84 export const H5 = createHeadingElement({level: 5}) 79 85 export const H6 = createHeadingElement({level: 6}) 80 86 export function P({style, ...rest}: TextProps) { 81 - const t = useTheme() 82 87 const attr = 83 88 web({ 84 89 role: 'paragraph', 85 90 }) || {} 86 91 return ( 87 - <RNText 92 + <Text 88 93 {...attr} 89 94 {...rest} 90 - style={normalizeTextStyles([ 91 - atoms.text_md, 92 - atoms.leading_normal, 93 - t.atoms.text, 94 - flatten(style), 95 - ])} 95 + style={[atoms.text_md, atoms.leading_normal, flatten(style)]} 96 96 /> 97 97 ) 98 98 }
+4 -1
src/view/screens/Storybook/Typography.tsx
··· 8 8 export function Typography() { 9 9 return ( 10 10 <View style={[a.gap_md]}> 11 - <Text style={[a.text_5xl]}>atoms.text_5xl</Text> 11 + <Text selectable style={[a.text_5xl]}> 12 + atoms.text_5xl 13 + </Text> 12 14 <Text style={[a.text_4xl]}>atoms.text_4xl</Text> 13 15 <Text style={[a.text_3xl]}>atoms.text_3xl</Text> 14 16 <Text style={[a.text_2xl]}>atoms.text_2xl</Text> ··· 24 26 value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`} 25 27 /> 26 28 <RichText 29 + selectable 27 30 resolveFacets 28 31 value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`} 29 32 style={[a.text_xl]}