Skip to content

Commit e82d653

Browse files
committed
feat: add theme toggler button in top bar
The toggler button will read/write to the `theme` key in localStorage. For simplicity, I wanted to keep the theming logic strictly on the client side. This is why I chose localStorage instead of cookies.
1 parent 02c5801 commit e82d653

3 files changed

Lines changed: 74 additions & 3 deletions

File tree

gcp/website/frontend3/src/base.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99
{% if disable_turbo_cache %}
1010
<meta name="turbo-cache-control" content="no-cache">
1111
{% endif %}
12+
<script>
13+
// Early inline script element here is necessary to prevent the "flash of
14+
// unstyled content" problem where the dark default theme is first rendered
15+
// followed by a sudden flash to light mode.
16+
// See https://en.wikipedia.org/wiki/Flash_of_unstyled_content.
17+
const theme = localStorage.getItem('theme') || 'dark';
18+
document.documentElement.setAttribute('data-theme', theme);
19+
</script>
1220
<link rel="icon" type="image/png" href="/static/img/favicon-32x32.png" sizes="32x32">
1321
<link rel="icon" type="image/png" href="/static/img/favicon-16x16.png" sizes="16x16">
1422
<link rel="preconnect" href="https://fonts.googleapis.com">
@@ -92,7 +100,13 @@
92100
</a>
93101
</li>
94102
{% endif %}
95-
<li>
103+
<li class="theme-toggle-container">
104+
<button id="theme-toggle" class="theme-toggle-btn" aria-label="Toggle theme">
105+
<span class="material-icons icon-light-mode">light_mode</span>
106+
<span class="material-icons icon-dark-mode">dark_mode</span>
107+
</button>
108+
</li>
109+
<li class="github-container">
96110
<a class="logo-img" href="https://github.com/google/osv.dev" target="_blank"
97111
aria-label="Go to our github page">
98112
<img class="logo-link" src="/static/img/github-mark-white.svg" alt="Github Logo" width="24" height="24">

gcp/website/frontend3/src/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ function initializeSearch() {
5656
searchInstance = new ExpandableSearch();
5757
}
5858

59+
function initializeThemeToggle() {
60+
const toggle = document.getElementById('theme-toggle');
61+
// Ensure the event handler is attached only once.
62+
if (toggle && !toggle.dataset.themeInitialized) {
63+
toggle.dataset.themeInitialized = 'true';
64+
toggle.addEventListener('click', () => {
65+
const currentTheme = localStorage.getItem("theme") || 'dark';
66+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
67+
localStorage.setItem('theme', newTheme);
68+
document.documentElement.setAttribute('data-theme', newTheme);
69+
});
70+
}
71+
}
72+
5973
// Ensure initialization happens after all dependencies are loaded
6074
function ensureInitialization() {
6175
if (!customElements) {
@@ -65,6 +79,7 @@ function ensureInitialization() {
6579

6680
if (customElements.get('md-filled-text-field')) {
6781
initializeSearch();
82+
initializeThemeToggle();
6883
} else {
6984
// wait a bit longer for components to load
7085
setTimeout(ensureInitialization, 50);

gcp/website/frontend3/src/styles.scss

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ pre {
264264
}
265265

266266
.logo-link {
267-
width: 24px;
268-
height: 24px;
267+
width: 20px;
268+
height: 20px;
269269
}
270270

271271
.tabs {
@@ -280,6 +280,11 @@ pre {
280280
vertical-align: middle;
281281
}
282282

283+
a.logo-img {
284+
display: flex;
285+
align-items: center;
286+
}
287+
283288
// Remove external link indicator.
284289
a.logo-img::after {
285290
display: none;
@@ -290,6 +295,7 @@ pre {
290295
.social-icons {
291296
padding: 0;
292297
display: flex;
298+
align-items: center;
293299
gap: 20px;
294300
}
295301
}
@@ -2145,3 +2151,39 @@ div.highlight {
21452151
.search-suggestions::-webkit-scrollbar-thumb:hover {
21462152
background-color: $osv-grey-800;
21472153
}
2154+
2155+
/* Theme toggle button visibility */
2156+
.theme-toggle-container {
2157+
display: flex;
2158+
align-items: center;
2159+
}
2160+
.theme-toggle-btn {
2161+
background: transparent;
2162+
border: none;
2163+
color: white;
2164+
cursor: pointer;
2165+
padding: 8px;
2166+
display: flex;
2167+
align-items: center;
2168+
justify-content: center;
2169+
transition: background-color 0.2s ease;
2170+
border-radius: 50%;
2171+
2172+
&:hover {
2173+
background-color: rgba(255, 255, 255, 0.1);
2174+
}
2175+
2176+
.icon-dark-mode { display: none; }
2177+
.icon-light-mode { display: inline-block; }
2178+
}
2179+
html[data-theme="light"] .theme-toggle-btn {
2180+
color: $osv-text-color;
2181+
.icon-dark-mode { display: inline-block; }
2182+
.icon-light-mode { display: none; }
2183+
}
2184+
2185+
.github-container {
2186+
width: 36px;
2187+
display: flex;
2188+
justify-content: center;
2189+
}

0 commit comments

Comments
 (0)