Files
RN_Template/RN_TEMPLATE/app/components/Text.tsx

119 lines
3.7 KiB
TypeScript
Raw Normal View History

2026-02-05 13:16:05 +08:00
import { ReactNode, forwardRef, ForwardedRef } from "react"
// eslint-disable-next-line no-restricted-imports
import { StyleProp, Text as RNText, TextProps as RNTextProps, TextStyle } from "react-native"
import { TOptions } from "i18next"
import { useTranslation } from "react-i18next"
import { isRTL, TxKeyPath } from "@/i18n"
import { useAppTheme } from "@/theme/context"
import type { ThemedStyle, ThemedStyleArray } from "@/theme/types"
import { typography } from "@/theme/typography"
import { fs } from "@/utils/responsive"
type Sizes = keyof typeof $sizeStyles
type Weights = keyof typeof typography.primary
type Presets = "default" | "bold" | "heading" | "subheading" | "formLabel" | "formHelper"
export interface TextProps extends RNTextProps {
/**
* Text which is looked up via i18n.
*/
tx?: TxKeyPath
/**
* The text to display if not using `tx` or nested components.
*/
text?: string
/**
* Optional options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
txOptions?: TOptions
/**
* An optional style override useful for padding & margin.
*/
style?: StyleProp<TextStyle>
/**
* One of the different types of text presets.
*/
preset?: Presets
/**
* Text weight modifier.
*/
weight?: Weights
/**
* Text size modifier.
*/
size?: Sizes
/**
* Children components.
*/
children?: ReactNode
}
/**
* For your text displaying needs.
* This component is a HOC over the built-in React Native one.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Text/}
* @param {TextProps} props - The props for the `Text` component.
* @returns {JSX.Element} The rendered `Text` component.
*/
export const Text = forwardRef(function Text(props: TextProps, ref: ForwardedRef<RNText>) {
const { weight, size, tx, txOptions, text, children, style: $styleOverride, ...rest } = props
const { themed } = useAppTheme()
const { t } = useTranslation()
const i18nText = tx && t(tx, txOptions)
const content = i18nText || text || children
const preset: Presets = props.preset ?? "default"
const $styles: StyleProp<TextStyle> = [
$rtlStyle,
themed($presets[preset]),
weight && $fontWeightStyles[weight],
size && $sizeStyles[size],
$styleOverride,
]
return (
<RNText {...rest} style={$styles} ref={ref}>
{content}
</RNText>
)
})
const $sizeStyles = {
xxl: { fontSize: fs(36), lineHeight: fs(44) } satisfies TextStyle,
xl: { fontSize: fs(24), lineHeight: fs(34) } satisfies TextStyle,
lg: { fontSize: fs(20), lineHeight: fs(32) } satisfies TextStyle,
md: { fontSize: fs(18), lineHeight: fs(26) } satisfies TextStyle,
sm: { fontSize: fs(16), lineHeight: fs(24) } satisfies TextStyle,
xs: { fontSize: fs(14), lineHeight: fs(21) } satisfies TextStyle,
xxs: { fontSize: fs(12), lineHeight: fs(18) } satisfies TextStyle,
}
const $fontWeightStyles = Object.entries(typography.primary).reduce((acc, [weight, fontFamily]) => {
return { ...acc, [weight]: { fontFamily } }
}, {}) as Record<Weights, TextStyle>
const $baseStyle: ThemedStyle<TextStyle> = (theme) => ({
...$sizeStyles.sm,
...$fontWeightStyles.normal,
color: theme.colors.text,
})
const $presets: Record<Presets, ThemedStyleArray<TextStyle>> = {
default: [$baseStyle],
bold: [$baseStyle, { ...$fontWeightStyles.bold }],
heading: [
$baseStyle,
{
...$sizeStyles.xxl,
...$fontWeightStyles.bold,
},
],
subheading: [$baseStyle, { ...$sizeStyles.lg, ...$fontWeightStyles.medium }],
formLabel: [$baseStyle, { ...$fontWeightStyles.medium }],
formHelper: [$baseStyle, { ...$sizeStyles.sm, ...$fontWeightStyles.normal }],
}
const $rtlStyle: TextStyle = isRTL ? { writingDirection: "rtl" } : {}