/** @jsxImportSource @fluentui-react-native/framework-base */ import React from 'react'; import { I18nManager, Platform, Text as RNText } from 'react-native'; import type { UseTokens, FontWeightValue } from '@fluentui-react-native/framework'; import { fontStyles, useFluentTheme, mergeStyles, compressible, patchTokens } from '@fluentui-react-native/framework'; import { useKeyProps } from '@fluentui-react-native/interactive-hooks'; import { globalTokens } from '@fluentui-react-native/theme-tokens'; import type { TextProps, TextTokens } from './Text.types'; import { textName } from './Text.types'; import { useTextTokens } from './TextTokens'; const emptyProps = {}; export const Text = compressible((props: TextProps, useTokens: UseTokens) => { if (props === undefined) { props = emptyProps; } // split out color and variant from props const { align = undefined, block, color, componentRef, font, italic, numberOfLines, onAccessibilityTap, onKeyUp, onKeyDown, keyUpEvents, keyDownEvents, onPress, size, strikethrough, style, truncate = false, underline, variant, weight, wrap = true, ...rest } = props; const theme = useFluentTheme(); // get the tokens from the theme let [tokens, cache] = useTokens(theme); const textAlign = I18nManager.isRTL ? align === 'start' ? 'right' : align === 'end' ? 'left' : align : align === 'start' ? 'left' : align === 'end' ? 'right' : align; const textOnPress = React.useCallback( (e) => { if (onPress) { onPress(e); } e.stopPropagation(); }, [onPress], ); const keyProps = useKeyProps(textOnPress, ' ', 'Enter'); const onAccTap = React.useCallback( (event?) => { onAccessibilityTap ? onAccessibilityTap() : onPress(event); }, [onPress, onAccessibilityTap], ); // override tokens from props [tokens, cache] = patchTokens(tokens, cache, { color, variant, fontFamily: font == 'base' ? 'primary' : font, fontMaximumSize: tokens.maximumFontSize, fontSize: globalTokens.font['size' + size], fontWeight: globalTokens.font.weight[weight] as FontWeightValue, // leave it undefined for tokens to be set by user fontStyle: italic ? 'italic' : undefined, textAlign: textAlign, textDecorationLine: underline && strikethrough ? 'underline line-through' : underline ? 'underline' : strikethrough ? 'line-through' : undefined, }); // now build the text style from tokens that can be shared between different Text instances const [tokenStyle] = cache( () => ({ margin: 0, color: tokens.color, fontStyle: tokens.fontStyle, textAlign: tokens.textAlign, textDecorationLine: tokens.textDecorationLine, ...fontStyles.from(tokens, theme), }), ['color', 'fontStyle', 'textAlign', 'textDecorationLine', ...fontStyles.keys], ); // Safety measure: Dynamic Type is an iOS-specific thing const dynamicTypeVariant = Platform.OS === 'ios' ? tokenStyle.dynamicTypeRamp : undefined; let maxFontSizeScaleAdjustment: TextProps = emptyProps; // tokenStyle.fontSize can also be a string (e.g., "14px"). // Therefore, we only support scaling for number-based size values in order to avoid any messy calculations. if (dynamicTypeVariant !== undefined && typeof tokenStyle.fontSize === 'number' && tokenStyle.maximumFontSize !== undefined) { maxFontSizeScaleAdjustment = { maxFontSizeMultiplier: tokenStyle.maximumFontSize / tokenStyle.fontSize, }; } const isWin32Platform = Platform.OS === ('win32' as any); const filteredProps = { onKeyUp: isWin32Platform ? onKeyUp : undefined, keyUpEvents: isWin32Platform ? keyUpEvents : undefined, validKeysUp: undefined, onKeyDown: isWin32Platform ? onKeyDown : undefined, keyDownEvents: isWin32Platform ? keyDownEvents : undefined, validKeysDown: undefined, onAccessibilityTap: isWin32Platform ? onAccTap : undefined, }; // return a continuation function that allows this text to be compressed return (extra: TextProps, children: React.ReactNode) => { const mergedProps = { ...rest, ...keyProps, ...filteredProps, ...extra, ...maxFontSizeScaleAdjustment, onPress, numberOfLines: numberOfLines ?? (truncate || !wrap ? 1 : 0), style: mergeStyles(tokenStyle, props.style, extra?.style), }; // RN TextStyle doesn't recognize these properties. // Don't let them leak through or RN will complain about invalid props. delete (mergedProps.style as TextTokens).dynamicTypeRamp; delete (mergedProps.style as TextTokens).maximumFontSize; return ( {children} ); }; }, useTextTokens); Text.displayName = textName; export default Text;