Skip to content

Commit 0d0bf85

Browse files
committed
Add DarkMode component
1 parent 0b07656 commit 0d0bf85

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

docs/astro.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default defineConfig({
1212
},
1313
plugins: [
1414
starlightGiscus({
15+
element: 'button.darkmode-toggle',
1516
repo: 'dragomano/starlight-giscus',
1617
repoId: 'R_kgDONyBz0w',
1718
category: 'Q&A',
@@ -28,6 +29,9 @@ export default defineConfig({
2829
social: [
2930
{ icon: 'github', label: 'GitHub', href: 'https://github.com/dragomano/starlight-giscus' },
3031
],
32+
components: {
33+
ThemeSelect: './src/components/DarkMode.astro',
34+
},
3135
title: 'Starlight Giscus',
3236
locales: {
3337
root: {

docs/src/components/DarkMode.astro

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
---
2+
/**
3+
* Dark Mode Toggle Component
4+
*
5+
* @description A fully accessible dark mode toggle with auto/light/dark modes
6+
*/
7+
8+
interface Props {
9+
/**
10+
* Additional classes to apply to the button
11+
*/
12+
class?: string;
13+
/**
14+
* Accessible label for the toggle button
15+
* @default "Переключить тему"
16+
*/
17+
label?: string;
18+
/**
19+
* Children elements for slots
20+
*/
21+
children?: any;
22+
/**
23+
* HTML attributes to spread on the dark mode toggle
24+
*/
25+
[key: string]: string | number | boolean | undefined | any;
26+
}
27+
28+
const { class: className, label = 'Переключить тему', ...rest } = Astro.props;
29+
---
30+
31+
<button
32+
class:list={['darkmode-toggle', className]}
33+
aria-pressed='false'
34+
aria-label={label}
35+
transition:persist
36+
{...rest}
37+
>
38+
<span class='icon icon-light'>
39+
<slot name='light'>
40+
<svg
41+
xmlns='http://www.w3.org/2000/svg'
42+
aria-hidden='true'
43+
width='32'
44+
height='32'
45+
viewBox='0 0 24 24'
46+
>
47+
<path
48+
fill-rule='evenodd'
49+
clip-rule='evenodd'
50+
d='M13 3a1 1 0 1 0-2 0v1a1 1 0 1 0 2 0V3zM5.707 4.293a1 1 0 0 0-1.414 1.414l1 1a1 1 0 0 0 1.414-1.414l-1-1zm14 0a1 1 0 0 0-1.414 0l-1 1a1 1 0 0 0 1.414 1.414l1-1a1 1 0 0 0 0-1.414zM12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10zm-9 4a1 1 0 1 0 0 2h1a1 1 0 1 0 0-2H3zm17 0a1 1 0 1 0 0 2h1a1 1 0 1 0 0-2h-1zM6.707 18.707a1 1 0 0 0-1.414-1.414l-1 1a1 1 0 0 0 1.414 1.414l1-1zm12-1.414a1 1 0 0 0-1.414 1.414l1 1a1 1 0 0 0 1.414-1.414l-1-1zM13 20a1 1 0 1 0-2 0v1a1 1 0 1 0 2 0v-1z'
51+
fill='currentColor'></path>
52+
</svg>
53+
</slot>
54+
</span>
55+
<span class='icon icon-dark'>
56+
<slot name='dark'>
57+
<svg
58+
xmlns='http://www.w3.org/2000/svg'
59+
aria-hidden='true'
60+
width='32'
61+
height='32'
62+
viewBox='0 0 24 24'
63+
>
64+
<path
65+
fill='currentColor'
66+
d='M9.353 3C5.849 4.408 3 7.463 3 11.47A9.53 9.53 0 0 0 12.53 21c4.007 0 7.062-2.849 8.47-6.353C8.17 17.065 8.14 8.14 9.353 3z'
67+
></path>
68+
</svg>
69+
</slot>
70+
</span>
71+
</button>
72+
73+
<script is:inline>
74+
if (!window.darkModeInitialized) {
75+
window.darkModeInitialized = true;
76+
77+
const root = document.documentElement;
78+
let storedTheme = localStorage.getItem('starlight-theme');
79+
80+
const applyTheme = (theme, store = true) => {
81+
const darkModeToggles = document.querySelectorAll('.darkmode-toggle');
82+
const isDarkTheme = theme === 'dark';
83+
84+
root.setAttribute('data-theme', theme);
85+
root.style.colorScheme = theme;
86+
87+
darkModeToggles.forEach((toggle) => {
88+
toggle.setAttribute('aria-pressed', isDarkTheme.toString());
89+
90+
const lightIcon = toggle.querySelector('.icon-light');
91+
const darkIcon = toggle.querySelector('.icon-dark');
92+
93+
if (lightIcon && darkIcon) {
94+
lightIcon.style.display = isDarkTheme ? 'flex' : 'none';
95+
darkIcon.style.display = isDarkTheme ? 'none' : 'flex';
96+
}
97+
});
98+
99+
if (store) {
100+
localStorage.setItem('starlight-theme', theme);
101+
}
102+
};
103+
104+
const updateGiscusTheme = (newTheme) => {
105+
const giscusFrame = document.querySelector('iframe.giscus-frame');
106+
107+
if (giscusFrame) {
108+
giscusFrame.contentWindow?.postMessage(
109+
{
110+
giscus: {
111+
setConfig: {
112+
theme: newTheme,
113+
},
114+
},
115+
},
116+
'*',
117+
);
118+
}
119+
};
120+
121+
const applySystemTheme = () => {
122+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
123+
124+
applyTheme(prefersDark ? 'dark' : 'light', false);
125+
};
126+
127+
const initializeTheme = () => {
128+
if (storedTheme) {
129+
applyTheme(storedTheme);
130+
updateGiscusTheme(storedTheme);
131+
132+
return;
133+
}
134+
135+
applySystemTheme();
136+
};
137+
138+
initializeTheme();
139+
140+
document.addEventListener('click', (e) => {
141+
if (e.target.closest('.darkmode-toggle')) {
142+
const currentTheme = root.getAttribute('data-theme');
143+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
144+
145+
applyTheme(newTheme);
146+
updateGiscusTheme(newTheme);
147+
}
148+
});
149+
150+
document.addEventListener('astro:after-swap', () => {
151+
storedTheme = localStorage.getItem('starlight-theme');
152+
153+
initializeTheme();
154+
});
155+
}
156+
</script>
157+
158+
<style>
159+
:where(.darkmode-toggle) {
160+
display: inline-flex;
161+
justify-content: center;
162+
align-items: center;
163+
cursor: pointer;
164+
border: 2px solid;
165+
border-radius: 0.5rem;
166+
background: transparent;
167+
padding-inline: 0.5rem;
168+
padding-block: 0.5rem;
169+
}
170+
171+
:where(.darkmode-toggle:hover),
172+
:where(.darkmode-toggle:focus-visible) {
173+
box-shadow: 0 0 0 0.25rem;
174+
}
175+
176+
:where(.darkmode-toggle:focus-visible) {
177+
outline: none;
178+
}
179+
180+
:where(.icon) {
181+
display: flex;
182+
inline-size: 1.5rem;
183+
block-size: 1.5rem;
184+
}
185+
186+
:where(.icon svg),
187+
:where(.icon) :global(svg) {
188+
inline-size: 100%;
189+
block-size: 100%;
190+
}
191+
192+
:where(.darkmode-toggle .icon-dark) {
193+
display: flex;
194+
}
195+
196+
:where(.darkmode-toggle .icon-light) {
197+
display: none;
198+
}
199+
200+
[data-theme='dark'] .darkmode-toggle .icon-dark {
201+
display: none;
202+
}
203+
204+
[data-theme='dark'] .darkmode-toggle .icon-light {
205+
display: flex;
206+
}
207+
</style>

0 commit comments

Comments
 (0)