diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index be44d354e..f56f8b03c 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -29,9 +29,10 @@ import './scss/normalized.scss'; const App = () => { const { context, Provider } = usePageContext(); - const { experienceSettings, setLoading, setExperienceSettings } = context; + const { isPreview, experienceSettings, setLoading, setExperienceSettings } = context; const customCssRef = useRef(document.createElement('style')); - const [isPreview] = usePreview(context); + + usePreview(context); useEffect(() => { document.head.append(customCssRef.current); diff --git a/packages/ui/src/hooks/use-page-context.ts b/packages/ui/src/hooks/use-page-context.ts index ce40853d4..a0c9cb949 100644 --- a/packages/ui/src/hooks/use-page-context.ts +++ b/packages/ui/src/hooks/use-page-context.ts @@ -3,6 +3,7 @@ import { useState, useMemo, createContext } from 'react'; import { isMobile } from 'react-device-detect'; import type { SignInExperienceResponse, Platform, Theme } from '@/types'; +import { parseQueryParameters } from '@/utils'; export type Context = { theme: Theme; @@ -11,6 +12,7 @@ export type Context = { platform: Platform; termsAgreement: boolean; experienceSettings: SignInExperienceResponse | undefined; + isPreview: boolean; setTheme: React.Dispatch>; setToast: React.Dispatch>; setLoading: React.Dispatch>; @@ -26,6 +28,7 @@ export const PageContext = createContext({ platform: isMobile ? 'mobile' : 'web', termsAgreement: false, experienceSettings: undefined, + isPreview: false, setTheme: noop, setToast: noop, setLoading: noop, @@ -42,6 +45,9 @@ const usePageContext = () => { const [experienceSettings, setExperienceSettings] = useState(); const [termsAgreement, setTermsAgreement] = useState(false); + const { preview } = parseQueryParameters(window.location.search); + const isPreview = preview === 'true'; + const context = useMemo( () => ({ theme, @@ -50,6 +56,7 @@ const usePageContext = () => { platform, termsAgreement, experienceSettings, + isPreview, setTheme, setLoading, setToast, @@ -57,7 +64,7 @@ const usePageContext = () => { setTermsAgreement, setExperienceSettings, }), - [experienceSettings, loading, platform, termsAgreement, theme, toast] + [experienceSettings, isPreview, loading, platform, termsAgreement, theme, toast] ); return { diff --git a/packages/ui/src/hooks/use-preview.ts b/packages/ui/src/hooks/use-preview.ts index 7b9b56024..36eaf4175 100644 --- a/packages/ui/src/hooks/use-preview.ts +++ b/packages/ui/src/hooks/use-preview.ts @@ -3,28 +3,15 @@ import { conditionalString } from '@silverhand/essentials'; import { useEffect, useState } from 'react'; import * as styles from '@/Layout/AppLayout/index.module.scss'; -import * as appStyles from '@/Providers/AppBoundary/index.module.scss'; import type { Context } from '@/hooks/use-page-context'; import initI18n from '@/i18n/init'; import { changeLanguage } from '@/i18n/utils'; -import type { SignInExperienceResponse, PreviewConfig, Theme } from '@/types'; -import { parseQueryParameters } from '@/utils'; +import type { SignInExperienceResponse, PreviewConfig } from '@/types'; import { filterPreviewSocialConnectors } from '@/utils/social-connectors'; -const applyTheme = (theme: Theme) => { - document.body.classList.remove( - conditionalString(appStyles.light), - conditionalString(appStyles.dark) - ); - document.body.classList.add(conditionalString(appStyles[theme])); -}; - const usePreview = (context: Context): [boolean, PreviewConfig?] => { const [previewConfig, setPreviewConfig] = useState(); - const { setExperienceSettings, setPlatform } = context; - - const { preview } = parseQueryParameters(window.location.search); - const isPreview = preview === 'true'; + const { isPreview, setExperienceSettings, setPlatform, setTheme } = context; useEffect(() => { if (!isPreview) { @@ -80,13 +67,13 @@ const usePreview = (context: Context): [boolean, PreviewConfig?] => { }; (async () => { - applyTheme(mode); + setTheme(mode); setPlatform(platform); setExperienceSettings(experienceSettings); })(); - }, [isPreview, previewConfig, setExperienceSettings, setPlatform]); + }, [isPreview, previewConfig, setExperienceSettings, setPlatform, setTheme]); useEffect(() => { if (!isPreview || !previewConfig?.language) { diff --git a/packages/ui/src/hooks/use-theme.ts b/packages/ui/src/hooks/use-theme.ts index e85eb79b8..ceed2c0e5 100644 --- a/packages/ui/src/hooks/use-theme.ts +++ b/packages/ui/src/hooks/use-theme.ts @@ -8,9 +8,18 @@ const darkThemeWatchMedia = window.matchMedia('(prefers-color-scheme: dark)'); const getThemeBySystemConfiguration = (): Theme => (darkThemeWatchMedia.matches ? 'dark' : 'light'); export default function useTheme(): Theme { - const { experienceSettings, theme, setTheme } = useContext(PageContext); + const { isPreview, experienceSettings, theme, setTheme } = useContext(PageContext); useEffect(() => { + /** + * Note: + * In preview mode, the theme of the page is controlled by the preview options and does not follow system changes. + * The `usePreview` hook changes the theme of the page by calling the `setTheme` API of the `PageContext`. + */ + if (isPreview) { + return; + } + if (!experienceSettings?.color.isDarkModeEnabled) { return; } @@ -26,7 +35,7 @@ export default function useTheme(): Theme { return () => { darkThemeWatchMedia.removeEventListener('change', changeTheme); }; - }, [experienceSettings, setTheme]); + }, [experienceSettings, isPreview, setTheme]); return theme; }