Skip to content

Commit 5530f06

Browse files
Merge pull request #223 from mehershiri/swap-theme-icons
Fixed and swapped the theme icons
2 parents b5b3d67 + d440e7a commit 5530f06

File tree

4 files changed

+214
-23
lines changed

4 files changed

+214
-23
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import React, {type ReactNode} from 'react';
2+
import clsx from 'clsx';
3+
import useIsBrowser from '@docusaurus/useIsBrowser';
4+
import {translate} from '@docusaurus/Translate';
5+
import IconLightMode from '@theme/Icon/LightMode';
6+
import IconDarkMode from '@theme/Icon/DarkMode';
7+
import IconSystemColorMode from '@theme/Icon/SystemColorMode';
8+
import type {Props} from '@theme/ColorModeToggle';
9+
import type {ColorMode} from '@docusaurus/theme-common';
10+
11+
import styles from './styles.module.css';
12+
13+
// The order of color modes is defined here, and can be customized with swizzle
14+
function getNextColorMode(
15+
colorMode: ColorMode | null,
16+
respectPrefersColorScheme: boolean,
17+
) {
18+
// 2-value transition
19+
if (!respectPrefersColorScheme) {
20+
return colorMode === 'dark' ? 'light' : 'dark';
21+
}
22+
23+
// 3-value transition
24+
switch (colorMode) {
25+
case null:
26+
return 'light';
27+
case 'light':
28+
return 'dark';
29+
case 'dark':
30+
return null;
31+
default:
32+
throw new Error(`unexpected color mode ${colorMode}`);
33+
}
34+
}
35+
36+
function getColorModeLabel(colorMode: ColorMode | null): string {
37+
switch (colorMode) {
38+
case null:
39+
return translate({
40+
message: 'system mode',
41+
id: 'theme.colorToggle.ariaLabel.mode.system',
42+
description: 'The name for the system color mode',
43+
});
44+
case 'light':
45+
return translate({
46+
message: 'switch to dark mode',
47+
id: 'theme.colorToggle.ariaLabel.mode.light',
48+
description: 'The name for the light color mode',
49+
});
50+
case 'dark':
51+
return translate({
52+
message: 'switch to light mode',
53+
id: 'theme.colorToggle.ariaLabel.mode.dark',
54+
description: 'The name for the dark color mode',
55+
});
56+
default:
57+
throw new Error(`unexpected color mode ${colorMode}`);
58+
}
59+
}
60+
61+
function getColorModeAriaLabel(colorMode: ColorMode | null) {
62+
return translate(
63+
{
64+
message: 'Switch between dark and light mode (currently {mode})',
65+
id: 'theme.colorToggle.ariaLabel',
66+
description: 'The ARIA label for the color mode toggle',
67+
},
68+
{
69+
mode: getColorModeLabel(colorMode),
70+
},
71+
);
72+
}
73+
74+
function CurrentColorModeIcon(): ReactNode {
75+
// 3 icons are always rendered for technical reasons
76+
// We use "data-theme-choice" to render the correct one
77+
// This must work even before React hydrates
78+
return (
79+
<>
80+
<IconLightMode
81+
// a18y is handled at the button level,
82+
// not relying on button content (svg icons)
83+
aria-hidden
84+
className={clsx(styles.toggleIcon, styles.darkToggleIcon)}
85+
data-theme-choice="dark"
86+
/>
87+
<IconDarkMode
88+
aria-hidden
89+
className={clsx(styles.toggleIcon, styles.lightToggleIcon)}
90+
data-theme-choice="light"
91+
/>
92+
<IconSystemColorMode
93+
aria-hidden
94+
className={clsx(styles.toggleIcon, styles.systemToggleIcon)}
95+
data-theme-choice="system"
96+
/>
97+
</>
98+
);
99+
}
100+
101+
function ColorModeToggle({
102+
className,
103+
buttonClassName,
104+
respectPrefersColorScheme,
105+
value,
106+
onChange,
107+
}: Props): ReactNode {
108+
const isBrowser = useIsBrowser();
109+
return (
110+
<div className={clsx(styles.toggle, className)}>
111+
<button
112+
className={clsx(
113+
'clean-btn',
114+
styles.toggleButton,
115+
!isBrowser && styles.toggleButtonDisabled,
116+
buttonClassName,
117+
)}
118+
type="button"
119+
onClick={() =>
120+
onChange(getNextColorMode(value, respectPrefersColorScheme))
121+
}
122+
disabled={!isBrowser}
123+
title={getColorModeLabel(value)}
124+
aria-label={getColorModeAriaLabel(value)}
125+
126+
// For accessibility decisions
127+
// See https://github.com/facebook/docusaurus/issues/7667#issuecomment-2724401796
128+
129+
// aria-live disabled on purpose - This is annoying because:
130+
// - without this attribute, VoiceOver doesn't announce on button enter
131+
// - with this attribute, VoiceOver announces twice on ctrl+opt+space
132+
// - with this attribute, NVDA announces many times
133+
// aria-live="polite"
134+
>
135+
<CurrentColorModeIcon />
136+
</button>
137+
</div>
138+
);
139+
}
140+
141+
export default React.memo(ColorModeToggle);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
.toggle {
2+
width: 2rem;
3+
height: 2rem;
4+
}
5+
6+
.toggleButton {
7+
-webkit-tap-highlight-color: transparent;
8+
align-items: center;
9+
display: flex;
10+
justify-content: center;
11+
width: 100%;
12+
height: 100%;
13+
border-radius: 50%;
14+
transition: background var(--ifm-transition-fast);
15+
}
16+
17+
.toggleButton:hover {
18+
background: var(--ifm-color-emphasis-200);
19+
}
20+
21+
.toggleIcon {
22+
display: none;
23+
}
24+
25+
[data-theme-choice='system'] .systemToggleIcon,
26+
[data-theme-choice='light'] .lightToggleIcon,
27+
[data-theme-choice='dark'] .darkToggleIcon {
28+
display: initial;
29+
}
30+
31+
.toggleButtonDisabled {
32+
cursor: not-allowed;
33+
}

src/theme/Navbar/Content/index.tsx

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,44 @@
1-
import React, {type ReactNode} from 'react';
1+
import React, { type ReactNode } from 'react';
22
import clsx from 'clsx';
33
import {
44
useThemeConfig,
55
ErrorCauseBoundary,
66
ThemeClassNames,
7+
useColorMode,
78
} from '@docusaurus/theme-common';
89
import {
910
splitNavbarItems,
1011
useNavbarMobileSidebar,
1112
} from '@docusaurus/theme-common/internal';
12-
import NavbarItem, {type Props as NavbarItemConfig} from '@theme/NavbarItem';
13+
import NavbarItem, { type Props as NavbarItemConfig } from '@theme/NavbarItem';
1314
import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle';
1415
import SearchBar from '@theme/SearchBar';
1516
import NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle';
16-
import NavbarLogo from '@theme/Navbar/Logo';
1717
import NavbarSearch from '@theme/Navbar/Search';
1818

1919
import styles from './styles.module.css';
2020

21-
2221
function useNavbarItems() {
23-
// TODO temporary casting until ThemeConfig type is improved
2422
return useThemeConfig().navbar.items as NavbarItemConfig[];
2523
}
2624

27-
function NavbarItems({items}: {items: NavbarItemConfig[]}): ReactNode {
25+
function NavbarItems({ items }: { items: NavbarItemConfig[] }): ReactNode {
2826
return (
2927
<>
3028
{items.map((item, i) => (
3129
<ErrorCauseBoundary
3230
key={i}
3331
onError={(error) =>
3432
new Error(
35-
`A theme navbar item failed to render.
36-
Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:
37-
${JSON.stringify(item, null, 2)}`,
38-
{cause: error},
33+
`A theme navbar item failed to render. Check your config:\n${JSON.stringify(
34+
item,
35+
null,
36+
2
37+
)}`,
38+
{ cause: error }
3939
)
40-
}>
40+
}
41+
>
4142
<NavbarItem {...item} />
4243
</ErrorCauseBoundary>
4344
))}
@@ -57,16 +58,17 @@ function NavbarContentLayout({
5758
<div
5859
className={clsx(
5960
ThemeClassNames.layout.navbar.containerLeft,
60-
'navbar__items',
61-
)}>
61+
'navbar__items'
62+
)}
63+
>
6264
{left}
63-
6465
</div>
6566
<div
6667
className={clsx(
6768
ThemeClassNames.layout.navbar.containerRight,
68-
'navbar__items navbar__items--right',
69-
)}>
69+
'navbar__items navbar__items--right'
70+
)}
71+
>
7072
{right}
7173
</div>
7274
</div>
@@ -75,28 +77,31 @@ function NavbarContentLayout({
7577

7678
export default function NavbarContent(): ReactNode {
7779
const mobileSidebar = useNavbarMobileSidebar();
78-
7980
const items = useNavbarItems();
8081
const [leftItems, rightItems] = splitNavbarItems(items);
81-
8282
const searchBarItem = items.find((item) => item.type === 'search');
8383

84+
// ✅ Hooks and variables must be at top-level
85+
const { navbar } = useThemeConfig();
86+
const { colorMode } = useColorMode();
87+
88+
const logoSrc =
89+
colorMode === 'dark' && navbar.logo.srcDark ? navbar.logo.srcDark : navbar.logo.src;
90+
8491
return (
8592
<NavbarContentLayout
8693
left={
87-
// TODO stop hardcoding items?
8894
<>
8995
{!mobileSidebar.disabled && <NavbarMobileSidebarToggle />}
90-
<div className={styles.navBarWrap}>
91-
<NavbarLogo />
96+
97+
<div className={styles.navBarWrap}>
98+
<img src={logoSrc} alt={navbar.logo.alt} style={{ height: '32px' }} />
9299
</div>
93100

94101
<NavbarItems items={leftItems} />
95102
</>
96103
}
97104
right={
98-
// TODO stop hardcoding items?
99-
// Ask the user to add the respective navbar items => more flexible
100105
<>
101106
<NavbarItems items={rightItems} />
102107
<NavbarColorModeToggle className={styles.colorModeToggle} />

src/theme/Navbar/Content/logo.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import {useThemeConfig, useColorMode} from '@docusaurus/theme-common';
3+
4+
export default function NavbarLogo() {
5+
const {navbar} = useThemeConfig();
6+
const {colorMode} = useColorMode();
7+
8+
const logoSrc =
9+
colorMode === 'dark' && navbar.logo.srcDark ? navbar.logo.srcDark : navbar.logo.src;
10+
11+
return <img src={logoSrc} alt={navbar.logo.alt} style={{height: '32px'}} />;
12+
}

0 commit comments

Comments
 (0)