From 1cd6836f1d10bcdf7f96d9e4b9f7de0ddea9391f Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 28 Feb 2023 14:16:23 +0100 Subject: [PATCH] feat: add ability to customize the fonts with the theme (#11243) **Motivation** React Navigation 5 introduced a theming API. However, the theme only supported customizing colors. With this change, the theme can be used to customize font family as well - making it simpler to use a specific font across all of React Navigation's UI elements. **Test plan** Specify a custom font in the theme and check that React Navigation's UI elements use the new font. The following screenshots show the usage of the "Lato" font. SCR-20230224-qy4 SCR-20230224-qyl SCR-20230224-qz6 SCR-20230224-qzt --- packages/bottom-tabs/src/views/Badge.tsx | 5 +- .../bottom-tabs/src/views/BottomTabItem.tsx | 5 +- packages/drawer/src/views/DrawerItem.tsx | 10 +-- .../elements/src/Header/HeaderBackButton.tsx | 5 +- packages/elements/src/Header/HeaderTitle.tsx | 9 +-- .../src/views/MaterialTopTabBar.tsx | 9 ++- .../native-stack/src/views/HeaderConfig.tsx | 63 ++++++++++++++----- packages/native/src/theming/DarkTheme.tsx | 2 + packages/native/src/theming/DefaultTheme.tsx | 2 + packages/native/src/theming/fonts.tsx | 63 +++++++++++++++++++ packages/native/src/types.tsx | 22 +++++++ 11 files changed, 156 insertions(+), 39 deletions(-) create mode 100644 packages/native/src/theming/fonts.tsx diff --git a/packages/bottom-tabs/src/views/Badge.tsx b/packages/bottom-tabs/src/views/Badge.tsx index 403d023ce1..798b7b3db6 100644 --- a/packages/bottom-tabs/src/views/Badge.tsx +++ b/packages/bottom-tabs/src/views/Badge.tsx @@ -32,7 +32,7 @@ export function Badge({ const [opacity] = React.useState(() => new Animated.Value(visible ? 1 : 0)); const [rendered, setRendered] = React.useState(visible); - const theme = useTheme(); + const { colors, fonts } = useTheme(); React.useEffect(() => { if (!rendered) { @@ -61,7 +61,7 @@ export function Badge({ } // @ts-expect-error: backgroundColor definitely exists - const { backgroundColor = theme.colors.notification, ...restStyle } = + const { backgroundColor = colors.notification, ...restStyle } = StyleSheet.flatten(style) || {}; const textColor = color(backgroundColor).isLight() ? 'black' : 'white'; @@ -90,6 +90,7 @@ export function Badge({ fontSize, borderRadius, }, + fonts.regular, styles.container, restStyle, ]} diff --git a/packages/bottom-tabs/src/views/BottomTabItem.tsx b/packages/bottom-tabs/src/views/BottomTabItem.tsx index ef645470e4..b4dec2dc2d 100644 --- a/packages/bottom-tabs/src/views/BottomTabItem.tsx +++ b/packages/bottom-tabs/src/views/BottomTabItem.tsx @@ -195,7 +195,7 @@ export function BottomTabItem({ iconStyle, style, }: Props) { - const { colors } = useTheme(); + const { colors, fonts } = useTheme(); const activeTintColor = customActiveTintColor === undefined @@ -219,8 +219,9 @@ export function BottomTabItem({ {label} diff --git a/packages/elements/src/Header/HeaderBackButton.tsx b/packages/elements/src/Header/HeaderBackButton.tsx index c3808e1c1b..b99edb67b0 100644 --- a/packages/elements/src/Header/HeaderBackButton.tsx +++ b/packages/elements/src/Header/HeaderBackButton.tsx @@ -33,7 +33,7 @@ export function HeaderBackButton({ testID, style, }: HeaderBackButtonProps) { - const { colors } = useTheme(); + const { colors, fonts } = useTheme(); const [initialLabelWidth, setInitialLabelWidth] = React.useState< undefined | number @@ -106,8 +106,9 @@ export function HeaderBackButton({ leftLabelText === label ? handleLabelLayout : undefined } style={[ - styles.label, tintColor ? { color: tintColor } : null, + fonts.regular, + styles.label, labelStyle, ]} numberOfLines={1} diff --git a/packages/elements/src/Header/HeaderTitle.tsx b/packages/elements/src/Header/HeaderTitle.tsx index 0dc574d93a..e3833b9c65 100644 --- a/packages/elements/src/Header/HeaderTitle.tsx +++ b/packages/elements/src/Header/HeaderTitle.tsx @@ -16,7 +16,7 @@ type Props = Omit & { }; export function HeaderTitle({ tintColor, style, ...rest }: Props) { - const { colors } = useTheme(); + const { colors, fonts } = useTheme(); return ( @@ -37,16 +38,12 @@ const styles = StyleSheet.create({ title: Platform.select({ ios: { fontSize: 17, - fontWeight: '600', }, android: { fontSize: 20, - fontFamily: 'sans-serif-medium', - fontWeight: 'normal', }, default: { fontSize: 18, - fontWeight: '500', }, }), }); diff --git a/packages/material-top-tabs/src/views/MaterialTopTabBar.tsx b/packages/material-top-tabs/src/views/MaterialTopTabBar.tsx index 104595019f..3bb5c0bebf 100644 --- a/packages/material-top-tabs/src/views/MaterialTopTabBar.tsx +++ b/packages/material-top-tabs/src/views/MaterialTopTabBar.tsx @@ -17,7 +17,7 @@ export function MaterialTopTabBar({ descriptors, ...rest }: MaterialTopTabBarProps) { - const { colors } = useTheme(); + const { colors, fonts } = useTheme(); const focusedOptions = descriptors[state.routes[state.index].key].options; @@ -103,7 +103,12 @@ export function MaterialTopTabBar({ if (typeof label === 'string') { return ( {label} diff --git a/packages/native-stack/src/views/HeaderConfig.tsx b/packages/native-stack/src/views/HeaderConfig.tsx index ca364c1740..5629425a64 100644 --- a/packages/native-stack/src/views/HeaderConfig.tsx +++ b/packages/native-stack/src/views/HeaderConfig.tsx @@ -59,15 +59,22 @@ export function HeaderConfig({ title, canGoBack, }: Props): JSX.Element { - const { colors } = useTheme(); + const { colors, fonts } = useTheme(); const tintColor = headerTintColor ?? (Platform.OS === 'ios' ? colors.primary : colors.text); const headerBackTitleStyleFlattened = - StyleSheet.flatten(headerBackTitleStyle) || {}; + StyleSheet.flatten([headerBackTitleStyle, fonts.regular]) || {}; const headerLargeTitleStyleFlattened = - StyleSheet.flatten(headerLargeTitleStyle) || {}; - const headerTitleStyleFlattened = StyleSheet.flatten(headerTitleStyle) || {}; + StyleSheet.flatten([ + headerLargeTitleStyle, + Platform.select({ ios: fonts.heavy, default: fonts.medium }), + ]) || {}; + const headerTitleStyleFlattened = + StyleSheet.flatten([ + headerTitleStyle, + Platform.select({ ios: fonts.bold, default: fonts.medium }), + ]) || {}; const headerStyleFlattened = StyleSheet.flatten(headerStyle) || {}; const headerLargeStyleFlattened = StyleSheet.flatten(headerLargeStyle) || {}; @@ -78,12 +85,33 @@ export function HeaderConfig({ headerTitleStyleFlattened.fontFamily, ]); + const backTitleFontSize = + 'fontSize' in headerBackTitleStyleFlattened + ? headerBackTitleStyleFlattened.fontSize + : undefined; + const titleText = getHeaderTitle({ title, headerTitle }, route.name); const titleColor = - headerTitleStyleFlattened.color ?? headerTintColor ?? colors.text; - const titleFontSize = headerTitleStyleFlattened.fontSize; + 'color' in headerTitleStyleFlattened + ? headerTitleStyleFlattened.color + : headerTintColor ?? colors.text; + const titleFontSize = + 'fontSize' in headerTitleStyleFlattened + ? headerTitleStyleFlattened.fontSize + : undefined; const titleFontWeight = headerTitleStyleFlattened.fontWeight; + const largeTitleBackgroundColor = headerLargeStyleFlattened.backgroundColor; + const largeTitleColor = + 'color' in headerLargeTitleStyleFlattened + ? headerLargeTitleStyleFlattened.color + : undefined; + const largeTitleFontSize = + 'fontSize' in headerLargeTitleStyleFlattened + ? headerLargeTitleStyleFlattened.fontSize + : undefined; + const largeTitleFontWeight = headerLargeTitleStyleFlattened.fontWeight; + const headerTitleStyleSupported: TextStyle = { color: titleColor }; if (headerTitleStyleFlattened.fontFamily != null) { @@ -98,6 +126,12 @@ export function HeaderConfig({ headerTitleStyleSupported.fontWeight = titleFontWeight; } + const headerBackgroundColor = + headerStyleFlattened.backgroundColor ?? + (headerBackground != null || headerTransparent + ? 'transparent' + : colors.card); + const headerLeftElement = headerLeft?.({ tintColor, canGoBack, @@ -162,15 +196,10 @@ export function HeaderConfig({ ) : null} ); diff --git a/packages/native/src/types.tsx b/packages/native/src/types.tsx index 20282f6b07..94e1ca308c 100644 --- a/packages/native/src/types.tsx +++ b/packages/native/src/types.tsx @@ -6,6 +6,22 @@ import type { Route, } from '@react-navigation/core'; +type FontStyle = { + fontFamily: string; + fontWeight: + | 'normal' + | 'bold' + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900'; +}; + export type Theme = { dark: boolean; colors: { @@ -16,6 +32,12 @@ export type Theme = { border: string; notification: string; }; + fonts: { + regular: FontStyle; + medium: FontStyle; + bold: FontStyle; + heavy: FontStyle; + }; }; export type LinkingOptions = {