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