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

feat(theme): OS-default color mode #10474

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,6 @@ describe('themeConfig', () => {
const colorMode: ThemeConfig['colorMode'] = {
defaultMode: 'dark',
disableSwitch: false,
respectPrefersColorScheme: true,
};
expect(testValidateThemeConfig({colorMode})).toEqual({
...DEFAULT_CONFIG,
Expand Down
3 changes: 1 addition & 2 deletions packages/docusaurus-theme-classic/src/inlineScripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ThemeQueryStringKey = 'docusaurus-theme';
const DataQueryStringPrefixKey = 'docusaurus-data-';

export function getThemeInlineScript({
colorMode: {defaultMode, respectPrefersColorScheme},
colorMode: {defaultMode},
siteStorage,
}: {
colorMode: ThemeConfig['colorMode'];
Expand All @@ -29,7 +29,6 @@ export function getThemeInlineScript({
/* language=js */
return `(function() {
var defaultMode = '${defaultMode}';
var respectPrefersColorScheme = ${respectPrefersColorScheme};

function setDataThemeAttribute(theme) {
document.documentElement.setAttribute('data-theme', theme);
Expand Down
10 changes: 5 additions & 5 deletions packages/docusaurus-theme-classic/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ const BlogSchema = Joi.object<ThemeConfig['blog']>({
const DEFAULT_COLOR_MODE_CONFIG: ThemeConfig['colorMode'] = {
defaultMode: 'light',
disableSwitch: false,
respectPrefersColorScheme: false,
};

export const DEFAULT_CONFIG: ThemeConfig = {
Expand Down Expand Up @@ -283,12 +282,13 @@ const NavbarItemSchema = Joi.object({

const ColorModeSchema = Joi.object({
defaultMode: Joi.string()
.equal('dark', 'light')
.equal('system', 'dark', 'light')
.default(DEFAULT_COLOR_MODE_CONFIG.defaultMode),
disableSwitch: Joi.bool().default(DEFAULT_COLOR_MODE_CONFIG.disableSwitch),
respectPrefersColorScheme: Joi.bool().default(
DEFAULT_COLOR_MODE_CONFIG.respectPrefersColorScheme,
),
respectPrefersColorScheme: Joi.any().forbidden().messages({
'any.unknown':
'colorMode.respectPrefersColorScheme is deprecated. Please use colorMode.defaultMode=system instead.',
}),
switchConfig: Joi.any().forbidden().messages({
'any.unknown':
'colorMode.switchConfig is deprecated. If you want to customize the icons for light and dark mode, swizzle IconLightMode, IconDarkMode, or ColorModeToggle instead.',
Expand Down
13 changes: 11 additions & 2 deletions packages/docusaurus-theme-classic/src/theme-classic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1404,22 +1404,31 @@ declare module '@theme/TOCCollapsible/CollapseButton' {
}

declare module '@theme/ColorModeToggle' {
import type {ColorMode} from '@docusaurus/theme-common';
import type {ColorMode, ColorModeChoice} from '@docusaurus/theme-common';

export interface Props {
readonly className?: string;
readonly buttonClassName?: string;
readonly value: ColorMode;
readonly choice: ColorModeChoice;
/**
* The parameter represents the "to-be" value. For example, if currently in
* dark mode, clicking the button should call `onChange("light")`
*/
readonly onChange: (colorMode: ColorMode) => void;
readonly onChange: (colorModeChoice: ColorModeChoice) => void;
}

export default function ColorModeToggle(props: Props): JSX.Element;
}

declare module '@theme/Icon/SystemMode' {
import type {ComponentProps} from 'react';

export interface Props extends ComponentProps<'svg'> {}

export default function IconSystemMode(props: Props): JSX.Element;
}

declare module '@theme/Logo' {
import type {ComponentProps} from 'react';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import IconLightMode from '@theme/Icon/LightMode';
import IconDarkMode from '@theme/Icon/DarkMode';
import type {Props} from '@theme/ColorModeToggle';

import IconSystemMode from '@theme/Icon/SystemMode';
import type {ColorModeChoice} from '@docusaurus/theme-common';
import styles from './styles.module.css';

function ColorModeToggle({
className,
buttonClassName,
value,
choice,
onChange,
}: Props): JSX.Element {
const isBrowser = useIsBrowser();
Expand All @@ -30,21 +33,49 @@ function ColorModeToggle({
description: 'The ARIA label for the navbar color mode toggle',
},
{
mode:
value === 'dark'
? translate({
message: 'dark mode',
id: 'theme.colorToggle.ariaLabel.mode.dark',
description: 'The name for the dark color mode',
})
: translate({
message: 'light mode',
id: 'theme.colorToggle.ariaLabel.mode.light',
description: 'The name for the light color mode',
}),
mode: {
dark: () =>
translate({
message: 'dark mode',
id: 'theme.colorToggle.ariaLabel.mode.dark',
description: 'The name for the dark color mode',
}),
light: () =>
translate({
message: 'light mode',
id: 'theme.colorToggle.ariaLabel.mode.light',
description: 'The name for the light color mode',
}),
system: () =>
translate({
message: 'system mode',
id: 'theme.colorToggle.ariaLabel.mode.system',
description: 'The name for the system color mode',
}),
}[choice ?? 'system'](),
},
);

// cycle through dark/light/system, as follows:
//
// (prefers-color-scheme: dark)
// ? [system, light, dark]
// : [system, dark, light]
const nextTheme = (): ColorModeChoice => {
// system -> opposite
if (choice === 'system') {
return value === 'dark' ? 'light' : 'dark';
}

// same as `prefers-color-scheme` -> system
if (window.matchMedia(`(prefers-color-scheme: ${choice})`).matches) {
return 'system';
}

// dark/light -> opposite
return choice === 'dark' ? 'light' : 'dark';
};

return (
<div className={clsx(styles.toggle, className)}>
<button
Expand All @@ -55,7 +86,7 @@ function ColorModeToggle({
buttonClassName,
)}
type="button"
onClick={() => onChange(value === 'dark' ? 'light' : 'dark')}
onClick={() => onChange(nextTheme())}
disabled={!isBrowser}
title={title}
aria-label={title}
Expand All @@ -66,6 +97,9 @@ function ColorModeToggle({
<IconDarkMode
className={clsx(styles.toggleIcon, styles.darkToggleIcon)}
/>
<IconSystemMode
className={clsx(styles.toggleIcon, styles.systemToggleIcon)}
/>
</button>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@
background: var(--ifm-color-emphasis-200);
}

[data-theme='light'] .darkToggleIcon,
[data-theme='dark'] .lightToggleIcon {
[data-theme-choice='system'] .lightToggleIcon,
[data-theme-choice='system'] .darkToggleIcon,
[data-theme-choice='dark'] .systemToggleIcon,
[data-theme-choice='dark'] .lightToggleIcon,
[data-theme-choice='light'] .systemToggleIcon,
[data-theme-choice='light'] .darkToggleIcon {
display: none;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import type {Props} from '@theme/Icon/SystemMode';

export default function IconSystemMode(props: Props): JSX.Element {
return (
<svg viewBox="0 0 24 24" width={24} height={24} {...props}>
<path
fill="currentColor"
d="m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"
/>
</svg>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function NavbarColorModeToggle({
}: Props): JSX.Element | null {
const navbarStyle = useThemeConfig().navbar.style;
const disabled = useThemeConfig().colorMode.disableSwitch;
const {colorMode, setColorMode} = useColorMode();
const {colorMode, colorModeChoice, setColorMode} = useColorMode();

if (disabled) {
return null;
Expand All @@ -29,6 +29,7 @@ export default function NavbarColorModeToggle({
navbarStyle === 'dark' ? styles.darkNavbarColorModeToggle : undefined
}
value={colorMode}
choice={colorModeChoice}
onChange={setColorMode}
/>
);
Expand Down
Loading
Loading