about summary refs log tree commit diff
path: root/src/components/Typography.tsx
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2024-11-23 16:20:24 -0800
committerGitHub <noreply@github.com>2024-11-23 16:20:24 -0800
commit32bf8122e8c8a0fbadd53b8a015cfbc9014519a2 (patch)
tree55bd24596e6fadadbf4326b26e3d14e418c5c7bb /src/components/Typography.tsx
parent523d1f01a51c0e85e49916fb42b204f7004ffac1 (diff)
parentb4d07c4112b9a62b5380948051aa4a7fd391a2d4 (diff)
downloadvoidsky-32bf8122e8c8a0fbadd53b8a015cfbc9014519a2.tar.zst
Merge branch 'main' into main
Diffstat (limited to 'src/components/Typography.tsx')
-rw-r--r--src/components/Typography.tsx149
1 files changed, 10 insertions, 139 deletions
diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx
index 69e073271..3e202cb8f 100644
--- a/src/components/Typography.tsx
+++ b/src/components/Typography.tsx
@@ -1,140 +1,15 @@
-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 {atoms, flatten, useAlf, useTheme, web} from '#/alf'
+import {
+  childHasEmoji,
+  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.
@@ -162,10 +37,6 @@ export function Text({
         `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
       )
     }
-
-    if (emoji && !childIsString(children)) {
-      logger.error('Text: when <Text emoji />, children can only be strings.')
-    }
   }
 
   const shared = {
@@ -178,12 +49,12 @@ export function Text({
 
   return (
     <UITextView {...shared}>
-      {isIOS && emoji ? renderChildrenWithEmoji(children, shared) : children}
+      {renderChildrenWithEmoji(children, shared, emoji ?? false)}
     </UITextView>
   )
 }
 
-export function createHeadingElement({level}: {level: number}) {
+function createHeadingElement({level}: {level: number}) {
   return function HeadingElement({style, ...rest}: TextProps) {
     const attr =
       web({