@docusaurus/theme-classic 3.7.0-canary-6252 → 3.7.0-canary-6254
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/inlineScripts.js +6 -27
- package/lib/theme/ColorModeToggle/index.d.ts +1 -1
- package/lib/theme/ColorModeToggle/index.js +85 -27
- package/lib/theme/ColorModeToggle/styles.module.css +7 -2
- package/lib/theme/Icon/SystemColorMode/index.d.ts +9 -0
- package/lib/theme/Icon/SystemColorMode/index.js +16 -0
- package/lib/theme/Navbar/ColorModeToggle/index.js +5 -4
- package/package.json +15 -15
- package/src/inlineScripts.ts +6 -27
- package/src/theme/ColorModeToggle/index.tsx +102 -32
- package/src/theme/ColorModeToggle/styles.module.css +7 -2
- package/src/theme/Icon/SystemColorMode/index.tsx +20 -0
- package/src/theme/Navbar/ColorModeToggle/index.tsx +5 -4
- package/src/theme-classic.d.ts +12 -3
package/lib/inlineScripts.js
CHANGED
|
@@ -22,43 +22,22 @@ function getThemeInlineScript({ colorMode: { defaultMode, respectPrefersColorSch
|
|
|
22
22
|
return `(function() {
|
|
23
23
|
var defaultMode = '${defaultMode}';
|
|
24
24
|
var respectPrefersColorScheme = ${respectPrefersColorScheme};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
document.documentElement.setAttribute('data-theme', theme);
|
|
25
|
+
function getSystemColorMode() {
|
|
26
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
28
27
|
}
|
|
29
|
-
|
|
30
28
|
function getQueryStringTheme() {
|
|
31
29
|
try {
|
|
32
30
|
return new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
|
|
33
|
-
} catch (e) {
|
|
34
|
-
}
|
|
31
|
+
} catch (e) {}
|
|
35
32
|
}
|
|
36
|
-
|
|
37
33
|
function getStoredTheme() {
|
|
38
34
|
try {
|
|
39
35
|
return window['${siteStorage.type}'].getItem('${themeStorageKey}');
|
|
40
|
-
} catch (err) {
|
|
41
|
-
}
|
|
36
|
+
} catch (err) {}
|
|
42
37
|
}
|
|
43
|
-
|
|
44
38
|
var initialTheme = getQueryStringTheme() || getStoredTheme();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} else {
|
|
48
|
-
if (
|
|
49
|
-
respectPrefersColorScheme &&
|
|
50
|
-
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
51
|
-
) {
|
|
52
|
-
setDataThemeAttribute('dark');
|
|
53
|
-
} else if (
|
|
54
|
-
respectPrefersColorScheme &&
|
|
55
|
-
window.matchMedia('(prefers-color-scheme: light)').matches
|
|
56
|
-
) {
|
|
57
|
-
setDataThemeAttribute('light');
|
|
58
|
-
} else {
|
|
59
|
-
setDataThemeAttribute(defaultMode === 'dark' ? 'dark' : 'light');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
39
|
+
document.documentElement.setAttribute('data-theme', initialTheme || (respectPrefersColorScheme ? getSystemColorMode() : defaultMode));
|
|
40
|
+
document.documentElement.setAttribute('data-theme-choice', initialTheme || (respectPrefersColorScheme ? 'system' : defaultMode));
|
|
62
41
|
})();`;
|
|
63
42
|
}
|
|
64
43
|
/* language=js */
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import React, { type ReactNode } from 'react';
|
|
8
8
|
import type { Props } from '@theme/ColorModeToggle';
|
|
9
|
-
declare function ColorModeToggle({ className, buttonClassName, value, onChange, }: Props): ReactNode;
|
|
9
|
+
declare function ColorModeToggle({ className, buttonClassName, respectPrefersColorScheme, value, onChange, }: Props): ReactNode;
|
|
10
10
|
declare const _default: React.MemoExoticComponent<typeof ColorModeToggle>;
|
|
11
11
|
export default _default;
|
|
@@ -10,30 +10,93 @@ import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
|
10
10
|
import {translate} from '@docusaurus/Translate';
|
|
11
11
|
import IconLightMode from '@theme/Icon/LightMode';
|
|
12
12
|
import IconDarkMode from '@theme/Icon/DarkMode';
|
|
13
|
+
import IconSystemColorMode from '@theme/Icon/SystemColorMode';
|
|
13
14
|
import styles from './styles.module.css';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// The order of color modes is defined here, and can be customized with swizzle
|
|
16
|
+
function getNextColorMode(colorMode, respectPrefersColorScheme) {
|
|
17
|
+
// 2-value transition
|
|
18
|
+
if (!respectPrefersColorScheme) {
|
|
19
|
+
return colorMode === 'dark' ? 'light' : 'dark';
|
|
20
|
+
}
|
|
21
|
+
// 3-value transition
|
|
22
|
+
switch (colorMode) {
|
|
23
|
+
case null:
|
|
24
|
+
return 'light';
|
|
25
|
+
case 'light':
|
|
26
|
+
return 'dark';
|
|
27
|
+
case 'dark':
|
|
28
|
+
return null;
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`unexpected color mode ${colorMode}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getColorModeLabel(colorMode) {
|
|
34
|
+
switch (colorMode) {
|
|
35
|
+
case null:
|
|
36
|
+
return translate({
|
|
37
|
+
message: 'system mode',
|
|
38
|
+
id: 'theme.colorToggle.ariaLabel.mode.system',
|
|
39
|
+
description: 'The name for the system color mode',
|
|
40
|
+
});
|
|
41
|
+
case 'light':
|
|
42
|
+
return translate({
|
|
43
|
+
message: 'light mode',
|
|
44
|
+
id: 'theme.colorToggle.ariaLabel.mode.light',
|
|
45
|
+
description: 'The name for the light color mode',
|
|
46
|
+
});
|
|
47
|
+
case 'dark':
|
|
48
|
+
return translate({
|
|
49
|
+
message: 'dark mode',
|
|
50
|
+
id: 'theme.colorToggle.ariaLabel.mode.dark',
|
|
51
|
+
description: 'The name for the dark color mode',
|
|
52
|
+
});
|
|
53
|
+
default:
|
|
54
|
+
throw new Error(`unexpected color mode ${colorMode}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function getColorModeAriaLabel(colorMode) {
|
|
58
|
+
return translate(
|
|
17
59
|
{
|
|
18
60
|
message: 'Switch between dark and light mode (currently {mode})',
|
|
19
61
|
id: 'theme.colorToggle.ariaLabel',
|
|
20
|
-
description: 'The ARIA label for the
|
|
62
|
+
description: 'The ARIA label for the color mode toggle',
|
|
21
63
|
},
|
|
22
64
|
{
|
|
23
|
-
mode:
|
|
24
|
-
value === 'dark'
|
|
25
|
-
? translate({
|
|
26
|
-
message: 'dark mode',
|
|
27
|
-
id: 'theme.colorToggle.ariaLabel.mode.dark',
|
|
28
|
-
description: 'The name for the dark color mode',
|
|
29
|
-
})
|
|
30
|
-
: translate({
|
|
31
|
-
message: 'light mode',
|
|
32
|
-
id: 'theme.colorToggle.ariaLabel.mode.light',
|
|
33
|
-
description: 'The name for the light color mode',
|
|
34
|
-
}),
|
|
65
|
+
mode: getColorModeLabel(colorMode),
|
|
35
66
|
},
|
|
36
67
|
);
|
|
68
|
+
}
|
|
69
|
+
function CurrentColorModeIcon() {
|
|
70
|
+
// 3 icons are always rendered for technical reasons
|
|
71
|
+
// We use "data-theme-choice" to render the correct one
|
|
72
|
+
// This must work even before React hydrates
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<IconLightMode
|
|
76
|
+
// a18y is handled at the button level,
|
|
77
|
+
// not relying on button content (svg icons)
|
|
78
|
+
aria-hidden
|
|
79
|
+
className={clsx(styles.toggleIcon, styles.lightToggleIcon)}
|
|
80
|
+
/>
|
|
81
|
+
<IconDarkMode
|
|
82
|
+
aria-hidden
|
|
83
|
+
className={clsx(styles.toggleIcon, styles.darkToggleIcon)}
|
|
84
|
+
/>
|
|
85
|
+
<IconSystemColorMode
|
|
86
|
+
aria-hidden
|
|
87
|
+
className={clsx(styles.toggleIcon, styles.systemToggleIcon)}
|
|
88
|
+
/>
|
|
89
|
+
</>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
function ColorModeToggle({
|
|
93
|
+
className,
|
|
94
|
+
buttonClassName,
|
|
95
|
+
respectPrefersColorScheme,
|
|
96
|
+
value,
|
|
97
|
+
onChange,
|
|
98
|
+
}) {
|
|
99
|
+
const isBrowser = useIsBrowser();
|
|
37
100
|
return (
|
|
38
101
|
<div className={clsx(styles.toggle, className)}>
|
|
39
102
|
<button
|
|
@@ -44,18 +107,13 @@ function ColorModeToggle({className, buttonClassName, value, onChange}) {
|
|
|
44
107
|
buttonClassName,
|
|
45
108
|
)}
|
|
46
109
|
type="button"
|
|
47
|
-
onClick={() =>
|
|
110
|
+
onClick={() =>
|
|
111
|
+
onChange(getNextColorMode(value, respectPrefersColorScheme))
|
|
112
|
+
}
|
|
48
113
|
disabled={!isBrowser}
|
|
49
|
-
title={
|
|
50
|
-
aria-label={
|
|
51
|
-
|
|
52
|
-
aria-pressed={value === 'dark' ? 'true' : 'false'}>
|
|
53
|
-
<IconLightMode
|
|
54
|
-
className={clsx(styles.toggleIcon, styles.lightToggleIcon)}
|
|
55
|
-
/>
|
|
56
|
-
<IconDarkMode
|
|
57
|
-
className={clsx(styles.toggleIcon, styles.darkToggleIcon)}
|
|
58
|
-
/>
|
|
114
|
+
title={getColorModeLabel(value)}
|
|
115
|
+
aria-label={getColorModeAriaLabel(value)}>
|
|
116
|
+
<CurrentColorModeIcon />
|
|
59
117
|
</button>
|
|
60
118
|
</div>
|
|
61
119
|
);
|
|
@@ -25,11 +25,16 @@
|
|
|
25
25
|
background: var(--ifm-color-emphasis-200);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
[data-theme='dark'] .lightToggleIcon {
|
|
28
|
+
.toggleIcon {
|
|
30
29
|
display: none;
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
[data-theme-choice='system'] .systemToggleIcon,
|
|
33
|
+
[data-theme-choice='light'] .lightToggleIcon,
|
|
34
|
+
[data-theme-choice='dark'] .darkToggleIcon {
|
|
35
|
+
display: initial;
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
.toggleButtonDisabled {
|
|
34
39
|
cursor: not-allowed;
|
|
35
40
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactNode } from 'react';
|
|
8
|
+
import type { Props } from '@theme/Icon/SystemColorMode';
|
|
9
|
+
export default function IconSystemColorMode(props: Props): ReactNode;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
export default function IconSystemColorMode(props) {
|
|
8
|
+
return (
|
|
9
|
+
<svg viewBox="0 0 24 24" width={24} height={24} {...props}>
|
|
10
|
+
<path
|
|
11
|
+
fill="currentColor"
|
|
12
|
+
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"
|
|
13
|
+
/>
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -10,9 +10,9 @@ import ColorModeToggle from '@theme/ColorModeToggle';
|
|
|
10
10
|
import styles from './styles.module.css';
|
|
11
11
|
export default function NavbarColorModeToggle({className}) {
|
|
12
12
|
const navbarStyle = useThemeConfig().navbar.style;
|
|
13
|
-
const
|
|
14
|
-
const {
|
|
15
|
-
if (
|
|
13
|
+
const {disableSwitch, respectPrefersColorScheme} = useThemeConfig().colorMode;
|
|
14
|
+
const {colorModeChoice, setColorMode} = useColorMode();
|
|
15
|
+
if (disableSwitch) {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
18
|
return (
|
|
@@ -21,7 +21,8 @@ export default function NavbarColorModeToggle({className}) {
|
|
|
21
21
|
buttonClassName={
|
|
22
22
|
navbarStyle === 'dark' ? styles.darkNavbarColorModeToggle : undefined
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
respectPrefersColorScheme={respectPrefersColorScheme}
|
|
25
|
+
value={colorModeChoice}
|
|
25
26
|
onChange={setColorMode}
|
|
26
27
|
/>
|
|
27
28
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/theme-classic",
|
|
3
|
-
"version": "3.7.0-canary-
|
|
3
|
+
"version": "3.7.0-canary-6254",
|
|
4
4
|
"description": "Classic theme for Docusaurus",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/theme-classic.d.ts",
|
|
@@ -20,19 +20,19 @@
|
|
|
20
20
|
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@docusaurus/core": "3.7.0-canary-
|
|
24
|
-
"@docusaurus/logger": "3.7.0-canary-
|
|
25
|
-
"@docusaurus/mdx-loader": "3.7.0-canary-
|
|
26
|
-
"@docusaurus/module-type-aliases": "3.7.0-canary-
|
|
27
|
-
"@docusaurus/plugin-content-blog": "3.7.0-canary-
|
|
28
|
-
"@docusaurus/plugin-content-docs": "3.7.0-canary-
|
|
29
|
-
"@docusaurus/plugin-content-pages": "3.7.0-canary-
|
|
30
|
-
"@docusaurus/theme-common": "3.7.0-canary-
|
|
31
|
-
"@docusaurus/theme-translations": "3.7.0-canary-
|
|
32
|
-
"@docusaurus/types": "3.7.0-canary-
|
|
33
|
-
"@docusaurus/utils": "3.7.0-canary-
|
|
34
|
-
"@docusaurus/utils-common": "3.7.0-canary-
|
|
35
|
-
"@docusaurus/utils-validation": "3.7.0-canary-
|
|
23
|
+
"@docusaurus/core": "3.7.0-canary-6254",
|
|
24
|
+
"@docusaurus/logger": "3.7.0-canary-6254",
|
|
25
|
+
"@docusaurus/mdx-loader": "3.7.0-canary-6254",
|
|
26
|
+
"@docusaurus/module-type-aliases": "3.7.0-canary-6254",
|
|
27
|
+
"@docusaurus/plugin-content-blog": "3.7.0-canary-6254",
|
|
28
|
+
"@docusaurus/plugin-content-docs": "3.7.0-canary-6254",
|
|
29
|
+
"@docusaurus/plugin-content-pages": "3.7.0-canary-6254",
|
|
30
|
+
"@docusaurus/theme-common": "3.7.0-canary-6254",
|
|
31
|
+
"@docusaurus/theme-translations": "3.7.0-canary-6254",
|
|
32
|
+
"@docusaurus/types": "3.7.0-canary-6254",
|
|
33
|
+
"@docusaurus/utils": "3.7.0-canary-6254",
|
|
34
|
+
"@docusaurus/utils-common": "3.7.0-canary-6254",
|
|
35
|
+
"@docusaurus/utils-validation": "3.7.0-canary-6254",
|
|
36
36
|
"@mdx-js/react": "^3.0.0",
|
|
37
37
|
"clsx": "^2.0.0",
|
|
38
38
|
"copy-text-to-clipboard": "^3.2.0",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"engines": {
|
|
63
63
|
"node": ">=18.0"
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "a877206d157c7a960696a43ea62d43c0a5d211e9"
|
|
66
66
|
}
|
package/src/inlineScripts.ts
CHANGED
|
@@ -30,43 +30,22 @@ export function getThemeInlineScript({
|
|
|
30
30
|
return `(function() {
|
|
31
31
|
var defaultMode = '${defaultMode}';
|
|
32
32
|
var respectPrefersColorScheme = ${respectPrefersColorScheme};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
document.documentElement.setAttribute('data-theme', theme);
|
|
33
|
+
function getSystemColorMode() {
|
|
34
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
36
35
|
}
|
|
37
|
-
|
|
38
36
|
function getQueryStringTheme() {
|
|
39
37
|
try {
|
|
40
38
|
return new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
|
|
41
|
-
} catch (e) {
|
|
42
|
-
}
|
|
39
|
+
} catch (e) {}
|
|
43
40
|
}
|
|
44
|
-
|
|
45
41
|
function getStoredTheme() {
|
|
46
42
|
try {
|
|
47
43
|
return window['${siteStorage.type}'].getItem('${themeStorageKey}');
|
|
48
|
-
} catch (err) {
|
|
49
|
-
}
|
|
44
|
+
} catch (err) {}
|
|
50
45
|
}
|
|
51
|
-
|
|
52
46
|
var initialTheme = getQueryStringTheme() || getStoredTheme();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} else {
|
|
56
|
-
if (
|
|
57
|
-
respectPrefersColorScheme &&
|
|
58
|
-
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
59
|
-
) {
|
|
60
|
-
setDataThemeAttribute('dark');
|
|
61
|
-
} else if (
|
|
62
|
-
respectPrefersColorScheme &&
|
|
63
|
-
window.matchMedia('(prefers-color-scheme: light)').matches
|
|
64
|
-
) {
|
|
65
|
-
setDataThemeAttribute('light');
|
|
66
|
-
} else {
|
|
67
|
-
setDataThemeAttribute(defaultMode === 'dark' ? 'dark' : 'light');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
47
|
+
document.documentElement.setAttribute('data-theme', initialTheme || (respectPrefersColorScheme ? getSystemColorMode() : defaultMode));
|
|
48
|
+
document.documentElement.setAttribute('data-theme-choice', initialTheme || (respectPrefersColorScheme ? 'system' : defaultMode));
|
|
70
49
|
})();`;
|
|
71
50
|
}
|
|
72
51
|
|
|
@@ -11,40 +11,105 @@ import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
|
11
11
|
import {translate} from '@docusaurus/Translate';
|
|
12
12
|
import IconLightMode from '@theme/Icon/LightMode';
|
|
13
13
|
import IconDarkMode from '@theme/Icon/DarkMode';
|
|
14
|
+
import IconSystemColorMode from '@theme/Icon/SystemColorMode';
|
|
14
15
|
import type {Props} from '@theme/ColorModeToggle';
|
|
16
|
+
import type {ColorMode} from '@docusaurus/theme-common';
|
|
15
17
|
|
|
16
18
|
import styles from './styles.module.css';
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
// The order of color modes is defined here, and can be customized with swizzle
|
|
21
|
+
function getNextColorMode(
|
|
22
|
+
colorMode: ColorMode | null,
|
|
23
|
+
respectPrefersColorScheme: boolean,
|
|
24
|
+
) {
|
|
25
|
+
// 2-value transition
|
|
26
|
+
if (!respectPrefersColorScheme) {
|
|
27
|
+
return colorMode === 'dark' ? 'light' : 'dark';
|
|
28
|
+
}
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
// 3-value transition
|
|
31
|
+
switch (colorMode) {
|
|
32
|
+
case null:
|
|
33
|
+
return 'light';
|
|
34
|
+
case 'light':
|
|
35
|
+
return 'dark';
|
|
36
|
+
case 'dark':
|
|
37
|
+
return null;
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(`unexpected color mode ${colorMode}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getColorModeLabel(colorMode: ColorMode | null): string {
|
|
44
|
+
switch (colorMode) {
|
|
45
|
+
case null:
|
|
46
|
+
return translate({
|
|
47
|
+
message: 'system mode',
|
|
48
|
+
id: 'theme.colorToggle.ariaLabel.mode.system',
|
|
49
|
+
description: 'The name for the system color mode',
|
|
50
|
+
});
|
|
51
|
+
case 'light':
|
|
52
|
+
return translate({
|
|
53
|
+
message: 'light mode',
|
|
54
|
+
id: 'theme.colorToggle.ariaLabel.mode.light',
|
|
55
|
+
description: 'The name for the light color mode',
|
|
56
|
+
});
|
|
57
|
+
case 'dark':
|
|
58
|
+
return translate({
|
|
59
|
+
message: 'dark mode',
|
|
60
|
+
id: 'theme.colorToggle.ariaLabel.mode.dark',
|
|
61
|
+
description: 'The name for the dark color mode',
|
|
62
|
+
});
|
|
63
|
+
default:
|
|
64
|
+
throw new Error(`unexpected color mode ${colorMode}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getColorModeAriaLabel(colorMode: ColorMode | null) {
|
|
69
|
+
return translate(
|
|
27
70
|
{
|
|
28
71
|
message: 'Switch between dark and light mode (currently {mode})',
|
|
29
72
|
id: 'theme.colorToggle.ariaLabel',
|
|
30
|
-
description: 'The ARIA label for the
|
|
73
|
+
description: 'The ARIA label for the color mode toggle',
|
|
31
74
|
},
|
|
32
75
|
{
|
|
33
|
-
mode:
|
|
34
|
-
value === 'dark'
|
|
35
|
-
? translate({
|
|
36
|
-
message: 'dark mode',
|
|
37
|
-
id: 'theme.colorToggle.ariaLabel.mode.dark',
|
|
38
|
-
description: 'The name for the dark color mode',
|
|
39
|
-
})
|
|
40
|
-
: translate({
|
|
41
|
-
message: 'light mode',
|
|
42
|
-
id: 'theme.colorToggle.ariaLabel.mode.light',
|
|
43
|
-
description: 'The name for the light color mode',
|
|
44
|
-
}),
|
|
76
|
+
mode: getColorModeLabel(colorMode),
|
|
45
77
|
},
|
|
46
78
|
);
|
|
79
|
+
}
|
|
47
80
|
|
|
81
|
+
function CurrentColorModeIcon(): ReactNode {
|
|
82
|
+
// 3 icons are always rendered for technical reasons
|
|
83
|
+
// We use "data-theme-choice" to render the correct one
|
|
84
|
+
// This must work even before React hydrates
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<IconLightMode
|
|
88
|
+
// a18y is handled at the button level,
|
|
89
|
+
// not relying on button content (svg icons)
|
|
90
|
+
aria-hidden
|
|
91
|
+
className={clsx(styles.toggleIcon, styles.lightToggleIcon)}
|
|
92
|
+
/>
|
|
93
|
+
<IconDarkMode
|
|
94
|
+
aria-hidden
|
|
95
|
+
className={clsx(styles.toggleIcon, styles.darkToggleIcon)}
|
|
96
|
+
/>
|
|
97
|
+
<IconSystemColorMode
|
|
98
|
+
aria-hidden
|
|
99
|
+
className={clsx(styles.toggleIcon, styles.systemToggleIcon)}
|
|
100
|
+
/>
|
|
101
|
+
</>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function ColorModeToggle({
|
|
106
|
+
className,
|
|
107
|
+
buttonClassName,
|
|
108
|
+
respectPrefersColorScheme,
|
|
109
|
+
value,
|
|
110
|
+
onChange,
|
|
111
|
+
}: Props): ReactNode {
|
|
112
|
+
const isBrowser = useIsBrowser();
|
|
48
113
|
return (
|
|
49
114
|
<div className={clsx(styles.toggle, className)}>
|
|
50
115
|
<button
|
|
@@ -55,18 +120,23 @@ function ColorModeToggle({
|
|
|
55
120
|
buttonClassName,
|
|
56
121
|
)}
|
|
57
122
|
type="button"
|
|
58
|
-
onClick={() =>
|
|
123
|
+
onClick={() =>
|
|
124
|
+
onChange(getNextColorMode(value, respectPrefersColorScheme))
|
|
125
|
+
}
|
|
59
126
|
disabled={!isBrowser}
|
|
60
|
-
title={
|
|
61
|
-
aria-label={
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
title={getColorModeLabel(value)}
|
|
128
|
+
aria-label={getColorModeAriaLabel(value)}
|
|
129
|
+
|
|
130
|
+
// For accessibility decisions
|
|
131
|
+
// See https://github.com/facebook/docusaurus/issues/7667#issuecomment-2724401796
|
|
132
|
+
|
|
133
|
+
// aria-live disabled on purpose - This is annoying because:
|
|
134
|
+
// - without this attribute, VoiceOver doesn't announce on button enter
|
|
135
|
+
// - with this attribute, VoiceOver announces twice on ctrl+opt+space
|
|
136
|
+
// - with this attribute, NVDA announces many times
|
|
137
|
+
// aria-live="polite"
|
|
138
|
+
>
|
|
139
|
+
<CurrentColorModeIcon />
|
|
70
140
|
</button>
|
|
71
141
|
</div>
|
|
72
142
|
);
|
|
@@ -25,11 +25,16 @@
|
|
|
25
25
|
background: var(--ifm-color-emphasis-200);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
[data-theme='dark'] .lightToggleIcon {
|
|
28
|
+
.toggleIcon {
|
|
30
29
|
display: none;
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
[data-theme-choice='system'] .systemToggleIcon,
|
|
33
|
+
[data-theme-choice='light'] .lightToggleIcon,
|
|
34
|
+
[data-theme-choice='dark'] .darkToggleIcon {
|
|
35
|
+
display: initial;
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
.toggleButtonDisabled {
|
|
34
39
|
cursor: not-allowed;
|
|
35
40
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {ReactNode} from 'react';
|
|
9
|
+
import type {Props} from '@theme/Icon/SystemColorMode';
|
|
10
|
+
|
|
11
|
+
export default function IconSystemColorMode(props: Props): ReactNode {
|
|
12
|
+
return (
|
|
13
|
+
<svg viewBox="0 0 24 24" width={24} height={24} {...props}>
|
|
14
|
+
<path
|
|
15
|
+
fill="currentColor"
|
|
16
|
+
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"
|
|
17
|
+
/>
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -13,10 +13,10 @@ import styles from './styles.module.css';
|
|
|
13
13
|
|
|
14
14
|
export default function NavbarColorModeToggle({className}: Props): ReactNode {
|
|
15
15
|
const navbarStyle = useThemeConfig().navbar.style;
|
|
16
|
-
const
|
|
17
|
-
const {
|
|
16
|
+
const {disableSwitch, respectPrefersColorScheme} = useThemeConfig().colorMode;
|
|
17
|
+
const {colorModeChoice, setColorMode} = useColorMode();
|
|
18
18
|
|
|
19
|
-
if (
|
|
19
|
+
if (disableSwitch) {
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -26,7 +26,8 @@ export default function NavbarColorModeToggle({className}: Props): ReactNode {
|
|
|
26
26
|
buttonClassName={
|
|
27
27
|
navbarStyle === 'dark' ? styles.darkNavbarColorModeToggle : undefined
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
respectPrefersColorScheme={respectPrefersColorScheme}
|
|
30
|
+
value={colorModeChoice}
|
|
30
31
|
onChange={setColorMode}
|
|
31
32
|
/>
|
|
32
33
|
);
|
package/src/theme-classic.d.ts
CHANGED
|
@@ -1555,12 +1555,13 @@ declare module '@theme/ColorModeToggle' {
|
|
|
1555
1555
|
export interface Props {
|
|
1556
1556
|
readonly className?: string;
|
|
1557
1557
|
readonly buttonClassName?: string;
|
|
1558
|
-
readonly
|
|
1558
|
+
readonly respectPrefersColorScheme: boolean;
|
|
1559
|
+
readonly value: ColorMode | null;
|
|
1559
1560
|
/**
|
|
1560
1561
|
* The parameter represents the "to-be" value. For example, if currently in
|
|
1561
|
-
*
|
|
1562
|
+
* light mode, clicking the button should call `onChange("dark")`
|
|
1562
1563
|
*/
|
|
1563
|
-
readonly onChange: (colorMode: ColorMode) => void;
|
|
1564
|
+
readonly onChange: (colorMode: ColorMode | null) => void;
|
|
1564
1565
|
}
|
|
1565
1566
|
|
|
1566
1567
|
export default function ColorModeToggle(props: Props): ReactNode;
|
|
@@ -1617,6 +1618,14 @@ declare module '@theme/Icon/LightMode' {
|
|
|
1617
1618
|
export default function IconLightMode(props: Props): ReactNode;
|
|
1618
1619
|
}
|
|
1619
1620
|
|
|
1621
|
+
declare module '@theme/Icon/SystemColorMode' {
|
|
1622
|
+
import type {ComponentProps} from 'react';
|
|
1623
|
+
|
|
1624
|
+
export interface Props extends ComponentProps<'svg'> {}
|
|
1625
|
+
|
|
1626
|
+
export default function IconSystemColorMode(props: Props): JSX.Element;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1620
1629
|
declare module '@theme/Icon/Menu' {
|
|
1621
1630
|
import type {ComponentProps, ReactNode} from 'react';
|
|
1622
1631
|
|