···11+import React from 'react'
22+import {TextProps as RNTextProps} from 'react-native'
33+import {StyleProp, TextStyle} from 'react-native'
44+import {UITextView} from 'react-native-uitextview'
55+import createEmojiRegex from 'emoji-regex'
66+77+import {isNative} from '#/platform/detection'
88+import {Alf, applyFonts, atoms, flatten} from '#/alf'
99+1010+/**
1111+ * Util to calculate lineHeight from a text size atom and a leading atom
1212+ *
1313+ * Example:
1414+ * `leading(atoms.text_md, atoms.leading_normal)` // => 24
1515+ */
1616+export function leading<
1717+ Size extends {fontSize?: number},
1818+ Leading extends {lineHeight?: number},
1919+>(textSize: Size, leading: Leading) {
2020+ const size = textSize?.fontSize || atoms.text_md.fontSize
2121+ const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
2222+ return Math.round(size * lineHeight)
2323+}
2424+2525+/**
2626+ * Ensures that `lineHeight` defaults to a relative value of `1`, or applies
2727+ * other relative leading atoms.
2828+ *
2929+ * If the `lineHeight` value is > 2, we assume it's an absolute value and
3030+ * returns it as-is.
3131+ */
3232+export function normalizeTextStyles(
3333+ styles: StyleProp<TextStyle>,
3434+ {
3535+ fontScale,
3636+ fontFamily,
3737+ }: {
3838+ fontScale: number
3939+ fontFamily: Alf['fonts']['family']
4040+ } & Pick<Alf, 'flags'>,
4141+) {
4242+ const s = flatten(styles)
4343+ // should always be defined on these components
4444+ s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
4545+4646+ if (s?.lineHeight) {
4747+ if (s.lineHeight !== 0 && s.lineHeight <= 2) {
4848+ s.lineHeight = Math.round(s.fontSize * s.lineHeight)
4949+ }
5050+ } else if (!isNative) {
5151+ s.lineHeight = s.fontSize
5252+ }
5353+5454+ applyFonts(s, fontFamily)
5555+5656+ return s
5757+}
5858+5959+export type StringChild = string | (string | null)[]
6060+export type TextProps = Omit<RNTextProps, 'children'> & {
6161+ /**
6262+ * Lets the user select text, to use the native copy and paste functionality.
6363+ */
6464+ selectable?: boolean
6565+ /**
6666+ * Provides `data-*` attributes to the underlying `UITextView` component on
6767+ * web only.
6868+ */
6969+ dataSet?: Record<string, string | number | undefined>
7070+ /**
7171+ * Appears as a small tooltip on web hover.
7272+ */
7373+ title?: string
7474+} & (
7575+ | {
7676+ emoji?: true
7777+ children: StringChild
7878+ }
7979+ | {
8080+ emoji?: false
8181+ children: RNTextProps['children']
8282+ }
8383+ )
8484+8585+const EMOJI = createEmojiRegex()
8686+8787+export function childHasEmoji(children: React.ReactNode) {
8888+ return (Array.isArray(children) ? children : [children]).some(
8989+ child => typeof child === 'string' && createEmojiRegex().test(child),
9090+ )
9191+}
9292+9393+export function childIsString(
9494+ children: React.ReactNode,
9595+): children is StringChild {
9696+ return (
9797+ typeof children === 'string' ||
9898+ (Array.isArray(children) &&
9999+ children.every(child => typeof child === 'string' || child === null))
100100+ )
101101+}
102102+103103+export function renderChildrenWithEmoji(
104104+ children: StringChild,
105105+ props: Omit<TextProps, 'children'> = {},
106106+) {
107107+ const normalized = Array.isArray(children) ? children : [children]
108108+109109+ return (
110110+ <UITextView {...props}>
111111+ {normalized.map(child => {
112112+ if (typeof child !== 'string') return child
113113+114114+ const emojis = child.match(EMOJI)
115115+116116+ if (emojis === null) {
117117+ return child
118118+ }
119119+120120+ return child.split(EMOJI).map((stringPart, index) => (
121121+ <UITextView key={index} {...props}>
122122+ {stringPart}
123123+ {emojis[index] ? (
124124+ <UITextView
125125+ {...props}
126126+ style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
127127+ {emojis[index]}
128128+ </UITextView>
129129+ ) : null}
130130+ </UITextView>
131131+ ))
132132+ })}
133133+ </UITextView>
134134+ )
135135+}
+11-134
src/components/Typography.tsx
···11-import React from 'react'
22-import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native'
31import {UITextView} from 'react-native-uitextview'
44-import createEmojiRegex from 'emoji-regex'
5263import {logger} from '#/logger'
77-import {isIOS, isNative} from '#/platform/detection'
88-import {Alf, applyFonts, atoms, flatten, useAlf, useTheme, web} from '#/alf'
44+import {isIOS} from '#/platform/detection'
55+import {atoms, flatten, useAlf, useTheme, web} from '#/alf'
66+import {
77+ childHasEmoji,
88+ childIsString,
99+ normalizeTextStyles,
1010+ renderChildrenWithEmoji,
1111+ TextProps,
1212+} from '#/alf/typography'
913import {IS_DEV} from '#/env'
1010-1111-export type StringChild = string | (string | null)[]
1212-1313-export type TextProps = Omit<RNTextProps, 'children'> & {
1414- /**
1515- * Lets the user select text, to use the native copy and paste functionality.
1616- */
1717- selectable?: boolean
1818- /**
1919- * Provides `data-*` attributes to the underlying `UITextView` component on
2020- * web only.
2121- */
2222- dataSet?: Record<string, string | number | undefined>
2323- /**
2424- * Appears as a small tooltip on web hover.
2525- */
2626- title?: string
2727-} & (
2828- | {
2929- emoji?: true
3030- children: StringChild
3131- }
3232- | {
3333- emoji?: false
3434- children: RNTextProps['children']
3535- }
3636- )
3737-3838-const EMOJI = createEmojiRegex()
3939-4040-export function childHasEmoji(children: React.ReactNode) {
4141- return (Array.isArray(children) ? children : [children]).some(
4242- child => typeof child === 'string' && createEmojiRegex().test(child),
4343- )
4444-}
4545-4646-export function childIsString(
4747- children: React.ReactNode,
4848-): children is StringChild {
4949- return (
5050- typeof children === 'string' ||
5151- (Array.isArray(children) &&
5252- children.every(child => typeof child === 'string' || child === null))
5353- )
5454-}
5555-5656-export function renderChildrenWithEmoji(
5757- children: StringChild,
5858- props: Omit<TextProps, 'children'> = {},
5959-) {
6060- const normalized = Array.isArray(children) ? children : [children]
6161-6262- return (
6363- <UITextView {...props}>
6464- {normalized.map(child => {
6565- if (typeof child !== 'string') return child
6666-6767- const emojis = child.match(EMOJI)
6868-6969- if (emojis === null) {
7070- return child
7171- }
7272-7373- return child.split(EMOJI).map((stringPart, index) => (
7474- <UITextView key={index} {...props}>
7575- {stringPart}
7676- {emojis[index] ? (
7777- <UITextView
7878- {...props}
7979- style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
8080- {emojis[index]}
8181- </UITextView>
8282- ) : null}
8383- </UITextView>
8484- ))
8585- })}
8686- </UITextView>
8787- )
8888-}
8989-9090-/**
9191- * Util to calculate lineHeight from a text size atom and a leading atom
9292- *
9393- * Example:
9494- * `leading(atoms.text_md, atoms.leading_normal)` // => 24
9595- */
9696-export function leading<
9797- Size extends {fontSize?: number},
9898- Leading extends {lineHeight?: number},
9999->(textSize: Size, leading: Leading) {
100100- const size = textSize?.fontSize || atoms.text_md.fontSize
101101- const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
102102- return Math.round(size * lineHeight)
103103-}
104104-105105-/**
106106- * Ensures that `lineHeight` defaults to a relative value of `1`, or applies
107107- * other relative leading atoms.
108108- *
109109- * If the `lineHeight` value is > 2, we assume it's an absolute value and
110110- * returns it as-is.
111111- */
112112-export function normalizeTextStyles(
113113- styles: StyleProp<TextStyle>,
114114- {
115115- fontScale,
116116- fontFamily,
117117- }: {
118118- fontScale: number
119119- fontFamily: Alf['fonts']['family']
120120- } & Pick<Alf, 'flags'>,
121121-) {
122122- const s = flatten(styles)
123123- // should always be defined on these components
124124- s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
125125-126126- if (s?.lineHeight) {
127127- if (s.lineHeight !== 0 && s.lineHeight <= 2) {
128128- s.lineHeight = Math.round(s.fontSize * s.lineHeight)
129129- }
130130- } else if (!isNative) {
131131- s.lineHeight = s.fontSize
132132- }
133133-134134- applyFonts(s, fontFamily)
135135-136136- return s
137137-}
1414+export type {TextProps}
1381513916/**
14017 * Our main text component. Use this most of the time.
···18360 )
18461}
18562186186-export function createHeadingElement({level}: {level: number}) {
6363+function createHeadingElement({level}: {level: number}) {
18764 return function HeadingElement({style, ...rest}: TextProps) {
18865 const attr =
18966 web({
+2-1
src/screens/Onboarding/Layout.tsx
···1717 useTheme,
1818 web,
1919} from '#/alf'
2020+import {leading} from '#/alf/typography'
2021import {Button, ButtonIcon, ButtonText} from '#/components/Button'
2122import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
2223import {createPortalGroup} from '#/components/Portal'
2323-import {leading, P, Text} from '#/components/Typography'
2424+import {P, Text} from '#/components/Typography'
2425import {IS_DEV} from '#/env'
25262627const COL_WIDTH = 420
+1-1
src/view/com/composer/text-input/TextInput.tsx
···3131 suggestLinkCardUri,
3232} from '#/view/com/composer/text-input/text-input-util'
3333import {atoms as a, useAlf} from '#/alf'
3434-import {normalizeTextStyles} from '#/components/Typography'
3434+import {normalizeTextStyles} from '#/alf/typography'
3535import {Autocomplete} from './mobile/Autocomplete'
36363737export interface TextInputRef {
···2323} from '#/view/com/composer/text-input/text-input-util'
2424import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter'
2525import {atoms as a, useAlf} from '#/alf'
2626+import {normalizeTextStyles} from '#/alf/typography'
2627import {Portal} from '#/components/Portal'
2727-import {normalizeTextStyles} from '#/components/Typography'
2828import {Text} from '../../util/text/Text'
2929import {createSuggestion} from './web/Autocomplete'
3030import {Emoji} from './web/EmojiPicker.web'
+6-2
src/view/com/util/text/Text.tsx
···1212 childIsString,
1313 renderChildrenWithEmoji,
1414 StringChild,
1515-} from '#/components/Typography'
1515+} from '#/alf/typography'
1616import {IS_DEV} from '#/env'
17171818export type CustomTextProps = Omit<TextProps, 'children'> & {
···3232 }
3333 )
34343535-export function Text({
3535+export {Text_DEPRECATED as Text}
3636+/**
3737+ * @deprecated use Text from Typography instead.
3838+ */
3939+function Text_DEPRECATED({
3640 type = 'md',
3741 children,
3842 emoji,