Bluesky app fork with some witchin' additions 馃挮
at main 2.9 kB view raw
1import {Children} from 'react' 2import {type TextProps as RNTextProps} from 'react-native' 3import {type StyleProp, type TextStyle} from 'react-native' 4import {UITextView} from 'react-native-uitextview' 5import createEmojiRegex from 'emoji-regex' 6 7import {isNative} from '#/platform/detection' 8import {isIOS} from '#/platform/detection' 9import {type Alf, applyFonts, atoms, flatten} from '#/alf' 10 11/** 12 * Ensures that `lineHeight` defaults to a relative value of `1`, or applies 13 * other relative leading atoms. 14 * 15 * If the `lineHeight` value is > 2, we assume it's an absolute value and 16 * returns it as-is. 17 */ 18export function normalizeTextStyles( 19 styles: StyleProp<TextStyle>, 20 { 21 fontScale, 22 fontFamily, 23 }: { 24 fontScale: number 25 fontFamily: Alf['fonts']['family'] 26 } & Pick<Alf, 'flags'>, 27) { 28 const s = flatten(styles) ?? {} 29 30 // should always be defined on these components 31 s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale 32 33 if (s?.lineHeight) { 34 if (s.lineHeight !== 0 && s.lineHeight <= 2) { 35 s.lineHeight = Math.round(s.fontSize * s.lineHeight) 36 } 37 } else if (!isNative) { 38 s.lineHeight = s.fontSize 39 } 40 41 applyFonts(s, fontFamily) 42 43 return s 44} 45 46export type StringChild = string | (string | null)[] 47export type TextProps = RNTextProps & { 48 /** 49 * Lets the user select text, to use the native copy and paste functionality. 50 */ 51 selectable?: boolean 52 /** 53 * Provides `data-*` attributes to the underlying `UITextView` component on 54 * web only. 55 */ 56 dataSet?: Record<string, string | number | undefined> 57 /** 58 * Appears as a small tooltip on web hover. 59 */ 60 title?: string 61 /** 62 * Whether the children could possibly contain emoji. 63 */ 64 emoji?: boolean 65} 66 67const EMOJI = createEmojiRegex() 68 69export function childHasEmoji(children: React.ReactNode) { 70 let hasEmoji = false 71 Children.forEach(children, child => { 72 if (typeof child === 'string' && createEmojiRegex().test(child)) { 73 hasEmoji = true 74 } 75 }) 76 return hasEmoji 77} 78 79export function renderChildrenWithEmoji( 80 children: React.ReactNode, 81 props: Omit<TextProps, 'children'> = {}, 82 emoji: boolean, 83) { 84 if (!isIOS || !emoji) { 85 return children 86 } 87 return Children.map(children, child => { 88 if (typeof child !== 'string') return child 89 90 const emojis = child.match(EMOJI) 91 92 if (emojis === null) { 93 return child 94 } 95 96 return child.split(EMOJI).map((stringPart, index) => [ 97 stringPart, 98 emojis[index] ? ( 99 <UITextView 100 {...props} 101 style={[props?.style, {fontFamily: 'System'}]} 102 key={index}> 103 {emojis[index]} 104 </UITextView> 105 ) : null, 106 ]) 107 }) 108} 109 110const SINGLE_EMOJI_RE = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u 111export function isOnlyEmoji(text: string) { 112 return text.length <= 15 && SINGLE_EMOJI_RE.test(text) 113}