diff options
author | dan <dan.abramov@gmail.com> | 2024-11-21 20:44:40 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-21 20:44:40 +0000 |
commit | cc60566cde880deeea1e50774a2108af8f7766f8 (patch) | |
tree | 5b6c925bf4331907fe57a094e30d6a23615debf4 /src | |
parent | ff23ddb556be4b2a9c4029dce6f857df34fc0b6b (diff) | |
download | voidsky-cc60566cde880deeea1e50774a2108af8f7766f8.tar.zst |
Fix Fast Refresh in <Text> files (#6609)
* Separate non-components from components * Mark old Text as deprecated * Move emoji utilities to non-React file * Fix type * Fix import
Diffstat (limited to 'src')
-rw-r--r-- | src/alf/typography.tsx | 135 | ||||
-rw-r--r-- | src/components/Typography.tsx | 145 | ||||
-rw-r--r-- | src/screens/Onboarding/Layout.tsx | 3 | ||||
-rw-r--r-- | src/view/com/composer/text-input/TextInput.tsx | 2 | ||||
-rw-r--r-- | src/view/com/composer/text-input/TextInput.web.tsx | 2 | ||||
-rw-r--r-- | src/view/com/util/text/Text.tsx | 8 |
6 files changed, 156 insertions, 139 deletions
diff --git a/src/alf/typography.tsx b/src/alf/typography.tsx new file mode 100644 index 000000000..d00910bf8 --- /dev/null +++ b/src/alf/typography.tsx @@ -0,0 +1,135 @@ +import React from 'react' +import {TextProps as RNTextProps} from 'react-native' +import {StyleProp, TextStyle} from 'react-native' +import {UITextView} from 'react-native-uitextview' +import createEmojiRegex from 'emoji-regex' + +import {isNative} from '#/platform/detection' +import {Alf, applyFonts, atoms, flatten} from '#/alf' + +/** + * Util to calculate lineHeight from a text size atom and a leading atom + * + * Example: + * `leading(atoms.text_md, atoms.leading_normal)` // => 24 + */ +export function leading< + Size extends {fontSize?: number}, + Leading extends {lineHeight?: number}, +>(textSize: Size, leading: Leading) { + const size = textSize?.fontSize || atoms.text_md.fontSize + const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight + return Math.round(size * lineHeight) +} + +/** + * Ensures that `lineHeight` defaults to a relative value of `1`, or applies + * other relative leading atoms. + * + * If the `lineHeight` value is > 2, we assume it's an absolute value and + * returns it as-is. + */ +export function normalizeTextStyles( + styles: StyleProp<TextStyle>, + { + fontScale, + fontFamily, + }: { + fontScale: number + fontFamily: Alf['fonts']['family'] + } & Pick<Alf, 'flags'>, +) { + const s = flatten(styles) + // should always be defined on these components + s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale + + if (s?.lineHeight) { + if (s.lineHeight !== 0 && s.lineHeight <= 2) { + s.lineHeight = Math.round(s.fontSize * s.lineHeight) + } + } else if (!isNative) { + s.lineHeight = s.fontSize + } + + applyFonts(s, fontFamily) + + return s +} + +export type StringChild = string | (string | null)[] +export type TextProps = Omit<RNTextProps, 'children'> & { + /** + * Lets the user select text, to use the native copy and paste functionality. + */ + selectable?: boolean + /** + * Provides `data-*` attributes to the underlying `UITextView` component on + * web only. + */ + dataSet?: Record<string, string | number | undefined> + /** + * Appears as a small tooltip on web hover. + */ + title?: string +} & ( + | { + emoji?: true + children: StringChild + } + | { + emoji?: false + children: RNTextProps['children'] + } + ) + +const EMOJI = createEmojiRegex() + +export function childHasEmoji(children: React.ReactNode) { + return (Array.isArray(children) ? children : [children]).some( + child => typeof child === 'string' && createEmojiRegex().test(child), + ) +} + +export function childIsString( + children: React.ReactNode, +): children is StringChild { + return ( + typeof children === 'string' || + (Array.isArray(children) && + children.every(child => typeof child === 'string' || child === null)) + ) +} + +export function renderChildrenWithEmoji( + children: StringChild, + props: Omit<TextProps, 'children'> = {}, +) { + const normalized = Array.isArray(children) ? children : [children] + + return ( + <UITextView {...props}> + {normalized.map(child => { + if (typeof child !== 'string') return child + + const emojis = child.match(EMOJI) + + if (emojis === null) { + return child + } + + return child.split(EMOJI).map((stringPart, index) => ( + <UITextView key={index} {...props}> + {stringPart} + {emojis[index] ? ( + <UITextView + {...props} + style={[props?.style, {color: 'black', fontFamily: 'System'}]}> + {emojis[index]} + </UITextView> + ) : null} + </UITextView> + )) + })} + </UITextView> + ) +} diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx index 69e073271..4bcbf9f09 100644 --- a/src/components/Typography.tsx +++ b/src/components/Typography.tsx @@ -1,140 +1,17 @@ -import React from 'react' -import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native' import {UITextView} from 'react-native-uitextview' -import createEmojiRegex from 'emoji-regex' import {logger} from '#/logger' -import {isIOS, isNative} from '#/platform/detection' -import {Alf, applyFonts, atoms, flatten, useAlf, useTheme, web} from '#/alf' +import {isIOS} from '#/platform/detection' +import {atoms, flatten, useAlf, useTheme, web} from '#/alf' +import { + childHasEmoji, + childIsString, + normalizeTextStyles, + renderChildrenWithEmoji, + TextProps, +} from '#/alf/typography' import {IS_DEV} from '#/env' - -export type StringChild = string | (string | null)[] - -export type TextProps = Omit<RNTextProps, 'children'> & { - /** - * Lets the user select text, to use the native copy and paste functionality. - */ - selectable?: boolean - /** - * Provides `data-*` attributes to the underlying `UITextView` component on - * web only. - */ - dataSet?: Record<string, string | number | undefined> - /** - * Appears as a small tooltip on web hover. - */ - title?: string -} & ( - | { - emoji?: true - children: StringChild - } - | { - emoji?: false - children: RNTextProps['children'] - } - ) - -const EMOJI = createEmojiRegex() - -export function childHasEmoji(children: React.ReactNode) { - return (Array.isArray(children) ? children : [children]).some( - child => typeof child === 'string' && createEmojiRegex().test(child), - ) -} - -export function childIsString( - children: React.ReactNode, -): children is StringChild { - return ( - typeof children === 'string' || - (Array.isArray(children) && - children.every(child => typeof child === 'string' || child === null)) - ) -} - -export function renderChildrenWithEmoji( - children: StringChild, - props: Omit<TextProps, 'children'> = {}, -) { - const normalized = Array.isArray(children) ? children : [children] - - return ( - <UITextView {...props}> - {normalized.map(child => { - if (typeof child !== 'string') return child - - const emojis = child.match(EMOJI) - - if (emojis === null) { - return child - } - - return child.split(EMOJI).map((stringPart, index) => ( - <UITextView key={index} {...props}> - {stringPart} - {emojis[index] ? ( - <UITextView - {...props} - style={[props?.style, {color: 'black', fontFamily: 'System'}]}> - {emojis[index]} - </UITextView> - ) : null} - </UITextView> - )) - })} - </UITextView> - ) -} - -/** - * Util to calculate lineHeight from a text size atom and a leading atom - * - * Example: - * `leading(atoms.text_md, atoms.leading_normal)` // => 24 - */ -export function leading< - Size extends {fontSize?: number}, - Leading extends {lineHeight?: number}, ->(textSize: Size, leading: Leading) { - const size = textSize?.fontSize || atoms.text_md.fontSize - const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight - return Math.round(size * lineHeight) -} - -/** - * Ensures that `lineHeight` defaults to a relative value of `1`, or applies - * other relative leading atoms. - * - * If the `lineHeight` value is > 2, we assume it's an absolute value and - * returns it as-is. - */ -export function normalizeTextStyles( - styles: StyleProp<TextStyle>, - { - fontScale, - fontFamily, - }: { - fontScale: number - fontFamily: Alf['fonts']['family'] - } & Pick<Alf, 'flags'>, -) { - const s = flatten(styles) - // should always be defined on these components - s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale - - if (s?.lineHeight) { - if (s.lineHeight !== 0 && s.lineHeight <= 2) { - s.lineHeight = Math.round(s.fontSize * s.lineHeight) - } - } else if (!isNative) { - s.lineHeight = s.fontSize - } - - applyFonts(s, fontFamily) - - return s -} +export type {TextProps} /** * Our main text component. Use this most of the time. @@ -183,7 +60,7 @@ export function Text({ ) } -export function createHeadingElement({level}: {level: number}) { +function createHeadingElement({level}: {level: number}) { return function HeadingElement({style, ...rest}: TextProps) { const attr = web({ diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx index 02b207303..4a07ebd83 100644 --- a/src/screens/Onboarding/Layout.tsx +++ b/src/screens/Onboarding/Layout.tsx @@ -17,10 +17,11 @@ import { useTheme, web, } from '#/alf' +import {leading} from '#/alf/typography' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' import {createPortalGroup} from '#/components/Portal' -import {leading, P, Text} from '#/components/Typography' +import {P, Text} from '#/components/Typography' import {IS_DEV} from '#/env' const COL_WIDTH = 420 diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx index 10cf1a931..96cecb37c 100644 --- a/src/view/com/composer/text-input/TextInput.tsx +++ b/src/view/com/composer/text-input/TextInput.tsx @@ -31,7 +31,7 @@ import { suggestLinkCardUri, } from '#/view/com/composer/text-input/text-input-util' import {atoms as a, useAlf} from '#/alf' -import {normalizeTextStyles} from '#/components/Typography' +import {normalizeTextStyles} from '#/alf/typography' import {Autocomplete} from './mobile/Autocomplete' export interface TextInputRef { diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index fa742d258..34d19e14c 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -23,8 +23,8 @@ import { } from '#/view/com/composer/text-input/text-input-util' import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' import {atoms as a, useAlf} from '#/alf' +import {normalizeTextStyles} from '#/alf/typography' import {Portal} from '#/components/Portal' -import {normalizeTextStyles} from '#/components/Typography' import {Text} from '../../util/text/Text' import {createSuggestion} from './web/Autocomplete' import {Emoji} from './web/EmojiPicker.web' diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx index dbf5e2e13..a5278e0a0 100644 --- a/src/view/com/util/text/Text.tsx +++ b/src/view/com/util/text/Text.tsx @@ -12,7 +12,7 @@ import { childIsString, renderChildrenWithEmoji, StringChild, -} from '#/components/Typography' +} from '#/alf/typography' import {IS_DEV} from '#/env' export type CustomTextProps = Omit<TextProps, 'children'> & { @@ -32,7 +32,11 @@ export type CustomTextProps = Omit<TextProps, 'children'> & { } ) -export function Text({ +export {Text_DEPRECATED as Text} +/** + * @deprecated use Text from Typography instead. + */ +function Text_DEPRECATED({ type = 'md', children, emoji, |