Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theme switching: Add TypeScript types #24178

Merged
merged 43 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2da64c9
add ts type for theme
Aug 5, 2023
899d38e
adapt light and dark theme to ts type
Aug 5, 2023
e089833
use light theme in ThemeProvider
Aug 5, 2023
b32f538
adapt colors to new ts type
Aug 5, 2023
21787ba
change usage of "default.js" to "dark.js"
Aug 5, 2023
0e02b13
fix: rename back dark.ts
Sep 16, 2023
1d2de3f
Merge branch 'main' into @chrispader/theme-switching-ts-types
Sep 16, 2023
2ba934c
add TODO comment about renaming default.ts
Sep 16, 2023
4b10b95
fix: update outdated comment
Sep 16, 2023
84c4731
fix: add TODO comment
Sep 16, 2023
429e2b6
fix: add types for the rest of theme switching logic
Sep 16, 2023
b7b1179
fix: type remaining file with TODO
Sep 16, 2023
b77e4d0
Merge branch 'main' into @chrispader/theme-switching-ts-types
Sep 19, 2023
f69116a
fix: remove theme spreading and abstraction
Sep 19, 2023
08f5b4f
fix: changed colors
Sep 19, 2023
fd4e5aa
fix: remove more diffs to main
Sep 19, 2023
c794fd1
fix: typo
Sep 19, 2023
10f4778
update todo comments
Sep 19, 2023
dba25a2
fix: naming
Sep 19, 2023
283467a
Merge branch 'ts/style/styles-refactor' into @chrispader/theme-switch…
Sep 19, 2023
0bbcd56
fix: update styles.ts
Sep 19, 2023
2b6f412
feat: update types int ThemeStylesProvider logic
Sep 19, 2023
b2d6054
Merge branch 'main' into @chrispader/theme-switching-ts-types
Sep 20, 2023
e4d4a10
fix: merge main
Sep 20, 2023
3f28300
enforce consistent themes
Sep 20, 2023
fb20af7
fix: move ThemeColors to types.ts
Sep 20, 2023
33d0bc8
fix: make colors non optional
Sep 20, 2023
0b7a872
fix: add remaining color in light theme
Sep 20, 2023
6a41699
Merge branch 'main' into @chrispader/theme-switching-ts-types
Sep 27, 2023
8fb99cc
Merge branch 'main' into @chrispader/theme-switching-ts-types
Oct 16, 2023
6f0eb4c
Merge branch 'main' into @chrispader/theme-switching-ts-types
Oct 19, 2023
58a59de
remove TODO
Oct 19, 2023
2caad03
replace comment
Oct 19, 2023
ef2771c
remove TODO
Oct 19, 2023
23bcb92
remove TODO block
Oct 19, 2023
5a3cbad
Merge branch 'main' into @chrispader/theme-switching-ts-types
Oct 19, 2023
e70f1a9
Merge branch 'main' into @chrispader/theme-switching-ts-types
Oct 20, 2023
0b54f57
Merge branch 'main' into @chrispader/theme-switching-ts-types
Oct 22, 2023
bbdb468
Merge branch 'main' into @chrispader/theme-switching-ts-types
chrispader Nov 2, 2023
bb89e03
Merge branch 'main' into @chrispader/theme-switching-ts-types
chrispader Nov 2, 2023
0c74c17
fix: imports and remove TODO blocks
chrispader Nov 2, 2023
0eb2fe6
Merge branch 'main' into @chrispader/theme-switching-ts-types
chrispader Nov 5, 2023
b46f0b2
fix: prettier
chrispader Nov 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/styles/ThemeStylesContext.ts
Original file line number Diff line number Diff line change
@@ -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>(styles);

export default ThemeStylesContext;
7 changes: 2 additions & 5 deletions src/styles/ThemeStylesProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
/* 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;
};

function ThemeStylesProvider({children}: ThemeStylesProviderProps) {
const theme = useTheme();

const themeStyles = useMemo(() => styles(theme), [theme]);
const themeStyles = useMemo(() => stylesGenerator(theme), [theme]);

return <ThemeStylesContext.Provider value={themeStyles}>{children}</ThemeStylesContext.Provider>;
}
Expand Down
11 changes: 9 additions & 2 deletions src/styles/colors.js → src/styles/colors.ts
Original file line number Diff line number Diff line change
@@ -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<string, Color> = {
chrispader marked this conversation as resolved.
Show resolved Hide resolved
// Brand Colors
black: '#000000',
grgia marked this conversation as resolved.
Show resolved Hide resolved
white: '#FFFFFF',
ivory: '#fffaf0',
Expand Down Expand Up @@ -91,3 +96,5 @@ export default {
ice700: '#28736D',
ice800: '#134038',
};

export default colors;
18 changes: 7 additions & 11 deletions src/styles/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -80,7 +80,7 @@ const touchCalloutNone: Pick<ViewStyle, 'WebkitTouchCallout'> = Browser.isMobile
// to prevent vertical text offset in Safari for badges, new lineHeight values have been added
const lineHeightBadge: Pick<ViewStyle, 'lineHeight'> = Browser.isSafari() ? {lineHeight: variables.lineHeightXSmall} : {lineHeight: variables.lineHeightNormal};

const picker = (theme: ThemeDefault) =>
const picker = (theme: ThemeColors) =>
({
backgroundColor: theme.transparent,
color: theme.text,
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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};
6 changes: 0 additions & 6 deletions src/styles/themes/ThemeContext.js

This file was deleted.

7 changes: 7 additions & 0 deletions src/styles/themes/ThemeContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';
import darkTheme from './default';
import {ThemeColors} from './types';

const ThemeContext = React.createContext<ThemeColors>(darkTheme);

export default ThemeContext;
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 <ThemeContext.Provider value={theme}>{props.children}</ThemeContext.Provider>;
}
Expand Down
25 changes: 12 additions & 13 deletions src/styles/themes/default.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
27 changes: 13 additions & 14 deletions src/styles/themes/light.ts
Original file line number Diff line number Diff line change
@@ -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 = {
chrispader marked this conversation as resolved.
Show resolved Hide resolved
// Figma keys
Expand All @@ -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,
Expand Down Expand Up @@ -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;
91 changes: 86 additions & 5 deletions src/styles/themes/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,89 @@
import DeepRecord from '@src/types/utils/DeepRecord';
import defaultTheme from './default';
type Color = string;

type ThemeBase = DeepRecord<string, string>;
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<string, Color>;
};

export {type ThemeColors, type Color};
Original file line number Diff line number Diff line change
@@ -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 <ThemeProvider>?');
throw new Error('ThemeContext was null! Are you sure that you wrapped the component under a <ThemeProvider>?');
}

return theme;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ThemePreference>(CONST.THEME.DEFAULT);
const [systemTheme, setSystemTheme] = useState<ColorSchemeName>();
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;
}
Expand Down
Loading
Loading