fix: sync color scheme on loading state (#13878)

Before:


https://github.com/user-attachments/assets/a769a2f8-554a-42f7-8adb-e8b4be92a4d1


Aftter:


https://github.com/user-attachments/assets/9200967a-902f-4f8d-822d-827dda32bac2

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
neo773
2025-08-13 02:17:58 +05:30
committed by GitHub
parent 3ffb35d7a8
commit af835e23be
5 changed files with 43 additions and 30 deletions
+20 -4
View File
@@ -60,14 +60,30 @@
disableInputAutoZoom();
}
</script>
<style>
:root {
/* THEME_DARK.background.tertiary */
--theme-dark-background-tertiary: #1d1d1d;
/* THEME_LIGHT.background.tertiary */
--theme-light-background-tertiary: #f1f1f1;
}
@media (prefers-color-scheme: dark) {
html, body {
background-color: var(--theme-dark-background-tertiary);
}
}
@media (prefers-color-scheme: light) {
html, body {
background-color: var(--theme-light-background-tertiary);
}
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script
type="module"
src="/src/renderBodyOptimisticBackgroundBeforeAppBootstrap.ts"
></script>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
@@ -1,7 +1,8 @@
import { ThemeProvider } from '@emotion/react';
import { createContext, useState } from 'react';
import { createContext } from 'react';
import { useSystemColorScheme } from '../hooks/useSystemColorScheme';
import { persistedColorSchemeState } from '@/ui/theme/states/persistedColorSchemeState';
import { useRecoilState } from 'recoil';
import { type ColorScheme } from 'twenty-ui/input';
import { THEME_DARK, THEME_LIGHT, ThemeContextProvider } from 'twenty-ui/theme';
@@ -14,16 +15,16 @@ export const ThemeSchemeContext = createContext<(theme: ColorScheme) => void>(
);
export const BaseThemeProvider = ({ children }: BaseThemeProviderProps) => {
const systemColorScheme = useSystemColorScheme();
const [themeScheme, setThemeScheme] = useState(systemColorScheme);
const [persistedColorScheme, setPersistedColorScheme] = useRecoilState(
persistedColorSchemeState,
);
document.documentElement.className =
themeScheme === 'Dark' ? 'dark' : 'light';
persistedColorScheme === 'Dark' ? 'dark' : 'light';
const theme = themeScheme === 'Dark' ? THEME_DARK : THEME_LIGHT;
const theme = persistedColorScheme === 'Dark' ? THEME_DARK : THEME_LIGHT;
return (
<ThemeSchemeContext.Provider value={setThemeScheme}>
<ThemeSchemeContext.Provider value={setPersistedColorScheme}>
<ThemeProvider theme={theme}>
<ThemeContextProvider theme={theme}>{children}</ThemeContextProvider>
</ThemeProvider>
@@ -4,6 +4,7 @@ import { useRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { persistedColorSchemeState } from '@/ui/theme/states/persistedColorSchemeState';
import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import {
type IconComponent,
@@ -20,6 +21,7 @@ export const useColorScheme = () => {
const { updateOneRecord: updateOneWorkspaceMember } = useUpdateOneRecord({
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
});
const [, setPersistedColorScheme] = useRecoilState(persistedColorSchemeState);
const colorScheme = currentWorkspaceMember?.colorScheme ?? 'System';
@@ -28,6 +30,7 @@ export const useColorScheme = () => {
if (!currentWorkspaceMember) {
return;
}
setPersistedColorScheme(value);
setCurrentWorkspaceMember((current) => {
if (!current) {
return current;
@@ -47,6 +50,7 @@ export const useColorScheme = () => {
[
currentWorkspaceMember,
setCurrentWorkspaceMember,
setPersistedColorScheme,
updateOneWorkspaceMember,
],
);
@@ -0,0 +1,10 @@
import { atom } from 'recoil';
import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { localStorageEffect } from '~/utils/recoil-effects';
export const persistedColorSchemeState = atom<ColorScheme>({
key: 'persistedColorSchemeState',
default: 'System',
effects: [localStorageEffect()],
});
@@ -1,18 +0,0 @@
// TODO consume theme from twenty-ui after its migration as a package, at the moment the bunlde is too big
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
const THEME_LIGHT_BACKGROUND_TERTIARY = '#f1f1f1';
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
const THEME_DARK_BACKGROUND_TERTIARY = '#1d1d1d';
// TODO should search in local storage for user last session appearance preferences
const renderBodyOptimisticBackgroundBeforeAppBootstrap = () => {
const isDarkTheme =
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDarkTheme) {
document.body.style.background = THEME_DARK_BACKGROUND_TERTIARY;
} else {
document.body.style.background = THEME_LIGHT_BACKGROUND_TERTIARY;
}
};
renderBodyOptimisticBackgroundBeforeAppBootstrap();