From 6b7ce4007692ed33514b83477d5bc92b56765c07 Mon Sep 17 00:00:00 2001 From: Adrian Cotfas Date: Thu, 14 May 2026 12:26:35 +0300 Subject: [PATCH] refactor: replace string with ColorValue for ThemeColors --- example/src/DrawerItems.tsx | 10 +- .../Examples/BottomNavigationBarExample.tsx | 2 +- src/components/ActivityIndicator.tsx | 3 +- src/components/Appbar/AppbarAction.tsx | 10 +- src/components/Appbar/AppbarBackAction.tsx | 3 +- src/components/Appbar/AppbarBackIcon.tsx | 10 +- .../BottomNavigation/BottomNavigation.tsx | 4 +- .../BottomNavigation/BottomNavigationBar.tsx | 4 +- src/components/BottomNavigation/utils.ts | 12 ++- src/components/Button/Button.tsx | 7 +- src/components/Button/utils.tsx | 14 +-- src/components/Checkbox/CheckboxAndroid.tsx | 5 +- src/components/Checkbox/CheckboxIOS.tsx | 9 +- src/components/Checkbox/utils.ts | 18 ++-- src/components/Chip/Chip.tsx | 3 +- src/components/Chip/helpers.tsx | 14 ++- src/components/CrossFadeIcon.tsx | 4 +- src/components/Dialog/DialogIcon.tsx | 4 +- src/components/Drawer/DrawerItem.tsx | 3 +- src/components/FAB/FAB.tsx | 3 +- src/components/FAB/utils.ts | 6 +- src/components/Icon.tsx | 11 +- src/components/IconButton/IconButton.tsx | 9 +- src/components/IconButton/utils.ts | 10 +- src/components/List/ListAccordion.tsx | 7 +- src/components/List/ListIcon.tsx | 10 +- src/components/List/ListItem.tsx | 11 +- src/components/MaterialCommunityIcon.tsx | 13 ++- src/components/Searchbar.tsx | 7 +- src/components/Surface.tsx | 9 +- src/components/Switch/utils.ts | 10 +- src/components/TextInput/Addons/Outline.tsx | 4 +- src/components/TextInput/Addons/Underline.tsx | 14 ++- .../TextInput/Adornment/TextInputIcon.tsx | 5 +- src/components/TextInput/Adornment/utils.ts | 6 +- src/components/TextInput/helpers.tsx | 32 +++--- src/components/TextInput/types.tsx | 26 ++--- .../TouchableRipple/TouchableRipple.tsx | 7 +- src/components/TouchableRipple/utils.ts | 4 +- .../__tests__/BottomNavigation.test.tsx | 8 +- .../views/MaterialBottomTabView.tsx | 9 +- src/theme/schemes/DynamicTheme.android.tsx | 7 +- src/theme/tokens/sys/elevation.ts | 4 +- src/theme/types/color.ts | 100 +++++++++--------- 44 files changed, 283 insertions(+), 188 deletions(-) diff --git a/example/src/DrawerItems.tsx b/example/src/DrawerItems.tsx index 567d96a0e3..0b26938cae 100644 --- a/example/src/DrawerItems.tsx +++ b/example/src/DrawerItems.tsx @@ -1,5 +1,11 @@ import * as React from 'react'; -import { I18nManager, StyleSheet, View, Platform } from 'react-native'; +import { + ColorValue, + I18nManager, + Platform, + StyleSheet, + View, +} from 'react-native'; import { DrawerContentScrollView } from '@react-navigation/drawer'; import Constants, { ExecutionEnvironment } from 'expo-constants'; @@ -31,7 +37,7 @@ const DrawerItemsData = [ label: 'Starred', icon: 'star', key: 1, - right: ({ color }: { color: string }) => ( + right: ({ color }: { color: ColorValue }) => ( descriptors[route.key].options.tabBarIcon?.({ focused, - color, + color: typeof color === 'string' ? color : '', size: 24, }) || null } diff --git a/src/components/ActivityIndicator.tsx b/src/components/ActivityIndicator.tsx index cfbd6c948a..95da56ae0c 100644 --- a/src/components/ActivityIndicator.tsx +++ b/src/components/ActivityIndicator.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Animated, + ColorValue, Easing, Platform, StyleProp, @@ -20,7 +21,7 @@ export type Props = React.ComponentPropsWithRef & { /** * The color of the spinner. */ - color?: string; + color?: ColorValue; /** * Size of the indicator. */ diff --git a/src/components/Appbar/AppbarAction.tsx b/src/components/Appbar/AppbarAction.tsx index 98e90f956b..2d0be0227c 100644 --- a/src/components/Appbar/AppbarAction.tsx +++ b/src/components/Appbar/AppbarAction.tsx @@ -1,5 +1,11 @@ import * as React from 'react'; -import type { Animated, StyleProp, View, ViewStyle } from 'react-native'; +import type { + Animated, + ColorValue, + StyleProp, + View, + ViewStyle, +} from 'react-native'; import { useInternalTheme } from '../../core/theming'; import type { Theme, ThemeProp } from '../../types'; @@ -11,7 +17,7 @@ export type Props = React.ComponentPropsWithoutRef & { /** * Custom color for action icon. */ - color?: string; + color?: ColorValue; /** * Name of the icon to show. */ diff --git a/src/components/Appbar/AppbarBackAction.tsx b/src/components/Appbar/AppbarBackAction.tsx index accc76bc65..ff24d9dad2 100644 --- a/src/components/Appbar/AppbarBackAction.tsx +++ b/src/components/Appbar/AppbarBackAction.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import type { Animated, + ColorValue, GestureResponderEvent, StyleProp, View, @@ -19,7 +20,7 @@ export type Props = $Omit< /** * Custom color for back icon. */ - color?: string; + color?: ColorValue; /** * Optional icon size. */ diff --git a/src/components/Appbar/AppbarBackIcon.tsx b/src/components/Appbar/AppbarBackIcon.tsx index 35eb98d18f..763e859412 100644 --- a/src/components/Appbar/AppbarBackIcon.tsx +++ b/src/components/Appbar/AppbarBackIcon.tsx @@ -1,10 +1,16 @@ import * as React from 'react'; -import { Image, Platform, StyleSheet, View } from 'react-native'; +import { ColorValue, Image, Platform, StyleSheet, View } from 'react-native'; import { useLocale } from '../../core/locale'; import MaterialCommunityIcon from '../MaterialCommunityIcon'; -const AppbarBackIcon = ({ size, color }: { size: number; color: string }) => { +const AppbarBackIcon = ({ + size, + color, +}: { + size: number; + color: ColorValue; +}) => { const { direction } = useLocale(); const isRTL = direction === 'rtl'; const iosIconSize = size - 3; diff --git a/src/components/BottomNavigation/BottomNavigation.tsx b/src/components/BottomNavigation/BottomNavigation.tsx index d0cd447295..609e9d62aa 100644 --- a/src/components/BottomNavigation/BottomNavigation.tsx +++ b/src/components/BottomNavigation/BottomNavigation.tsx @@ -155,7 +155,7 @@ export type Props = { renderIcon?: (props: { route: Route; focused: boolean; - color: string; + color: ColorValue; }) => React.ReactNode; /** * Callback which React Element to be used as tab label. @@ -163,7 +163,7 @@ export type Props = { renderLabel?: (props: { route: Route; focused: boolean; - color: string; + color: ColorValue; }) => React.ReactNode; /** * Callback which returns a React element to be used as the touchable for the tab item. diff --git a/src/components/BottomNavigation/BottomNavigationBar.tsx b/src/components/BottomNavigation/BottomNavigationBar.tsx index 3992463322..35ae03625c 100644 --- a/src/components/BottomNavigation/BottomNavigationBar.tsx +++ b/src/components/BottomNavigation/BottomNavigationBar.tsx @@ -122,7 +122,7 @@ export type Props = { renderIcon?: (props: { route: Route; focused: boolean; - color: string; + color: ColorValue; }) => React.ReactNode; /** * Callback which React Element to be used as tab label. @@ -130,7 +130,7 @@ export type Props = { renderLabel?: (props: { route: Route; focused: boolean; - color: string; + color: ColorValue; }) => React.ReactNode; /** * Callback which returns a React element to be used as the touchable for the tab item. diff --git a/src/components/BottomNavigation/utils.ts b/src/components/BottomNavigation/utils.ts index fbfe1a3766..ede88105e4 100644 --- a/src/components/BottomNavigation/utils.ts +++ b/src/components/BottomNavigation/utils.ts @@ -1,13 +1,15 @@ +import type { ColorValue } from 'react-native'; + import type { InternalTheme, Theme } from '../../types'; export const getActiveTintColor = ({ activeColor, theme, }: { - activeColor: string | undefined; + activeColor: ColorValue | undefined; theme: InternalTheme; }) => { - if (typeof activeColor === 'string') { + if (activeColor != null) { return activeColor; } @@ -18,10 +20,10 @@ export const getInactiveTintColor = ({ inactiveColor, theme, }: { - inactiveColor: string | undefined; + inactiveColor: ColorValue | undefined; theme: InternalTheme; }) => { - if (typeof inactiveColor === 'string') { + if (inactiveColor != null) { return inactiveColor; } @@ -34,7 +36,7 @@ export const getLabelColor = ({ focused, theme, }: { - tintColor: string; + tintColor: ColorValue; hasColor: boolean; focused: boolean; theme: InternalTheme; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 59cebe3a93..0f98135aa3 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { AccessibilityRole, Animated, + ColorValue, GestureResponderEvent, Platform, PressableAndroidRippleConfig, @@ -54,15 +55,15 @@ export type Props = $Omit, 'mode'> & { * @deprecated Deprecated in v5.x - use `buttonColor` or `textColor` instead. * Custom text color for flat button, or background color for contained button. */ - color?: string; + color?: ColorValue; /** * Custom button's background color. */ - buttonColor?: string; + buttonColor?: ColorValue; /** * Custom button's text color. */ - textColor?: string; + textColor?: ColorValue; /** * Whether to show a loading indicator. */ diff --git a/src/components/Button/utils.tsx b/src/components/Button/utils.tsx index 37038afbd7..03ce9513ea 100644 --- a/src/components/Button/utils.tsx +++ b/src/components/Button/utils.tsx @@ -1,4 +1,4 @@ -import type { ViewStyle } from 'react-native'; +import type { ColorValue, ViewStyle } from 'react-native'; import { black, white } from '../../theme/colors'; import { tokens } from '../../theme/tokens'; @@ -25,7 +25,7 @@ const isDark = ({ backgroundColor, }: { dark?: boolean; - backgroundColor?: string; + backgroundColor?: ColorValue; }) => { if (typeof dark === 'boolean') { return dark; @@ -44,7 +44,7 @@ const getButtonBackgroundColor = ({ disabled, customButtonColor, }: BaseProps & { - customButtonColor?: string; + customButtonColor?: ColorValue; }) => { const { colors } = theme as Theme; if (customButtonColor && !disabled) { @@ -81,8 +81,8 @@ const getButtonTextColor = ({ backgroundColor, dark, }: BaseProps & { - customTextColor?: string; - backgroundColor: string; + customTextColor?: ColorValue; + backgroundColor: ColorValue; dark?: boolean; }) => { const { colors } = theme as Theme; @@ -145,8 +145,8 @@ export const getButtonColors = ({ }: { theme: InternalTheme; mode: ButtonMode; - customButtonColor?: string; - customTextColor?: string; + customButtonColor?: ColorValue; + customTextColor?: ColorValue; disabled?: boolean; dark?: boolean; }) => { diff --git a/src/components/Checkbox/CheckboxAndroid.tsx b/src/components/Checkbox/CheckboxAndroid.tsx index a4e6fb0c23..8a93cc6a70 100644 --- a/src/components/Checkbox/CheckboxAndroid.tsx +++ b/src/components/Checkbox/CheckboxAndroid.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Animated, + ColorValue, GestureResponderEvent, StyleSheet, View, @@ -28,11 +29,11 @@ export type Props = $RemoveChildren & { /** * Custom color for unchecked checkbox. */ - uncheckedColor?: string; + uncheckedColor?: ColorValue; /** * Custom color for checkbox. */ - color?: string; + color?: ColorValue; /** * @optional */ diff --git a/src/components/Checkbox/CheckboxIOS.tsx b/src/components/Checkbox/CheckboxIOS.tsx index 692ed67c48..d4a8127180 100644 --- a/src/components/Checkbox/CheckboxIOS.tsx +++ b/src/components/Checkbox/CheckboxIOS.tsx @@ -1,5 +1,10 @@ import * as React from 'react'; -import { GestureResponderEvent, StyleSheet, View } from 'react-native'; +import { + ColorValue, + GestureResponderEvent, + StyleSheet, + View, +} from 'react-native'; import { getSelectionControlIOSColor } from './utils'; import { useInternalTheme } from '../../core/theming'; @@ -23,7 +28,7 @@ export type Props = $RemoveChildren & { /** * Custom color for checkbox. */ - color?: string; + color?: ColorValue; /** * @optional */ diff --git a/src/components/Checkbox/utils.ts b/src/components/Checkbox/utils.ts index 626b45e1d2..f47457151b 100644 --- a/src/components/Checkbox/utils.ts +++ b/src/components/Checkbox/utils.ts @@ -1,3 +1,5 @@ +import type { ColorValue } from 'react-native'; + import { tokens } from '../../theme/tokens'; import type { InternalTheme } from '../../types'; @@ -8,7 +10,7 @@ const getAndroidCheckedColor = ({ customColor, }: { theme: InternalTheme; - customColor?: string; + customColor?: ColorValue; }) => { if (customColor) { return customColor; @@ -22,7 +24,7 @@ const getAndroidUncheckedColor = ({ customUncheckedColor, }: { theme: InternalTheme; - customUncheckedColor?: string; + customUncheckedColor?: ColorValue; }) => { if (customUncheckedColor) { return customUncheckedColor; @@ -40,8 +42,8 @@ const getAndroidControlColor = ({ }: { theme: InternalTheme; checked: boolean; - checkedColor: string; - uncheckedColor: string; + checkedColor: ColorValue; + uncheckedColor: ColorValue; disabled?: boolean; }) => { if (disabled) { @@ -64,8 +66,8 @@ export const getAndroidSelectionControlColor = ({ theme: InternalTheme; checked: boolean; disabled?: boolean; - customColor?: string; - customUncheckedColor?: string; + customColor?: ColorValue; + customUncheckedColor?: ColorValue; }) => { const checkedColor = getAndroidCheckedColor({ theme, customColor }); const uncheckedColor = getAndroidUncheckedColor({ @@ -94,7 +96,7 @@ const getIOSCheckedColor = ({ customColor, }: { theme: InternalTheme; - customColor?: string; + customColor?: ColorValue; disabled?: boolean; }) => { if (disabled) { @@ -115,7 +117,7 @@ export const getSelectionControlIOSColor = ({ }: { theme: InternalTheme; disabled?: boolean; - customColor?: string; + customColor?: ColorValue; }) => { const checkedColor = getIOSCheckedColor({ theme, disabled, customColor }); const checkedColorOpacity = disabled diff --git a/src/components/Chip/Chip.tsx b/src/components/Chip/Chip.tsx index 76d12d5ae1..78b240703c 100644 --- a/src/components/Chip/Chip.tsx +++ b/src/components/Chip/Chip.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { AccessibilityState, Animated, + ColorValue, GestureResponderEvent, Platform, PressableAndroidRippleConfig, @@ -61,7 +62,7 @@ export type Props = $Omit, 'mode'> & { * Note: With theme version 3 `selectedColor` doesn't apply to the `icon`. * If you want specify custom color for the `icon`, render your own `Icon` component. */ - selectedColor?: string; + selectedColor?: ColorValue; /** * @supported Available in v5.x with theme version 3 * Whether to display overlay on selected chip diff --git a/src/components/Chip/helpers.tsx b/src/components/Chip/helpers.tsx index af72870edf..6090ed45bb 100644 --- a/src/components/Chip/helpers.tsx +++ b/src/components/Chip/helpers.tsx @@ -24,7 +24,7 @@ const getBorderColor = ({ isOutlined, disabled, selectedColor, -}: BaseProps & { backgroundColor: string; selectedColor?: string }) => { +}: BaseProps & { backgroundColor: ColorValue; selectedColor?: ColorValue }) => { const isSelectedColor = selectedColor !== undefined; const { colors } = md3(theme); @@ -38,7 +38,11 @@ const getBorderColor = ({ } if (isSelectedColor) { - return color(selectedColor).alpha(0.29).rgb().string(); + if (typeof selectedColor === 'string') { + return color(selectedColor).alpha(0.29).rgb().string(); + } + // PlatformColor / OpaqueColorValue: skip the alpha pass and render opaque. + return selectedColor; } return colors.outlineVariant; @@ -50,7 +54,7 @@ const getTextColor = ({ disabled, selectedColor, }: BaseProps & { - selectedColor?: string; + selectedColor?: ColorValue; }) => { const isSelectedColor = selectedColor !== undefined; const { colors } = md3(theme); @@ -126,7 +130,7 @@ const getIconColor = ({ disabled, selectedColor, }: BaseProps & { - selectedColor?: string; + selectedColor?: ColorValue; }) => { const isSelectedColor = selectedColor !== undefined; const { colors } = md3(theme); @@ -154,7 +158,7 @@ export const getChipColors = ({ }: BaseProps & { customBackgroundColor?: ColorValue; disabled?: boolean; - selectedColor?: string; + selectedColor?: ColorValue; }) => { const baseChipColorProps = { theme, isOutlined, disabled }; diff --git a/src/components/CrossFadeIcon.tsx b/src/components/CrossFadeIcon.tsx index 4cf3748c72..54852fb1a7 100644 --- a/src/components/CrossFadeIcon.tsx +++ b/src/components/CrossFadeIcon.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Animated, StyleSheet, View } from 'react-native'; +import { Animated, ColorValue, StyleSheet, View } from 'react-native'; import Icon, { IconSource, isEqualIcon, isValidIcon } from './Icon'; import { useInternalTheme } from '../core/theming'; @@ -13,7 +13,7 @@ type Props = { /** * Color of the icon. */ - color: string; + color: ColorValue; /** * Size of the icon. */ diff --git a/src/components/Dialog/DialogIcon.tsx b/src/components/Dialog/DialogIcon.tsx index 8cda9b0d30..e72d7f2821 100644 --- a/src/components/Dialog/DialogIcon.tsx +++ b/src/components/Dialog/DialogIcon.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { StyleSheet, View } from 'react-native'; +import { ColorValue, StyleSheet, View } from 'react-native'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; @@ -10,7 +10,7 @@ export type Props = { /** * Custom color for action icon. */ - color?: string; + color?: ColorValue; /** * Name of the icon to show. */ diff --git a/src/components/Drawer/DrawerItem.tsx b/src/components/Drawer/DrawerItem.tsx index 9a68471474..abcd6ae894 100644 --- a/src/components/Drawer/DrawerItem.tsx +++ b/src/components/Drawer/DrawerItem.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { + ColorValue, GestureResponderEvent, PressableAndroidRippleConfig, StyleProp, @@ -49,7 +50,7 @@ export type Props = React.ComponentPropsWithRef & { /** * Callback which returns a React element to display on the right side. For instance a Badge. */ - right?: (props: { color: string }) => React.ReactNode; + right?: (props: { color: ColorValue }) => React.ReactNode; /** * Specifies the largest possible scale a label font can reach. */ diff --git a/src/components/FAB/FAB.tsx b/src/components/FAB/FAB.tsx index c78a550a9f..0bcdf41557 100644 --- a/src/components/FAB/FAB.tsx +++ b/src/components/FAB/FAB.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { AccessibilityState, Animated, + ColorValue, GestureResponderEvent, PressableAndroidRippleConfig, StyleProp, @@ -77,7 +78,7 @@ export type Props = $Omit<$RemoveChildren, 'mode'> & { /** * Custom color for the icon and label of the `FAB`. */ - color?: string; + color?: ColorValue; /** * Whether `FAB` is currently visible. */ diff --git a/src/components/FAB/utils.ts b/src/components/FAB/utils.ts index 1d0dbb68e7..d46fcaf685 100644 --- a/src/components/FAB/utils.ts +++ b/src/components/FAB/utils.ts @@ -185,7 +185,7 @@ const getForegroundColor = ({ theme, isVariant, customColor, -}: BaseProps & { customColor?: string }) => { +}: BaseProps & { customColor?: ColorValue }) => { if (typeof customColor !== 'undefined') { return customColor; } @@ -217,7 +217,7 @@ export const getFABColors = ({ }: { theme: InternalTheme; variant: string; - customColor?: string; + customColor?: ColorValue; customBackgroundColor?: ColorValue; }) => { const isVariant = (variantToCompare: Variant) => { @@ -255,7 +255,7 @@ export const getFABGroupColors = ({ customBackdropColor, }: { theme: InternalTheme; - customBackdropColor?: string; + customBackdropColor?: ColorValue; }) => { return { labelColor: getLabelColor({ theme }), diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index eee770f6b7..8790b454ac 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -1,5 +1,10 @@ import * as React from 'react'; -import { Image, ImageSourcePropType, Platform } from 'react-native'; +import { + Image, + ImageSourcePropType, + Platform, + type ColorValue, +} from 'react-native'; import { accessibilityProps } from './MaterialCommunityIcon'; import { useLocale } from '../core/locale'; @@ -12,7 +17,7 @@ type IconSourceBase = string | ImageSourcePropType; export type IconSource = | IconSourceBase | Readonly<{ source: IconSourceBase; direction: 'rtl' | 'ltr' | 'auto' }> - | ((props: IconProps & { color: string }) => React.ReactNode); + | ((props: IconProps & { color: ColorValue }) => React.ReactNode); type IconProps = { /** @@ -65,7 +70,7 @@ export type Props = IconProps & { /** * Color of the icon. */ - color?: string; + color?: ColorValue; /** * TestID used for testing purposes */ diff --git a/src/components/IconButton/IconButton.tsx b/src/components/IconButton/IconButton.tsx index ec67a2ca1f..41b5370c78 100644 --- a/src/components/IconButton/IconButton.tsx +++ b/src/components/IconButton/IconButton.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; import { + Animated, + ColorValue, GestureResponderEvent, StyleProp, StyleSheet, - ViewStyle, View, - Animated, + ViewStyle, } from 'react-native'; import { getIconButtonColor } from './utils'; @@ -36,11 +37,11 @@ export type Props = Omit<$RemoveChildren, 'style'> & { * @renamed Renamed from 'color' to 'iconColor' in v5.x * Color of the icon. */ - iconColor?: string; + iconColor?: ColorValue; /** * Background color of the icon container. */ - containerColor?: string; + containerColor?: ColorValue; /** * Whether icon button is selected. A selected button receives alternative combination of icon and container colors. */ diff --git a/src/components/IconButton/utils.ts b/src/components/IconButton/utils.ts index 63ef8022c7..284e9efb2d 100644 --- a/src/components/IconButton/utils.ts +++ b/src/components/IconButton/utils.ts @@ -1,3 +1,5 @@ +import type { ColorValue } from 'react-native'; + import { tokens } from '../../theme/tokens'; import type { InternalTheme } from '../../types'; @@ -18,7 +20,7 @@ const getBackgroundColor = ({ disabled, selected, customContainerColor, -}: BaseProps & { customContainerColor?: string }) => { +}: BaseProps & { customContainerColor?: ColorValue }) => { if (disabled) { if (isMode('contained') || isMode('contained-tonal')) { return theme.colors.onSurface; @@ -58,7 +60,7 @@ const getIconColor = ({ disabled, selected, customIconColor, -}: BaseProps & { customIconColor?: string }) => { +}: BaseProps & { customIconColor?: ColorValue }) => { if (disabled) { return theme.colors.onSurface; } @@ -106,8 +108,8 @@ export const getIconButtonColor = ({ disabled?: boolean; selected?: boolean; mode?: IconButtonMode; - customIconColor?: string; - customContainerColor?: string; + customIconColor?: ColorValue; + customContainerColor?: ColorValue; }) => { const isMode = (modeToCompare: IconButtonMode) => { return mode === modeToCompare; diff --git a/src/components/List/ListAccordion.tsx b/src/components/List/ListAccordion.tsx index d79634d984..ef7699f184 100644 --- a/src/components/List/ListAccordion.tsx +++ b/src/components/List/ListAccordion.tsx @@ -1,15 +1,16 @@ import * as React from 'react'; import { + ColorValue, GestureResponderEvent, NativeSyntheticEvent, + PressableAndroidRippleConfig, StyleProp, StyleSheet, - TextStyle, TextLayoutEventData, + TextStyle, View, ViewProps, ViewStyle, - PressableAndroidRippleConfig, } from 'react-native'; import { ListAccordionGroupContext } from './ListAccordionGroup'; @@ -36,7 +37,7 @@ export type Props = { /** * Callback which returns a React element to display on the left side. */ - left?: (props: { color: string; style: Style }) => React.ReactNode; + left?: (props: { color: ColorValue; style: Style }) => React.ReactNode; /** * Callback which returns a React element to display on the right side. */ diff --git a/src/components/List/ListIcon.tsx b/src/components/List/ListIcon.tsx index c76865ccd3..0bef35d4c0 100644 --- a/src/components/List/ListIcon.tsx +++ b/src/components/List/ListIcon.tsx @@ -1,5 +1,11 @@ import * as React from 'react'; -import { View, ViewStyle, StyleSheet, StyleProp } from 'react-native'; +import { + ColorValue, + StyleProp, + StyleSheet, + View, + ViewStyle, +} from 'react-native'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; @@ -13,7 +19,7 @@ export type Props = { /** * Color for the icon. */ - color?: string; + color?: ColorValue; style?: StyleProp; /** * @optional diff --git a/src/components/List/ListItem.tsx b/src/components/List/ListItem.tsx index c50d075744..371c1da407 100644 --- a/src/components/List/ListItem.tsx +++ b/src/components/List/ListItem.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { + ColorValue, GestureResponderEvent, NativeSyntheticEvent, StyleProp, @@ -22,7 +23,7 @@ type Title = | ((props: { selectable: boolean; ellipsizeMode: EllipsizeProp | undefined; - color: string; + color: ColorValue; fontSize: number; }) => React.ReactNode); @@ -31,7 +32,7 @@ type Description = | ((props: { selectable: boolean; ellipsizeMode: EllipsizeProp | undefined; - color: string; + color: ColorValue; fontSize: number; }) => React.ReactNode); @@ -47,11 +48,11 @@ export type Props = $RemoveChildren & { /** * Callback which returns a React element to display on the left side. */ - left?: (props: { color: string; style: Style }) => React.ReactNode; + left?: (props: { color: ColorValue; style: Style }) => React.ReactNode; /** * Callback which returns a React element to display on the right side. */ - right?: (props: { color: string; style?: Style }) => React.ReactNode; + right?: (props: { color: ColorValue; style?: Style }) => React.ReactNode; /** * Function to execute on press. */ @@ -172,7 +173,7 @@ const ListItem = ( }; const renderDescription = ( - descriptionColor: string, + descriptionColor: ColorValue, description?: Description | null ) => { return typeof description === 'function' ? ( diff --git a/src/components/MaterialCommunityIcon.tsx b/src/components/MaterialCommunityIcon.tsx index 8c1704a3de..d5468a0d07 100644 --- a/src/components/MaterialCommunityIcon.tsx +++ b/src/components/MaterialCommunityIcon.tsx @@ -1,12 +1,19 @@ import * as React from 'react'; import { ComponentProps } from 'react'; -import { StyleSheet, Text, Platform, Role, ViewProps } from 'react-native'; +import { + ColorValue, + Platform, + Role, + StyleSheet, + Text, + ViewProps, +} from 'react-native'; import { black } from '../theme/colors'; export type IconProps = { name: ComponentProps['name']; - color?: string; + color?: ColorValue; size: number; direction: 'rtl' | 'ltr'; allowFontScaling?: boolean; @@ -59,7 +66,7 @@ type IconModuleType = React.ComponentType< | typeof import('@react-native-vector-icons/material-design-icons').default | typeof import('react-native-vector-icons/MaterialCommunityIcons').default > & { - color: string; + color: ColorValue; pointerEvents?: ViewProps['pointerEvents']; } >; diff --git a/src/components/Searchbar.tsx b/src/components/Searchbar.tsx index ba169fd9ba..975af841a6 100644 --- a/src/components/Searchbar.tsx +++ b/src/components/Searchbar.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Animated, + ColorValue, GestureResponderEvent, Platform, StyleProp, @@ -53,7 +54,7 @@ export type Props = React.ComponentPropsWithRef & { /** * Custom color for icon, default will be derived from theme */ - iconColor?: string; + iconColor?: ColorValue; /** * Callback to execute if we want the left icon to act as button. */ @@ -86,7 +87,7 @@ export type Props = React.ComponentPropsWithRef & { * @supported Available in v5.x with theme version 3 * Custom color for the right trailering icon, default will be derived from theme */ - traileringIconColor?: string; + traileringIconColor?: ColorValue; /** * Callback to execute on the right trailering icon button press. */ @@ -101,7 +102,7 @@ export type Props = React.ComponentPropsWithRef & { * Works only when `mode` is set to "bar". */ right?: (props: { - color: string; + color: ColorValue; style: Style; testID: string; }) => React.ReactNode; diff --git a/src/components/Surface.tsx b/src/components/Surface.tsx index 9c11790687..fdfed67696 100644 --- a/src/components/Surface.tsx +++ b/src/components/Surface.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Animated, + ColorValue, Platform, ShadowStyleIOS, StyleProp, @@ -83,7 +84,7 @@ const outerLayerStyleProperties: (keyof ViewStyle)[] = [ function getStyleForShadowLayer( elevation: SurfaceElevation, layer: 0 | 1, - shadowColor: string + shadowColor: ColorValue ): Animated.WithAnimatedValue { if (isAnimatedValue(elevation)) { return { @@ -122,8 +123,10 @@ const SurfaceIOS = forwardRef< View, Omit & { elevation: SurfaceElevation; - backgroundColor?: string | Animated.AnimatedInterpolation; - shadowColor: string; + backgroundColor?: + | ColorValue + | Animated.AnimatedInterpolation; + shadowColor: ColorValue; } >( ( diff --git a/src/components/Switch/utils.ts b/src/components/Switch/utils.ts index 7b05c42ee2..cebf7264f2 100644 --- a/src/components/Switch/utils.ts +++ b/src/components/Switch/utils.ts @@ -1,4 +1,4 @@ -import { Platform } from 'react-native'; +import { Platform, type ColorValue } from 'react-native'; import setColor from 'color'; @@ -23,7 +23,7 @@ const getCheckedColor = ({ color, }: { theme: InternalTheme; - color?: string; + color?: ColorValue; }) => { if (color) { return color; @@ -37,7 +37,7 @@ const getThumbTintColor = ({ disabled, value, checkedColor, -}: BaseProps & { checkedColor: string }) => { +}: BaseProps & { checkedColor: ColorValue }) => { const isIOS = Platform.OS === 'ios'; if (isIOS) { @@ -66,7 +66,7 @@ const getOnTintColor = ({ disabled, value, checkedColor, -}: BaseProps & { checkedColor: string }) => { +}: BaseProps & { checkedColor: ColorValue }) => { const isIOS = Platform.OS === 'ios'; if (isIOS) { @@ -95,7 +95,7 @@ export const getSwitchColor = ({ disabled, value, color, -}: BaseProps & { color?: string }) => { +}: BaseProps & { color?: ColorValue }) => { const checkedColor = getCheckedColor({ theme, color }); return { diff --git a/src/components/TextInput/Addons/Outline.tsx b/src/components/TextInput/Addons/Outline.tsx index 39ffa48b56..c40f5ffd62 100644 --- a/src/components/TextInput/Addons/Outline.tsx +++ b/src/components/TextInput/Addons/Outline.tsx @@ -10,10 +10,10 @@ import { import { TextInputLabelProp } from '../types'; type OutlineProps = { - activeColor: string; + activeColor: ColorValue; backgroundColor: ColorValue; hasActiveOutline?: boolean; - outlineColor?: string; + outlineColor?: ColorValue; roundness?: number; label?: TextInputLabelProp; style?: StyleProp; diff --git a/src/components/TextInput/Addons/Underline.tsx b/src/components/TextInput/Addons/Underline.tsx index a004900b95..b329ef1190 100644 --- a/src/components/TextInput/Addons/Underline.tsx +++ b/src/components/TextInput/Addons/Underline.tsx @@ -1,5 +1,11 @@ import * as React from 'react'; -import { Animated, StyleSheet, StyleProp, ViewStyle } from 'react-native'; +import { + Animated, + ColorValue, + StyleProp, + StyleSheet, + ViewStyle, +} from 'react-native'; import type { ThemeProp } from '../../../types'; @@ -9,10 +15,10 @@ type UnderlineProps = { }; error?: boolean; colors?: { - error?: string; + error?: ColorValue; }; - activeColor: string; - underlineColorCustom?: string; + activeColor: ColorValue; + underlineColorCustom?: ColorValue; hasActiveOutline?: boolean; disabledOpacity?: number; style?: StyleProp; diff --git a/src/components/TextInput/Adornment/TextInputIcon.tsx b/src/components/TextInput/Adornment/TextInputIcon.tsx index 0fc2c7c906..4652c7612c 100644 --- a/src/components/TextInput/Adornment/TextInputIcon.tsx +++ b/src/components/TextInput/Adornment/TextInputIcon.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { + ColorValue, GestureResponderEvent, StyleProp, StyleSheet, @@ -35,7 +36,9 @@ export type Props = $Omit< /** * Color of the icon or a function receiving a boolean indicating whether the TextInput is focused and returning the color. */ - color?: ((isTextInputFocused: boolean) => string | undefined) | string; + color?: + | ColorValue + | ((isTextInputFocused: boolean) => ColorValue | undefined); style?: StyleProp; /** * @optional diff --git a/src/components/TextInput/Adornment/utils.ts b/src/components/TextInput/Adornment/utils.ts index 40acdafbdb..64e5462b74 100644 --- a/src/components/TextInput/Adornment/utils.ts +++ b/src/components/TextInput/Adornment/utils.ts @@ -1,3 +1,5 @@ +import type { ColorValue } from 'react-native'; + import { tokens } from '../../../theme/tokens'; import type { InternalTheme } from '../../../types'; @@ -22,7 +24,9 @@ export function getIconColor({ customColor, }: BaseProps & { isTextInputFocused: boolean; - customColor?: ((isTextInputFocused: boolean) => string | undefined) | string; + customColor?: + | ColorValue + | ((isTextInputFocused: boolean) => ColorValue | undefined); }) { const color = typeof customColor === 'function' diff --git a/src/components/TextInput/helpers.tsx b/src/components/TextInput/helpers.tsx index 633c03f279..0dd1aad7e6 100644 --- a/src/components/TextInput/helpers.tsx +++ b/src/components/TextInput/helpers.tsx @@ -1,3 +1,5 @@ +import type { ColorValue } from 'react-native'; + import { AdornmentSide, AdornmentType } from './Adornment/enums'; import type { AdornmentConfig } from './Adornment/types'; import { @@ -302,7 +304,7 @@ type Mode = 'flat' | 'outlined'; const getInputTextColor = ({ theme, textColor, -}: BaseProps & { textColor?: string }) => { +}: BaseProps & { textColor?: ColorValue }) => { if (textColor) { return textColor; } @@ -318,8 +320,8 @@ const getActiveColor = ({ mode, }: BaseProps & { error?: boolean; - activeUnderlineColor?: string; - activeOutlineColor?: string; + activeUnderlineColor?: ColorValue; + activeOutlineColor?: ColorValue; mode?: Mode; }) => { const isFlat = mode === 'flat'; @@ -344,8 +346,8 @@ const getSelectionColor = ({ activeColor, customSelectionColor, }: { - activeColor: string; - customSelectionColor?: string; + activeColor: ColorValue; + customSelectionColor?: ColorValue; }) => { if (typeof customSelectionColor !== 'undefined') { return customSelectionColor; @@ -365,7 +367,7 @@ const getFlatUnderlineColor = ({ theme, disabled, underlineColor, -}: BaseProps & { underlineColor?: string }) => { +}: BaseProps & { underlineColor?: ColorValue }) => { if (!disabled && underlineColor) { return underlineColor; } @@ -377,7 +379,7 @@ const getOutlinedOutlineInputColor = ({ theme, disabled, customOutlineColor, -}: BaseProps & { customOutlineColor?: string }) => { +}: BaseProps & { customOutlineColor?: ColorValue }) => { if (!disabled && customOutlineColor) { return customOutlineColor; } @@ -401,10 +403,10 @@ export const getFlatInputColors = ({ error, theme, }: { - underlineColor?: string; - activeUnderlineColor?: string; - customSelectionColor?: string; - textColor?: string; + underlineColor?: ColorValue; + activeUnderlineColor?: ColorValue; + customSelectionColor?: ColorValue; + textColor?: ColorValue; disabled?: boolean; error?: boolean; theme: InternalTheme; @@ -448,10 +450,10 @@ export const getOutlinedInputColors = ({ error, theme, }: { - activeOutlineColor?: string; - customOutlineColor?: string; - customSelectionColor?: string; - textColor?: string; + activeOutlineColor?: ColorValue; + customOutlineColor?: ColorValue; + customSelectionColor?: ColorValue; + textColor?: ColorValue; disabled?: boolean; error?: boolean; theme: InternalTheme; diff --git a/src/components/TextInput/types.tsx b/src/components/TextInput/types.tsx index 49bc46e121..ffb1f0bda0 100644 --- a/src/components/TextInput/types.tsx +++ b/src/components/TextInput/types.tsx @@ -25,13 +25,13 @@ type TextInputProps = React.ComponentPropsWithRef & { placeholder?: string; error?: boolean; onChangeText?: Function; - selectionColor?: string; - cursorColor?: string; - underlineColor?: string; - activeUnderlineColor?: string; - outlineColor?: string; - activeOutlineColor?: string; - textColor?: string; + selectionColor?: ColorValue; + cursorColor?: ColorValue; + underlineColor?: ColorValue; + activeUnderlineColor?: ColorValue; + outlineColor?: ColorValue; + activeOutlineColor?: ColorValue; + textColor?: ColorValue; dense?: boolean; multiline?: boolean; numberOfLines?: number; @@ -54,11 +54,11 @@ export type RenderProps = { placeholder?: string; placeholderTextColor?: ColorValue; editable?: boolean; - selectionColor?: string; - cursorColor?: string; + selectionColor?: ColorValue; + cursorColor?: ColorValue; onFocus?: (args: any) => void; onBlur?: (args: any) => void; - underlineColorAndroid?: string; + underlineColorAndroid?: ColorValue; onLayout?: (args: any) => void; style: any; multiline?: boolean; @@ -114,13 +114,13 @@ export type LabelProps = { paddingLeft?: number; paddingRight?: number; labelTranslationXOffset?: number; - placeholderColor: string | null; + placeholderColor: ColorValue | null; disabledOpacity?: number; backgroundColor?: ColorValue; label?: TextInputLabelProp | null; hasActiveOutline?: boolean | null; - activeColor: string; - errorColor?: string; + activeColor: ColorValue; + errorColor?: ColorValue; labelError?: boolean | null; onLayoutAnimatedText: (args: any) => void; onLabelTextLayout: (event: NativeSyntheticEvent) => void; diff --git a/src/components/TouchableRipple/TouchableRipple.tsx b/src/components/TouchableRipple/TouchableRipple.tsx index ecf848da32..86b5e14c5e 100644 --- a/src/components/TouchableRipple/TouchableRipple.tsx +++ b/src/components/TouchableRipple/TouchableRipple.tsx @@ -122,7 +122,12 @@ const TouchableRipple = ( theme, rippleColor, }); - const hoverColor = color(calculatedRippleColor).fade(0.5).rgb().string(); + // Web-only style. PlatformColor doesn't exist on web, so the calculated + // ripple color is effectively always a string here. + const hoverColor = + typeof calculatedRippleColor === 'string' + ? color(calculatedRippleColor).fade(0.5).rgb().string() + : calculatedRippleColor; const { rippleEffectEnabled } = React.useContext(SettingsContext); const { onPress, onLongPress, onPressIn, onPressOut } = rest; diff --git a/src/components/TouchableRipple/utils.ts b/src/components/TouchableRipple/utils.ts index 10dcef6703..f7471f2d87 100644 --- a/src/components/TouchableRipple/utils.ts +++ b/src/components/TouchableRipple/utils.ts @@ -1,7 +1,5 @@ import type { ColorValue } from 'react-native'; -import color from 'color'; - import type { InternalTheme } from '../../types'; const getUnderlayColor = ({ @@ -15,7 +13,7 @@ const getUnderlayColor = ({ return underlayColor; } - return color(calculatedRippleColor).rgb().string(); + return calculatedRippleColor; }; const getRippleColor = ({ diff --git a/src/components/__tests__/BottomNavigation.test.tsx b/src/components/__tests__/BottomNavigation.test.tsx index 0ebc9458ed..6034efd07c 100644 --- a/src/components/__tests__/BottomNavigation.test.tsx +++ b/src/components/__tests__/BottomNavigation.test.tsx @@ -242,7 +242,9 @@ it('renders custom icon and label in shifting bottom navigation', () => { )} renderLabel={({ route, color }) => ( - {route.title} + + {route.title} + )} /> ).toJSON(); @@ -261,7 +263,9 @@ it('renders custom icon and label in non-shifting bottom navigation', () => { )} renderLabel={({ route, color }) => ( - {route.title} + + {route.title} + )} /> ).toJSON(); diff --git a/src/react-navigation/views/MaterialBottomTabView.tsx b/src/react-navigation/views/MaterialBottomTabView.tsx index 07d3644d16..fe540f7478 100644 --- a/src/react-navigation/views/MaterialBottomTabView.tsx +++ b/src/react-navigation/views/MaterialBottomTabView.tsx @@ -87,7 +87,14 @@ export default function MaterialBottomTabView({ } if (typeof options.tabBarIcon === 'function') { - return options.tabBarIcon({ focused, color }); + // react-navigation's tabBarIcon types `color` as `string`. When + // dynamicColor is on (Android), `color` is an OpaqueColorValue and + // can't be forwarded; fall back to an empty string. Consumers who + // need device-tinted tab icons should use BottomNavigation directly. + return options.tabBarIcon({ + focused, + color: typeof color === 'string' ? color : '', + }); } return null; diff --git a/src/theme/schemes/DynamicTheme.android.tsx b/src/theme/schemes/DynamicTheme.android.tsx index 935badb687..097109ea09 100644 --- a/src/theme/schemes/DynamicTheme.android.tsx +++ b/src/theme/schemes/DynamicTheme.android.tsx @@ -1,4 +1,4 @@ -import { Platform, PlatformColor } from 'react-native'; +import { Platform, PlatformColor, type ColorValue } from 'react-native'; import { DarkTheme } from './DarkTheme'; import { LightTheme } from './LightTheme'; @@ -7,8 +7,7 @@ import type { Theme, ThemeColors } from '../types'; const apiLevel = Platform.Version as number; -const ac = (name: string) => - PlatformColor(`@android:color/${name}`) as unknown as string; +const ac = (name: string) => PlatformColor(`@android:color/${name}`); /** * Picks the correct color value for the current Android API level. @@ -20,7 +19,7 @@ const ac = (name: string) => * (MCL @color/m3_ref_palette_* resources and error roles). Those fall through to ref. * @see https://github.com/material-components/material-components-android/blob/master/docs/theming/Color.md */ -const pick = (api34: string, api31: string | null, ref: string): string => +const pick = (api34: string, api31: string | null, ref: string): ColorValue => apiLevel >= 34 ? ac(api34) : apiLevel >= 31 && api31 !== null diff --git a/src/theme/tokens/sys/elevation.ts b/src/theme/tokens/sys/elevation.ts index 82887d4d2d..f02be56f29 100644 --- a/src/theme/tokens/sys/elevation.ts +++ b/src/theme/tokens/sys/elevation.ts @@ -1,7 +1,7 @@ // M3 elevation tokens and shadow builder per spec: // https://m3.material.io/styles/elevation/tokens -import { Animated } from 'react-native'; +import { Animated, type ColorValue } from 'react-native'; import { isAnimatedValue } from '../../../utils/animations'; import type { Elevation, ThemeElevation } from '../../types'; @@ -34,7 +34,7 @@ export const shadowLayers = [ export function shadow( elevation: number | Animated.Value = 0, - shadowColor: string + shadowColor: ColorValue ) { if (isAnimatedValue(elevation)) { return { diff --git a/src/theme/types/color.ts b/src/theme/types/color.ts index cc7ed49b86..1e49724b3e 100644 --- a/src/theme/types/color.ts +++ b/src/theme/types/color.ts @@ -1,59 +1,61 @@ +import type { ColorValue } from 'react-native'; + import type { ElevationColors } from './elevation'; export type ThemeColors = { - primary: string; - primaryContainer: string; - secondary: string; - secondaryContainer: string; - tertiary: string; - tertiaryContainer: string; - surface: string; - surfaceDim: string; - surfaceBright: string; - surfaceContainerLowest: string; - surfaceContainerLow: string; - surfaceContainer: string; - surfaceContainerHigh: string; - surfaceContainerHighest: string; - surfaceVariant: string; - background: string; - error: string; - errorContainer: string; - onPrimary: string; - onPrimaryContainer: string; - onSecondary: string; - onSecondaryContainer: string; - onTertiary: string; - onTertiaryContainer: string; - onSurface: string; - onSurfaceVariant: string; - onError: string; - onErrorContainer: string; - onBackground: string; - outline: string; - outlineVariant: string; - inverseSurface: string; - inverseOnSurface: string; - inversePrimary: string; - primaryFixed: string; - primaryFixedDim: string; - onPrimaryFixed: string; - onPrimaryFixedVariant: string; - secondaryFixed: string; - secondaryFixedDim: string; - onSecondaryFixed: string; - onSecondaryFixedVariant: string; - tertiaryFixed: string; - tertiaryFixedDim: string; - onTertiaryFixed: string; - onTertiaryFixedVariant: string; - shadow: string; - scrim: string; + primary: ColorValue; + primaryContainer: ColorValue; + secondary: ColorValue; + secondaryContainer: ColorValue; + tertiary: ColorValue; + tertiaryContainer: ColorValue; + surface: ColorValue; + surfaceDim: ColorValue; + surfaceBright: ColorValue; + surfaceContainerLowest: ColorValue; + surfaceContainerLow: ColorValue; + surfaceContainer: ColorValue; + surfaceContainerHigh: ColorValue; + surfaceContainerHighest: ColorValue; + surfaceVariant: ColorValue; + background: ColorValue; + error: ColorValue; + errorContainer: ColorValue; + onPrimary: ColorValue; + onPrimaryContainer: ColorValue; + onSecondary: ColorValue; + onSecondaryContainer: ColorValue; + onTertiary: ColorValue; + onTertiaryContainer: ColorValue; + onSurface: ColorValue; + onSurfaceVariant: ColorValue; + onError: ColorValue; + onErrorContainer: ColorValue; + onBackground: ColorValue; + outline: ColorValue; + outlineVariant: ColorValue; + inverseSurface: ColorValue; + inverseOnSurface: ColorValue; + inversePrimary: ColorValue; + primaryFixed: ColorValue; + primaryFixedDim: ColorValue; + onPrimaryFixed: ColorValue; + onPrimaryFixedVariant: ColorValue; + secondaryFixed: ColorValue; + secondaryFixedDim: ColorValue; + onSecondaryFixed: ColorValue; + onSecondaryFixedVariant: ColorValue; + tertiaryFixed: ColorValue; + tertiaryFixedDim: ColorValue; + onTertiaryFixed: ColorValue; + onTertiaryFixedVariant: ColorValue; + shadow: ColorValue; + scrim: ColorValue; /** Pre-computed state layer color at press opacity (0.10). * Used for ripple effects. Avoids runtime alpha manipulation * which is incompatible with PlatformColor on Android. * TODO: revisit after https://github.com/facebook/react-native/pull/56395 * @see https://m3.material.io/foundations/interaction/states/state-layers */ - stateLayerPressed: string; + stateLayerPressed: ColorValue; elevation: ElevationColors; };