mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 01:25:16 +00:00
Start using next-themes (#115)
* Start using next-themes * Fix with nonce * Lint
This commit is contained in:
+2
-1
@@ -31,7 +31,8 @@
|
||||
"jsontoxml": "^1.0.1",
|
||||
"katex": "^0.16.9",
|
||||
"memoizee": "^0.4.15",
|
||||
"next": "^14.0.4",
|
||||
"next": "^14.1.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"p-map": "^7.0.0",
|
||||
"parse-cache-control": "^1.0.1",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { CustomizationThemeMode } from '@gitbook/api';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import React from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
@@ -7,14 +9,18 @@ import { TranslateContext } from '@/intl/client';
|
||||
import { TranslationLanguage } from '@/intl/translations';
|
||||
|
||||
export function ClientContexts(props: {
|
||||
nonce: string;
|
||||
language: TranslationLanguage;
|
||||
forcedTheme: CustomizationThemeMode | undefined;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { children, language } = props;
|
||||
const { nonce, children, forcedTheme, language } = props;
|
||||
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<TranslateContext.Provider value={language}>{children}</TranslateContext.Provider>
|
||||
</RecoilRoot>
|
||||
<ThemeProvider nonce={nonce} attribute="class" enableSystem forcedTheme={forcedTheme}>
|
||||
<RecoilRoot>
|
||||
<TranslateContext.Provider value={language}>{children}</TranslateContext.Provider>
|
||||
</RecoilRoot>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { fonts, ibmPlexMono } from '@/fonts';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { getSpaceContent } from '@/lib/api';
|
||||
import { hexToRgb, shadesOfColor } from '@/lib/colors';
|
||||
import { getContentSecurityPolicyNonce } from '@/lib/csp';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { ClientContexts } from './ClientContexts';
|
||||
@@ -27,9 +28,11 @@ export default async function SpaceRootLayout(props: {
|
||||
});
|
||||
const headerTheme = generateHeaderTheme(customization);
|
||||
const language = getSpaceLanguage(customization);
|
||||
const nonce = getContentSecurityPolicyNonce();
|
||||
|
||||
return (
|
||||
<html
|
||||
suppressHydrationWarning
|
||||
lang={customization.internationalization.locale}
|
||||
className={tcls(
|
||||
customization.header.preset === CustomizationHeaderPreset.None
|
||||
@@ -85,7 +88,15 @@ export default async function SpaceRootLayout(props: {
|
||||
'dark:bg-dark',
|
||||
)}
|
||||
>
|
||||
<ClientContexts language={language}>{children}</ClientContexts>
|
||||
<ClientContexts
|
||||
nonce={nonce}
|
||||
language={language}
|
||||
forcedTheme={
|
||||
customization.themes.toggeable ? undefined : customization.themes.default
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</ClientContexts>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Monitor from '@geist-ui/icons/monitor';
|
||||
import Moon from '@geist-ui/icons/moon';
|
||||
import Sun from '@geist-ui/icons/sun';
|
||||
import { useTheme } from 'next-themes';
|
||||
import React from 'react';
|
||||
|
||||
import { IconComponent } from '@/components/icons';
|
||||
@@ -11,42 +12,22 @@ import { tcls } from '@/lib/tailwind';
|
||||
|
||||
type ThemeMode = 'light' | 'system' | 'dark';
|
||||
|
||||
const LOCALSTORAGE_KEY = 'color-theme';
|
||||
|
||||
/**
|
||||
* Buttons to toggle between light/system/dark modes.
|
||||
*/
|
||||
export function ThemeToggler(props: {}) {
|
||||
const [mode, setMode] = React.useState<ThemeMode>('system');
|
||||
|
||||
const language = useLanguage();
|
||||
|
||||
const onSwitchMode = (to: ThemeMode) => {
|
||||
setMode(to);
|
||||
window.localStorage.setItem(LOCALSTORAGE_KEY, to);
|
||||
};
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
const value = window.localStorage.getItem(LOCALSTORAGE_KEY);
|
||||
if (value && (value === 'light' || value === 'system' || value === 'dark')) {
|
||||
setMode(value);
|
||||
}
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const applied =
|
||||
mode === 'system'
|
||||
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light'
|
||||
: mode;
|
||||
|
||||
if (applied === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
}, [mode]);
|
||||
const onSwitchMode = (to: ThemeMode) => {
|
||||
setTheme(to);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -61,19 +42,19 @@ export function ThemeToggler(props: {}) {
|
||||
)}
|
||||
>
|
||||
<ThemeButton
|
||||
active={mode === 'light'}
|
||||
active={mounted && theme === 'light'}
|
||||
icon={Sun}
|
||||
onClick={() => onSwitchMode('light')}
|
||||
title={tString(language, 'switch_to_light_theme')}
|
||||
/>
|
||||
<ThemeButton
|
||||
active={mode === 'system'}
|
||||
active={mounted && theme === 'system'}
|
||||
icon={Monitor}
|
||||
onClick={() => onSwitchMode('system')}
|
||||
title={tString(language, 'switch_to_system_theme')}
|
||||
/>
|
||||
<ThemeButton
|
||||
active={mode === 'dark'}
|
||||
active={mounted && theme === 'dark'}
|
||||
icon={Moon}
|
||||
onClick={() => onSwitchMode('dark')}
|
||||
title={tString(language, 'switch_to_dark_theme')}
|
||||
|
||||
Reference in New Issue
Block a user