From 208856b9d92db0f5346c02284603dd3afd108b1d Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Fri, 14 Jun 2024 18:29:25 -0400 Subject: [PATCH] Add TextShadow component (#5851) * Add TextShadow component, add env var to silence design system emoji warnings * Temporarily default to disabling Android text shadows --- globals.d.ts | 1 + .../components/Heading/Heading.tsx | 8 ++- src/design-system/components/Text/Text.tsx | 8 ++- .../components/TextIcon/TextIcon.tsx | 2 +- .../components/TextShadow/TextShadow.tsx | 62 +++++++++++++++++++ src/design-system/index.ts | 2 + 6 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 src/design-system/components/TextShadow/TextShadow.tsx diff --git a/globals.d.ts b/globals.d.ts index 398a10deace..4a1cf40b077 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -102,4 +102,5 @@ declare module 'react-native-dotenv' { export const RPC_PROXY_API_KEY_DEV: string; export const REACT_NATIVE_RUDDERSTACK_WRITE_KEY: string; export const RUDDERSTACK_DATA_PLANE_URL: string; + export const SILENCE_EMOJI_WARNINGS: boolean; } diff --git a/src/design-system/components/Heading/Heading.tsx b/src/design-system/components/Heading/Heading.tsx index 304c34cfb6b..70812fd25b0 100644 --- a/src/design-system/components/Heading/Heading.tsx +++ b/src/design-system/components/Heading/Heading.tsx @@ -1,6 +1,7 @@ import React, { ElementRef, forwardRef, ReactNode, useEffect, useMemo } from 'react'; import { Text as NativeText } from 'react-native'; - +import { SILENCE_EMOJI_WARNINGS } from 'react-native-dotenv'; +import { IS_DEV, IS_IOS } from '@/env'; import { CustomColor } from '../../color/useForegroundColor'; import { createLineHeightFixNode } from '../../typography/createLineHeightFixNode'; import { nodeHasEmoji, nodeIsString, renderStringWithEmoji } from '../../typography/renderStringWithEmoji'; @@ -29,7 +30,7 @@ export const Heading = forwardRef, HeadingProps>(f ref ) { useEffect(() => { - if (__DEV__) { + if (IS_DEV && !SILENCE_EMOJI_WARNINGS) { if (!containsEmojiProp && nodeHasEmoji(children)) { // eslint-disable-next-line no-console console.log( @@ -42,6 +43,7 @@ export const Heading = forwardRef, HeadingProps>(f ); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const headingStyle = useHeadingStyle({ align, color, size, weight }); @@ -50,7 +52,7 @@ export const Heading = forwardRef, HeadingProps>(f return ( - {ios && containsEmojiProp && nodeIsString(children) ? renderStringWithEmoji(children) : children} + {IS_IOS && containsEmojiProp && nodeIsString(children) ? renderStringWithEmoji(children) : children} {lineHeightFixNode} ); diff --git a/src/design-system/components/Text/Text.tsx b/src/design-system/components/Text/Text.tsx index 3f3cf3a8314..414fde217e2 100644 --- a/src/design-system/components/Text/Text.tsx +++ b/src/design-system/components/Text/Text.tsx @@ -1,5 +1,7 @@ import React, { ElementRef, forwardRef, ReactNode, useMemo, useEffect } from 'react'; import { Text as NativeText, StyleProp, TextStyle } from 'react-native'; +import { SILENCE_EMOJI_WARNINGS } from 'react-native-dotenv'; +import { IS_DEV, IS_IOS } from '@/env'; import { TextColor } from '../../color/palettes'; import { CustomColor } from '../../color/useForegroundColor'; import { createLineHeightFixNode } from '../../typography/createLineHeightFixNode'; @@ -34,6 +36,7 @@ export type TextProps = { ) & { style?: StyleProp; }; + export const Text = forwardRef, TextProps>(function Text( { align, @@ -53,7 +56,7 @@ export const Text = forwardRef, TextProps>(functio ref ) { useEffect(() => { - if (__DEV__) { + if (IS_DEV && !SILENCE_EMOJI_WARNINGS) { if (!containsEmojiProp && nodeHasEmoji(children)) { // eslint-disable-next-line no-console console.log( @@ -67,6 +70,7 @@ export const Text = forwardRef, TextProps>(functio ); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const textStyle = useTextStyle({ @@ -90,7 +94,7 @@ export const Text = forwardRef, TextProps>(functio testID={testID} onPress={onPress} > - {ios && containsEmojiProp && nodeIsString(children) ? renderStringWithEmoji(children) : children} + {IS_IOS && containsEmojiProp && nodeIsString(children) ? renderStringWithEmoji(children) : children} {lineHeightFixNode} ); diff --git a/src/design-system/components/TextIcon/TextIcon.tsx b/src/design-system/components/TextIcon/TextIcon.tsx index 381c7513b32..06d2a443753 100644 --- a/src/design-system/components/TextIcon/TextIcon.tsx +++ b/src/design-system/components/TextIcon/TextIcon.tsx @@ -8,7 +8,7 @@ import { Text, TextSize, TextWeight } from '../Text/Text'; export type TextIconProps = { align?: 'center' | 'left' | 'right'; - children: string | (string | null)[]; + children: string | (string | null)[] | React.ReactNode; color: TextColor | CustomColor; containerSize?: number; height?: number; diff --git a/src/design-system/components/TextShadow/TextShadow.tsx b/src/design-system/components/TextShadow/TextShadow.tsx new file mode 100644 index 00000000000..2b5a679d474 --- /dev/null +++ b/src/design-system/components/TextShadow/TextShadow.tsx @@ -0,0 +1,62 @@ +import React, { ReactElement, useMemo } from 'react'; +import { StyleProp, TextStyle, View, ViewStyle } from 'react-native'; +import { IS_ANDROID } from '@/env'; +import { opacity } from '@/__swaps__/utils/swaps'; +import { useColorMode } from '../../color/ColorMode'; +import { useForegroundColor } from '../../color/useForegroundColor'; +import { Text, TextProps } from '../Text/Text'; + +export interface TextShadowProps { + blur?: number; + children: ReactElement; + color?: string; + containerStyle?: StyleProp; + disabled?: boolean; + enableInLightMode?: boolean; + shadowOpacity?: number; + textStyle?: StyleProp; + x?: number; + y?: number; +} + +export const TextShadow = ({ + blur = 16, + children, + color, + containerStyle, + // ⚠️ TODO: Need to test on Android - defaulting to disabled on Android for now + disabled = IS_ANDROID, + enableInLightMode, + shadowOpacity = 0.6, + textStyle, + x = 0, + y = 0, +}: TextShadowProps) => { + const { isDarkMode } = useColorMode(); + + const inferredTextColor = useForegroundColor(children.props.color ?? 'label'); + const inferredTextSize = children.props.size || '17pt'; + + const [internalContainerStyle, internalTextStyle] = useMemo(() => { + const extraSpaceForShadow = blur + Math.max(Math.abs(x), Math.abs(y)); + return [ + { margin: -extraSpaceForShadow }, + { + textShadowColor: opacity(color || inferredTextColor, shadowOpacity), + textShadowOffset: { width: x, height: y }, + textShadowRadius: blur, + padding: extraSpaceForShadow, + }, + ]; + }, [blur, color, inferredTextColor, shadowOpacity, x, y]); + + return !disabled && (isDarkMode || enableInLightMode) ? ( + + + {children} + + + ) : ( + <>{children} + ); +}; diff --git a/src/design-system/index.ts b/src/design-system/index.ts index d7cfff76a8c..52d79657419 100644 --- a/src/design-system/index.ts +++ b/src/design-system/index.ts @@ -22,6 +22,7 @@ export { Stack } from './components/Stack/Stack'; export { selectTextSizes, Text } from './components/Text/Text'; export { TextIcon } from './components/TextIcon/TextIcon'; export { TextLink } from './components/TextLink/TextLink'; +export { TextShadow } from './components/TextShadow/TextShadow'; export { useColorMode } from './color/ColorMode'; export { useForegroundColor } from './color/useForegroundColor'; export { useHeadingStyle } from './components/Heading/useHeadingStyle'; @@ -47,3 +48,4 @@ export type { StackProps } from './components/Stack/Stack'; export type { TextLinkProps } from './components/TextLink/TextLink'; export type { TextProps } from './components/Text/Text'; export type { TextIconProps } from './components/TextIcon/TextIcon'; +export type { TextShadowProps } from './components/TextShadow/TextShadow';