diff --git a/src/styles/ThemeStylesContext.ts b/src/styles/ThemeStylesContext.ts index 1c81ab3b39a5..3df2b19b31bf 100644 --- a/src/styles/ThemeStylesContext.ts +++ b/src/styles/ThemeStylesContext.ts @@ -1,6 +1,6 @@ import React from 'react'; -import styles from './styles'; +import styles, {type Styles} from './styles'; -const ThemeStylesContext = React.createContext(styles); +const ThemeStylesContext = React.createContext(styles); export default ThemeStylesContext; diff --git a/src/styles/ThemeStylesProvider.tsx b/src/styles/ThemeStylesProvider.tsx index 25ce1f58b65e..7f26422e98ce 100644 --- a/src/styles/ThemeStylesProvider.tsx +++ b/src/styles/ThemeStylesProvider.tsx @@ -1,12 +1,9 @@ /* eslint-disable react/jsx-props-no-spreading */ import React, {useMemo} from 'react'; -// TODO: Rename this to "styles" once the app is migrated to theme switching hooks and HOCs -import {stylesGenerator as stylesUntyped} from './styles'; +import {stylesGenerator} from './styles'; import useTheme from './themes/useTheme'; import ThemeStylesContext from './ThemeStylesContext'; -const styles = stylesUntyped; - type ThemeStylesProviderProps = { children: React.ReactNode; }; @@ -14,7 +11,7 @@ type ThemeStylesProviderProps = { function ThemeStylesProvider({children}: ThemeStylesProviderProps) { const theme = useTheme(); - const themeStyles = useMemo(() => styles(theme), [theme]); + const themeStyles = useMemo(() => stylesGenerator(theme), [theme]); return {children}; } diff --git a/src/styles/colors.js b/src/styles/colors.ts similarity index 85% rename from src/styles/colors.js rename to src/styles/colors.ts index 9ac3226a1b80..fbe694e051ee 100644 --- a/src/styles/colors.js +++ b/src/styles/colors.ts @@ -1,7 +1,12 @@ +import {Color} from './themes/types'; + /** - * DO NOT import colors.js into files. Use ../themes/default.js instead. + * DO NOT import colors.js into files. Use the theme switching hooks and HOCs instead. + * For functional components, you can use the `useTheme` and `useThemeStyles` hooks + * For class components, you can use the `withTheme` and `withThemeStyles` HOCs */ -export default { +const colors: Record = { + // Brand Colors black: '#000000', white: '#FFFFFF', ivory: '#fffaf0', @@ -91,3 +96,5 @@ export default { ice700: '#28736D', ice800: '#134038', }; + +export default colors; diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 0b4f2af92867..73f9aa823f40 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -19,7 +19,7 @@ import overflowXHidden from './overflowXHidden'; import pointerEventsAuto from './pointerEventsAuto'; import pointerEventsNone from './pointerEventsNone'; import defaultTheme from './themes/default'; -import {ThemeDefault} from './themes/types'; +import {ThemeColors} from './themes/types'; import borders from './utilities/borders'; import cursor from './utilities/cursor'; import display from './utilities/display'; @@ -80,7 +80,7 @@ const touchCalloutNone: Pick = Browser.isMobile // to prevent vertical text offset in Safari for badges, new lineHeight values have been added const lineHeightBadge: Pick = Browser.isSafari() ? {lineHeight: variables.lineHeightXSmall} : {lineHeight: variables.lineHeightNormal}; -const picker = (theme: ThemeDefault) => +const picker = (theme: ThemeColors) => ({ backgroundColor: theme.transparent, color: theme.text, @@ -96,14 +96,14 @@ const picker = (theme: ThemeDefault) => textAlign: 'left', } satisfies TextStyle); -const link = (theme: ThemeDefault) => +const link = (theme: ThemeColors) => ({ color: theme.link, textDecorationColor: theme.link, fontFamily: fontFamily.EXP_NEUE, } satisfies ViewStyle & MixedStyleDeclaration); -const baseCodeTagStyles = (theme: ThemeDefault) => +const baseCodeTagStyles = (theme: ThemeColors) => ({ borderWidth: 1, borderRadius: 5, @@ -116,7 +116,7 @@ const headlineFont = { fontWeight: '500', } satisfies TextStyle; -const webViewStyles = (theme: ThemeDefault) => +const webViewStyles = (theme: ThemeColors) => ({ // As of react-native-render-html v6, don't declare distinct styles for // custom renderers, the API for custom renderers has changed. Declare the @@ -211,7 +211,7 @@ const webViewStyles = (theme: ThemeDefault) => }, } satisfies WebViewStyle); -const styles = (theme: ThemeDefault) => +const styles = (theme: ThemeColors) => ({ // Add all of our utility and helper styles ...spacing, @@ -4014,12 +4014,8 @@ const styles = (theme: ThemeDefault) => }, } satisfies Styles); -// For now we need to export the styles function that takes the theme as an argument -// as something named different than "styles", because a lot of files import the "defaultStyles" -// as "styles", which causes ESLint to throw an error. -// TODO: Remove "stylesGenerator" and instead only return "styles" once the app is migrated to theme switching hooks and HOCs and "styles/theme/default.js" is not used anywhere anymore (GH issue: https://github.com/Expensify/App/issues/27337) const stylesGenerator = styles; const defaultStyles = styles(defaultTheme); export default defaultStyles; -export {stylesGenerator}; +export {stylesGenerator, type Styles}; diff --git a/src/styles/themes/ThemeContext.js b/src/styles/themes/ThemeContext.js deleted file mode 100644 index 30d476c22d9c..000000000000 --- a/src/styles/themes/ThemeContext.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import defaultColors from './default'; - -const ThemeContext = React.createContext(defaultColors); - -export default ThemeContext; diff --git a/src/styles/themes/ThemeContext.ts b/src/styles/themes/ThemeContext.ts new file mode 100644 index 000000000000..8c57cc9c7e9f --- /dev/null +++ b/src/styles/themes/ThemeContext.ts @@ -0,0 +1,7 @@ +import React from 'react'; +import darkTheme from './default'; +import {ThemeColors} from './types'; + +const ThemeContext = React.createContext(darkTheme); + +export default ThemeContext; diff --git a/src/styles/themes/ThemeProvider.js b/src/styles/themes/ThemeProvider.tsx similarity index 80% rename from src/styles/themes/ThemeProvider.js rename to src/styles/themes/ThemeProvider.tsx index 58d0baedbe06..50bfb3b045f4 100644 --- a/src/styles/themes/ThemeProvider.js +++ b/src/styles/themes/ThemeProvider.tsx @@ -2,8 +2,8 @@ import PropTypes from 'prop-types'; import React, {useMemo} from 'react'; import CONST from '@src/CONST'; -// Going to eventually import the light theme here too import darkTheme from './default'; +import lightTheme from './light'; import ThemeContext from './ThemeContext'; import useThemePreference from './useThemePreference'; @@ -12,10 +12,10 @@ const propTypes = { children: PropTypes.node.isRequired, }; -function ThemeProvider(props) { +function ThemeProvider(props: React.PropsWithChildren) { const themePreference = useThemePreference(); - const theme = useMemo(() => (themePreference === CONST.THEME.LIGHT ? /* TODO: replace with light theme */ darkTheme : darkTheme), [themePreference]); + const theme = useMemo(() => (themePreference === CONST.THEME.LIGHT ? lightTheme : darkTheme), [themePreference]); return {props.children}; } diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index 98ff8773fb51..dd92b1ce71d9 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,6 +1,6 @@ import colors from '@styles/colors'; import SCREENS from '@src/SCREENS'; -import type {ThemeBase} from './types'; +import {ThemeColors} from './types'; const darkTheme = { // Figma keys @@ -83,19 +83,18 @@ const darkTheme = { starDefaultBG: 'rgb(254, 228, 94)', loungeAccessOverlay: colors.blue800, mapAttributionText: colors.black, - PAGE_BACKGROUND_COLORS: {}, white: colors.white, -} satisfies ThemeBase; -darkTheme.PAGE_BACKGROUND_COLORS = { - [SCREENS.HOME]: darkTheme.sidebar, - [SCREENS.SAVE_THE_WORLD.ROOT]: colors.tangerine800, - [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, - [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, - [SCREENS.SETTINGS.WALLET]: colors.darkAppBackground, - [SCREENS.SETTINGS.SECURITY]: colors.ice500, - [SCREENS.SETTINGS.STATUS]: colors.green700, - [SCREENS.SETTINGS.ROOT]: darkTheme.sidebar, -}; + PAGE_BACKGROUND_COLORS: { + [SCREENS.HOME]: colors.darkHighlightBackground, + [SCREENS.SAVE_THE_WORLD.ROOT]: colors.tangerine800, + [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, + [SCREENS.SETTINGS.WALLET]: colors.darkAppBackground, + [SCREENS.SETTINGS.SECURITY]: colors.ice500, + [SCREENS.SETTINGS.STATUS]: colors.green700, + [SCREENS.SETTINGS.ROOT]: colors.darkHighlightBackground, + }, +} satisfies ThemeColors; export default darkTheme; diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index 624c7df0caa8..97fe2322945a 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,6 +1,6 @@ import colors from '@styles/colors'; import SCREENS from '@src/SCREENS'; -import type {ThemeDefault} from './types'; +import {ThemeColors} from './types'; const lightTheme = { // Figma keys @@ -16,9 +16,9 @@ const lightTheme = { iconSuccessFill: colors.green400, iconReversed: colors.lightAppBackground, iconColorfulBackground: `${colors.ivory}cc`, - textColorfulBackground: colors.ivory, textSupporting: colors.lightSupportingText, text: colors.lightPrimaryText, + textColorfulBackground: colors.ivory, link: colors.blue600, linkHover: colors.blue500, buttonDefaultBG: colors.lightDefaultButton, @@ -83,19 +83,18 @@ const lightTheme = { starDefaultBG: 'rgb(254, 228, 94)', loungeAccessOverlay: colors.blue800, mapAttributionText: colors.black, - PAGE_BACKGROUND_COLORS: {}, white: colors.white, -} satisfies ThemeDefault; -lightTheme.PAGE_BACKGROUND_COLORS = { - [SCREENS.HOME]: lightTheme.sidebar, - [SCREENS.SAVE_THE_WORLD.ROOT]: colors.tangerine800, - [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, - [SCREENS.SETTINGS.WALLET]: colors.darkAppBackground, - [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, - [SCREENS.SETTINGS.SECURITY]: colors.ice500, - [SCREENS.SETTINGS.STATUS]: colors.green700, - [SCREENS.SETTINGS.ROOT]: lightTheme.sidebar, -}; + PAGE_BACKGROUND_COLORS: { + [SCREENS.HOME]: colors.lightHighlightBackground, + [SCREENS.SAVE_THE_WORLD.ROOT]: colors.tangerine800, + [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, + [SCREENS.SETTINGS.WALLET]: colors.darkAppBackground, + [SCREENS.SETTINGS.SECURITY]: colors.ice500, + [SCREENS.SETTINGS.STATUS]: colors.green700, + [SCREENS.SETTINGS.ROOT]: colors.lightHighlightBackground, + }, +} satisfies ThemeColors; export default lightTheme; diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index 59e8001d29fe..4064dd289650 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -1,8 +1,89 @@ -import DeepRecord from '@src/types/utils/DeepRecord'; -import defaultTheme from './default'; +type Color = string; -type ThemeBase = DeepRecord; +type ThemeColors = { + // Figma keys + appBG: Color; + splashBG: Color; + highlightBG: Color; + border: Color; + borderLighter: Color; + borderFocus: Color; + icon: Color; + iconMenu: Color; + iconHovered: Color; + iconSuccessFill: Color; + iconReversed: Color; + iconColorfulBackground: Color; + textSupporting: Color; + text: Color; + textColorfulBackground: Color; + link: Color; + linkHover: Color; + buttonDefaultBG: Color; + buttonHoveredBG: Color; + buttonPressedBG: Color; + danger: Color; + dangerHover: Color; + dangerPressed: Color; + warning: Color; + success: Color; + successHover: Color; + successPressed: Color; + transparent: Color; + signInPage: Color; + dangerSection: Color; -type ThemeDefault = typeof defaultTheme; + // Additional keys + overlay: Color; + inverse: Color; + shadow: Color; + componentBG: Color; + hoverComponentBG: Color; + activeComponentBG: Color; + signInSidebar: Color; + sidebar: Color; + sidebarHover: Color; + heading: Color; + textLight: Color; + textDark: Color; + textReversed: Color; + textBackground: Color; + textMutedReversed: Color; + textError: Color; + offline: Color; + modalBackground: Color; + cardBG: Color; + cardBorder: Color; + spinner: Color; + unreadIndicator: Color; + placeholderText: Color; + heroCard: Color; + uploadPreviewActivityIndicator: Color; + dropUIBG: Color; + receiptDropUIBG: Color; + checkBox: Color; + pickerOptionsTextColor: Color; + imageCropBackgroundColor: Color; + fallbackIconColor: Color; + reactionActiveBackground: Color; + reactionActiveText: Color; + badgeAdHoc: Color; + badgeAdHocHover: Color; + mentionText: Color; + mentionBG: Color; + ourMentionText: Color; + ourMentionBG: Color; + tooltipSupportingText: Color; + tooltipPrimaryText: Color; + skeletonLHNIn: Color; + skeletonLHNOut: Color; + QRLogo: Color; + starDefaultBG: Color; + loungeAccessOverlay: Color; + mapAttributionText: Color; + white: Color; -export type {ThemeBase, ThemeDefault}; + PAGE_BACKGROUND_COLORS: Record; +}; + +export {type ThemeColors, type Color}; diff --git a/src/styles/themes/useTheme.js b/src/styles/themes/useTheme.ts similarity index 50% rename from src/styles/themes/useTheme.js rename to src/styles/themes/useTheme.ts index 8e88b23a7688..8bb4fe73c106 100644 --- a/src/styles/themes/useTheme.js +++ b/src/styles/themes/useTheme.ts @@ -1,11 +1,12 @@ import {useContext} from 'react'; import ThemeContext from './ThemeContext'; +import {ThemeColors} from './types'; -function useTheme() { +function useTheme(): ThemeColors { const theme = useContext(ThemeContext); if (!theme) { - throw new Error('StylesContext was null! Are you sure that you wrapped the component under a ?'); + throw new Error('ThemeContext was null! Are you sure that you wrapped the component under a ?'); } return theme; diff --git a/src/styles/themes/useThemePreference.js b/src/styles/themes/useThemePreference.ts similarity index 58% rename from src/styles/themes/useThemePreference.js rename to src/styles/themes/useThemePreference.ts index 8c26ad931d6d..ac6ac02933c7 100644 --- a/src/styles/themes/useThemePreference.js +++ b/src/styles/themes/useThemePreference.ts @@ -1,29 +1,31 @@ import {useContext, useEffect, useState} from 'react'; -import {Appearance} from 'react-native'; +import {Appearance, ColorSchemeName} from 'react-native'; import {PreferredThemeContext} from '@components/OnyxProvider'; import CONST from '@src/CONST'; +type ThemePreference = typeof CONST.THEME.LIGHT | typeof CONST.THEME.DARK; + function useThemePreference() { - const [themePreference, setThemePreference] = useState(CONST.THEME.DEFAULT); - const [systemTheme, setSystemTheme] = useState(); - const preferredThemeContext = useContext(PreferredThemeContext); + const [themePreference, setThemePreference] = useState(CONST.THEME.DEFAULT); + const [systemTheme, setSystemTheme] = useState(); + const preferredThemeFromStorage = useContext(PreferredThemeContext); useEffect(() => { // This is used for getting the system theme, that can be set in the OS's theme settings. This will always return either "light" or "dark" and will update automatically if the OS theme changes. const systemThemeSubscription = Appearance.addChangeListener(({colorScheme}) => setSystemTheme(colorScheme)); - return systemThemeSubscription.remove; + return () => systemThemeSubscription.remove(); }, []); useEffect(() => { - const theme = preferredThemeContext || CONST.THEME.DEFAULT; + const theme = preferredThemeFromStorage ?? CONST.THEME.DEFAULT; // If the user chooses to use the device theme settings, we need to set the theme preference to the system theme if (theme === CONST.THEME.SYSTEM) { - setThemePreference(systemTheme); + setThemePreference(systemTheme ?? CONST.THEME.DEFAULT); } else { setThemePreference(theme); } - }, [preferredThemeContext, systemTheme]); + }, [preferredThemeFromStorage, systemTheme]); return themePreference; } diff --git a/src/styles/useThemeStyles.ts b/src/styles/useThemeStyles.ts index a5b3baebbaec..69ba43692f49 100644 --- a/src/styles/useThemeStyles.ts +++ b/src/styles/useThemeStyles.ts @@ -5,7 +5,7 @@ function useThemeStyles() { const themeStyles = useContext(ThemeStylesContext); if (!themeStyles) { - throw new Error('StylesContext was null! Are you sure that you wrapped the component under a ?'); + throw new Error('ThemeStylesContext was null! Are you sure that you wrapped the component under a ?'); } // TODO: Remove this "eslint-disable-next" once the theme switching migration is done and styles are fully typed (GH Issue: https://github.com/Expensify/App/issues/27337)