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

refactor(theme-{classic,common}): refactor ColorModeToggle + useColorMode() hook #6930

Merged
merged 15 commits into from Mar 18, 2022
Merged
10 changes: 7 additions & 3 deletions packages/docusaurus-theme-classic/src/theme-classic.d.ts
Expand Up @@ -746,12 +746,16 @@ declare module '@theme/TOCCollapsible' {
}

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

export interface Props {
readonly className?: string;
readonly checked: boolean;
readonly onChange: (e: SyntheticEvent) => void;
readonly value: ColorMode;
/**
* 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;
}

export default function Toggle(props: Props): JSX.Element;
Expand Down
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import React, {useState, useRef, useEffect} from 'react';
import React from 'react';
import type {Props} from '@theme/ColorModeToggle';
import useIsBrowser from '@docusaurus/useIsBrowser';
import {translate} from '@docusaurus/Translate';
Expand All @@ -15,78 +15,51 @@ import IconDarkMode from '@theme/IconDarkMode';
import clsx from 'clsx';
import styles from './styles.module.css';

function ColorModeToggle({
className,
checked: defaultChecked,
onChange,
}: Props): JSX.Element {
function ColorModeToggle({className, value, onChange}: Props): JSX.Element {
const isBrowser = useIsBrowser();
const [checked, setChecked] = useState(defaultChecked);
const [focused, setFocused] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
setChecked(defaultChecked);
}, [defaultChecked]);
const title = translate(
{
message: 'Switch between dark and light mode (currently {mode})',
id: 'theme.colorToggle.ariaLabel',
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',
}),
},
);

return (
<div
className={clsx(
styles.toggle,
className,
checked && styles.toggleChecked,
focused && styles.toggleFocused,
!isBrowser && styles.toggleDisabled,
)}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
className={styles.toggleButton}
role="button"
tabIndex={-1}
onClick={() => inputRef.current?.click()}>
<div className={clsx(styles.toggle, className)}>
<button
className={clsx(
'clean-btn',
styles.toggleButton,
!isBrowser && styles.toggleButtonDisabled,
)}
type="button"
onClick={() => onChange(value === 'dark' ? 'light' : 'dark')}
disabled={!isBrowser}
title={title}
aria-label={title}>
<IconLightMode
className={clsx(styles.toggleIcon, styles.lightToggleIcon)}
/>
<IconDarkMode
className={clsx(styles.toggleIcon, styles.darkToggleIcon)}
/>
</div>

<input
ref={inputRef}
checked={checked}
type="checkbox"
className={styles.toggleScreenReader}
aria-label={translate(
{
message: 'Switch between dark and light mode (currently {mode})',
id: 'theme.colorToggle.ariaLabel',
description: 'The ARIA label for the navbar color mode toggle',
},
{
mode: checked
? 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',
}),
},
)}
onChange={onChange}
onClick={() => setChecked(!checked)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
inputRef.current?.click();
}
}}
/>
</button>
</div>
);
}
Expand Down
Expand Up @@ -6,46 +6,30 @@
*/

.toggle {
position: relative;
width: 32px;
height: 32px;
}

.toggleScreenReader {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
position: absolute;
width: 1px;
}

.toggleDisabled {
cursor: not-allowed;
width: 2rem;
height: 2rem;
}

.toggleButton {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
align-items: center;
display: flex;
justify-content: center;
width: 100%;
height: 100%;
border-radius: 50%;
transition: background var(--ifm-transition-fast);
}

.toggleButton:hover {
background-color: #00000010;
}

[data-theme='dark'] .toggleButton:hover {
background-color: #ffffff20;
background: var(--ifm-color-emphasis-200);
}

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

.toggleButtonDisabled {
cursor: not-allowed;
}
20 changes: 10 additions & 10 deletions packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx
Expand Up @@ -88,12 +88,12 @@ function useColorModeToggle() {
const {
colorMode: {disableSwitch},
} = useThemeConfig();
const {isDarkTheme, setLightTheme, setDarkTheme} = useColorMode();
const toggle = useCallback(
(e) => (e.target.checked ? setDarkTheme() : setLightTheme()),
[setLightTheme, setDarkTheme],
);
return {isDarkTheme, toggle, disabled: disableSwitch};
const {colorMode, setColorMode} = useColorMode();
return {
value: colorMode,
onChange: setColorMode,
disabled: disableSwitch,
};
}

function useSecondaryMenu({
Expand Down Expand Up @@ -173,8 +173,8 @@ function NavbarMobileSidebar({
{!colorModeToggle.disabled && (
<ColorModeToggle
className={styles.navbarSidebarToggle}
checked={colorModeToggle.isDarkTheme}
onChange={colorModeToggle.toggle}
value={colorModeToggle.value}
onChange={colorModeToggle.onChange}
/>
)}
<button
Expand Down Expand Up @@ -279,8 +279,8 @@ export default function Navbar(): JSX.Element {
{!colorModeToggle.disabled && (
<ColorModeToggle
className={styles.toggle}
checked={colorModeToggle.isDarkTheme}
onChange={colorModeToggle.toggle}
value={colorModeToggle.value}
onChange={colorModeToggle.onChange}
/>
)}
{!hasSearchNavbarItem && <SearchBar />}
Expand Down
Expand Up @@ -16,12 +16,13 @@ import styles from './styles.module.css';

export default function ThemedImage(props: Props): JSX.Element {
const isBrowser = useIsBrowser();
const {isDarkTheme} = useColorMode();
const {colorMode} = useColorMode();
const {sources, className, alt, ...propsRest} = props;

type SourceName = keyof Props['sources'];

const clientThemes: SourceName[] = isDarkTheme ? ['dark'] : ['light'];
const clientThemes: SourceName[] =
colorMode === 'dark' ? ['dark'] : ['light'];

const renderedSourceNames: SourceName[] = isBrowser
? clientThemes
Expand Down
4 changes: 2 additions & 2 deletions packages/docusaurus-theme-common/src/hooks/usePrismTheme.ts
Expand Up @@ -11,10 +11,10 @@ import {useThemeConfig} from '../utils/useThemeConfig';

export default function usePrismTheme(): typeof defaultTheme {
const {prism} = useThemeConfig();
const {isDarkTheme} = useColorMode();
const {colorMode} = useColorMode();
const lightModeTheme = prism.theme || defaultTheme;
const darkModeTheme = prism.darkTheme || lightModeTheme;
const prismTheme = isDarkTheme ? darkModeTheme : lightModeTheme;
const prismTheme = colorMode === 'dark' ? darkModeTheme : lightModeTheme;

return prismTheme;
}
7 changes: 6 additions & 1 deletion packages/docusaurus-theme-common/src/index.ts
Expand Up @@ -140,7 +140,12 @@ export {
PluginHtmlClassNameProvider,
} from './utils/metadataUtilsTemp';

export {useColorMode, ColorModeProvider} from './utils/colorModeUtils';
export {
useColorMode,
ColorModeProvider,
type ColorMode,
} from './utils/colorModeUtils';

export {
useTabGroupChoice,
TabGroupChoiceProvider,
Expand Down