Yuki Takei 3 лет назад
Родитель
Сommit
6c2b7e4b07

+ 2 - 1
packages/app/src/components/Layout/RawLayout.tsx

@@ -1,10 +1,11 @@
 import React, { ReactNode, useState } from 'react';
 import React, { ReactNode, useState } from 'react';
 
 
+import { ColorScheme } from '@growi/core';
 import Head from 'next/head';
 import Head from 'next/head';
 import { ToastContainer } from 'react-toastify';
 import { ToastContainer } from 'react-toastify';
 import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
 
-import { ColorScheme, useNextThemes, NextThemesProvider } from '~/stores/use-next-themes';
+import { useNextThemes, NextThemesProvider } from '~/stores/use-next-themes';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 

+ 3 - 3
packages/app/src/components/Navbar/AppearanceModeDropdown.tsx

@@ -47,15 +47,15 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
 
 
   const followOsCheckboxModifiedHandler = useCallback((isChecked: boolean) => {
   const followOsCheckboxModifiedHandler = useCallback((isChecked: boolean) => {
     if (isChecked) {
     if (isChecked) {
-      setTheme(Themes.system);
+      setTheme(Themes.SYSTEM);
     }
     }
     else {
     else {
-      setTheme(resolvedTheme ?? Themes.light);
+      setTheme(resolvedTheme ?? Themes.LIGHT);
     }
     }
   }, [resolvedTheme, setTheme]);
   }, [resolvedTheme, setTheme]);
 
 
   const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
   const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
-    setTheme(isDarkMode ? Themes.dark : Themes.light);
+    setTheme(isDarkMode ? Themes.DARK : Themes.LIGHT);
   }, [setTheme]);
   }, [setTheme]);
 
 
   /* eslint-disable react/prop-types */
   /* eslint-disable react/prop-types */

+ 2 - 1
packages/app/src/pages/_app.page.tsx

@@ -11,7 +11,7 @@ import * as nextI18nConfig from '^/config/next-i18next.config';
 import { ActivatePluginService } from '~/client/services/activate-plugin';
 import { ActivatePluginService } from '~/client/services/activate-plugin';
 import { useI18nextHMR } from '~/services/i18next-hmr';
 import { useI18nextHMR } from '~/services/i18next-hmr';
 import {
 import {
-  useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useIsDefaultLogo,
+  useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useIsDefaultLogo, useForcedColorScheme,
 } from '~/stores/context';
 } from '~/stores/context';
 import { SWRConfigValue, swrGlobalConfiguration } from '~/utils/swr-utils';
 import { SWRConfigValue, swrGlobalConfiguration } from '~/utils/swr-utils';
 
 
@@ -66,6 +66,7 @@ function GrowiApp({ Component, pageProps }: GrowiAppProps): JSX.Element {
   useConfidential(commonPageProps.confidential);
   useConfidential(commonPageProps.confidential);
   useGrowiVersion(commonPageProps.growiVersion);
   useGrowiVersion(commonPageProps.growiVersion);
   useIsDefaultLogo(commonPageProps.isDefaultLogo);
   useIsDefaultLogo(commonPageProps.isDefaultLogo);
+  useForcedColorScheme(commonPageProps.forcedColorScheme);
 
 
   // Use the layout defined at the page level, if available
   // Use the layout defined at the page level, if available
   const getLayout = Component.getLayout ?? (page => page);
   const getLayout = Component.getLayout ?? (page => page);

+ 6 - 2
packages/app/src/pages/utils/commons.ts

@@ -1,12 +1,13 @@
+import type { ColorScheme, IUser, IUserHasId } from '@growi/core';
 import {
 import {
-  DevidedPagePath, Lang, AllLang, IUser, IUserHasId,
+  DevidedPagePath, Lang, AllLang,
 } from '@growi/core';
 } from '@growi/core';
 import { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { SSRConfig, UserConfig } from 'next-i18next';
 import { SSRConfig, UserConfig } from 'next-i18next';
 
 
 import * as nextI18NextConfig from '^/config/next-i18next.config';
 import * as nextI18NextConfig from '^/config/next-i18next.config';
 
 
-import { CrowiRequest } from '~/interfaces/crowi-request';
+import type { CrowiRequest } from '~/interfaces/crowi-request';
 
 
 export type CommonProps = {
 export type CommonProps = {
   namespacesRequired: string[], // i18next
   namespacesRequired: string[], // i18next
@@ -22,6 +23,7 @@ export type CommonProps = {
   redirectDestination: string | null,
   redirectDestination: string | null,
   isDefaultLogo: boolean,
   isDefaultLogo: boolean,
   currentUser?: IUser,
   currentUser?: IUser,
+  forcedColorScheme?: ColorScheme,
 } & Partial<SSRConfig>;
 } & Partial<SSRConfig>;
 
 
 // eslint-disable-next-line max-len
 // eslint-disable-next-line max-len
@@ -47,6 +49,7 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
   const redirectDestination = !isMaintenanceMode && currentPathname === '/maintenance' ? '/' : isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance') ? '/maintenance' : null;
   const redirectDestination = !isMaintenanceMode && currentPathname === '/maintenance' ? '/' : isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance') ? '/maintenance' : null;
   const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
   const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
   const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo') || !isCustomizedLogoUploaded;
   const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo') || !isCustomizedLogoUploaded;
+  const forcedColorScheme = crowi.configManager.getConfig('crowi', 'customize:theme:forcedColorScheme');
 
 
   const props: CommonProps = {
   const props: CommonProps = {
     namespacesRequired: ['translation'],
     namespacesRequired: ['translation'],
@@ -62,6 +65,7 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
     redirectDestination,
     redirectDestination,
     currentUser,
     currentUser,
     isDefaultLogo,
     isDefaultLogo,
+    forcedColorScheme,
   };
   };
 
 
   return { props };
   return { props };

+ 5 - 1
packages/app/src/stores/context.tsx

@@ -1,4 +1,4 @@
-import { IUser } from '@growi/core';
+import type { ColorScheme, IUser } from '@growi/core';
 import { Key, SWRResponse } from 'swr';
 import { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
@@ -209,6 +209,10 @@ export const useIsCustomizedLogoUploaded = (initialData?: boolean): SWRResponse<
   return useStaticSWR('isCustomizedLogoUploaded', initialData);
   return useStaticSWR('isCustomizedLogoUploaded', initialData);
 };
 };
 
 
+export const useForcedColorScheme = (initialData?: ColorScheme): SWRResponse<ColorScheme, Error> => {
+  return useContextSWR('forcedColorScheme', initialData);
+};
+
 export const useGrowiCloudUri = (initialData?: string): SWRResponse<string, Error> => {
 export const useGrowiCloudUri = (initialData?: string): SWRResponse<string, Error> => {
   return useStaticSWR('growiCloudUri', initialData);
   return useStaticSWR('growiCloudUri', initialData);
 };
 };

+ 14 - 19
packages/app/src/stores/use-next-themes.tsx

@@ -1,35 +1,30 @@
-import { isClient } from '@growi/core';
+import { isClient, ColorScheme } from '@growi/core';
 import { ThemeProvider, useTheme } from 'next-themes';
 import { ThemeProvider, useTheme } from 'next-themes';
 import { ThemeProviderProps, UseThemeProps } from 'next-themes/dist/types';
 import { ThemeProviderProps, UseThemeProps } from 'next-themes/dist/types';
 
 
+import { useForcedColorScheme } from './context';
+
 export const Themes = {
 export const Themes = {
-  light: 'light',
-  dark: 'dark',
-  system: 'system',
+  ...ColorScheme,
+  SYSTEM: 'system',
 } as const;
 } as const;
 export type Themes = typeof Themes[keyof typeof Themes];
 export type Themes = typeof Themes[keyof typeof Themes];
 
 
-export const ResolvedThemes = {
-  light: Themes.light,
-  dark: Themes.dark,
-} as const;
-export type ResolvedThemes = typeof ResolvedThemes[keyof typeof ResolvedThemes];
-export const ColorScheme = ResolvedThemes;
-export type ColorScheme = ResolvedThemes;
-
 
 
 const ATTRIBUTE = 'data-theme';
 const ATTRIBUTE = 'data-theme';
 
 
 export const NextThemesProvider: React.FC<ThemeProviderProps> = (props) => {
 export const NextThemesProvider: React.FC<ThemeProviderProps> = (props) => {
-  return <ThemeProvider {...props} attribute={ATTRIBUTE} />;
+  const { data: forcedColorScheme } = useForcedColorScheme();
+
+  return <ThemeProvider {...props} forcedTheme={forcedColorScheme} attribute={ATTRIBUTE} />;
 };
 };
 
 
 type UseThemeExtendedProps = Omit<UseThemeProps, 'theme'|'resolvedTheme'> & {
 type UseThemeExtendedProps = Omit<UseThemeProps, 'theme'|'resolvedTheme'> & {
   theme: Themes,
   theme: Themes,
-  resolvedTheme: ResolvedThemes,
+  resolvedTheme: ColorScheme,
   useOsSettings: boolean,
   useOsSettings: boolean,
   isDarkMode: boolean,
   isDarkMode: boolean,
-  resolvedThemeByAttributes?: ResolvedThemes,
+  resolvedThemeByAttributes?: ColorScheme,
 }
 }
 
 
 export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
 export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
@@ -37,9 +32,9 @@ export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
 
 
   return Object.assign(props, {
   return Object.assign(props, {
     theme: props.theme as Themes,
     theme: props.theme as Themes,
-    resolvedTheme: props.resolvedTheme as ResolvedThemes,
-    useOsSettings: props.theme === Themes.system,
-    isDarkMode: props.resolvedTheme === ResolvedThemes.dark,
-    resolvedThemeByAttributes: isClient() ? document.documentElement.getAttribute(ATTRIBUTE) as ResolvedThemes : undefined,
+    resolvedTheme: props.resolvedTheme as ColorScheme,
+    useOsSettings: props.theme === Themes.SYSTEM,
+    isDarkMode: props.resolvedTheme === ColorScheme.DARK,
+    resolvedThemeByAttributes: isClient() ? document.documentElement.getAttribute(ATTRIBUTE) as ColorScheme : undefined,
   });
   });
 };
 };