diff --git a/templates/header.templ b/templates/header.templ
index 9f8347c..e0293d8 100644
--- a/templates/header.templ
+++ b/templates/header.templ
@@ -98,14 +98,15 @@ templ Header(user User, currentPath string) {
}
diff --git a/templates/header_templ.go b/templates/header_templ.go
index d627977..0a4b4b8 100644
--- a/templates/header_templ.go
+++ b/templates/header_templ.go
@@ -266,102 +266,135 @@ func Header(user User, currentPath string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" class=\"w-full flex items-center gap-2.5 px-3 py-1.5 rounded-lg text-sm transition-colors hover:bg-surface-hover\" style=\"color: var(--text-secondary);\"> ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" style=\"color: var(--text-secondary);\"> ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if user.Theme == opt.Name {
- templ_7745c5c3_Err = Icon("check", "h-4 w-4 shrink-0").Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "Bookshelf
")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- for _, w := range WoodOptions {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue("changeWoodPaneling('" + w.Name + "')")
+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(opt.Label)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/header.templ`, Line: 116, Col: 55}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/header.templ`, Line: 106, Col: 50}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\" class=\"wood-paneling-btn w-full flex items-center gap-2.5 px-3 py-1.5 rounded-lg text-sm transition-colors\" data-wood=\"")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var18 string
- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(w.Name)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/header.templ`, Line: 118, Col: 26}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
+ var templ_7745c5c3_Var18 = []any{"theme-check shrink-0" + themeCheckClass(opt.Name, user.Theme)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\" style=\"color: var(--text-secondary);\"> ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = Icon("check", "h-4 w-4").Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "Bookshelf
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, w := range WoodOptions {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if user.Role == "admin" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -535,12 +568,12 @@ func SidebarUserMenu(user User) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var28 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var28 == nil {
- templ_7745c5c3_Var28 = templ.NopComponent
+ templ_7745c5c3_Var31 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var31 == nil {
+ templ_7745c5c3_Var31 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -611,12 +644,12 @@ func SidebarSignIn(currentPath string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var30 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var30 == nil {
- templ_7745c5c3_Var30 = templ.NopComponent
+ templ_7745c5c3_Var33 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var33 == nil {
+ templ_7745c5c3_Var33 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/templates/utils.go b/templates/utils.go
index 1da27a0..88c9add 100644
--- a/templates/utils.go
+++ b/templates/utils.go
@@ -31,6 +31,17 @@ func navItemClass(current, target string) string {
return base + "text-content-muted hover:text-content hover:bg-surface-hover"
}
+// themeCheckClass returns the class suffix for a theme option's checkmark
+// wrapper: empty for the active theme (visible), " hidden" otherwise. The
+// check element is always rendered so updateThemeIndicators() can move it
+// client-side when the user picks a different theme.
+func themeCheckClass(name, current string) string {
+ if name == current {
+ return ""
+ }
+ return " hidden"
+}
+
func ContainsString(slice []string, item string) bool {
for _, s := range slice {
if s == item {
diff --git a/templates/utils_test.go b/templates/utils_test.go
index 2b5f50f..474b3bb 100644
--- a/templates/utils_test.go
+++ b/templates/utils_test.go
@@ -45,3 +45,12 @@ func TestGroupTunableSettingsEmpty(t *testing.T) {
t.Errorf("expected empty groups, got live=%d restart=%d", len(live), len(restart))
}
}
+
+func TestThemeCheckClass(t *testing.T) {
+ if got := themeCheckClass("dracula", "dracula"); got != "" {
+ t.Errorf("active theme should be visible, got %q", got)
+ }
+ if got := themeCheckClass("nord", "dracula"); got != " hidden" {
+ t.Errorf("inactive theme should be hidden, got %q", got)
+ }
+}
diff --git a/web/src/theme.ts b/web/src/theme.ts
index 968e637..dc20078 100644
--- a/web/src/theme.ts
+++ b/web/src/theme.ts
@@ -41,6 +41,7 @@ const loadTheme = (): void => {
// Change: Remove DOM element lookup, accept theme as parameter
const changeTheme = async (theme: string): Promise => {
applyTheme(theme);
+ updateThemeIndicators();
// Save to server if logged in
const token = localStorage.getItem(TOKEN_STORAGE_KEY);
@@ -78,6 +79,7 @@ const loadUserTheme = async (): Promise => {
const data = await response.json();
if (data.theme) {
applyTheme(data.theme as string);
+ updateThemeIndicators();
}
}
} catch {
@@ -85,10 +87,26 @@ const loadUserTheme = async (): Promise => {
}
};
+// Update the active-theme checkmark in the Appearance menu to follow the
+// theme currently applied to . Each theme button carries its theme name
+// in data-theme and a .theme-check element that is shown for the active theme
+// and hidden for the rest.
+const updateThemeIndicators = (): void => {
+ const match = document.body.className.match(/theme-([\w-]+)/);
+ const current = match ? match[1] : DEFAULT_THEME;
+ document.querySelectorAll(".theme-btn").forEach((btn) => {
+ const check = btn.querySelector(".theme-check");
+ if (!check) return;
+ const theme = btn.getAttribute("data-theme");
+ check.classList.toggle("hidden", theme !== current);
+ });
+};
+
// Initialize theme system
const initializeTheme = (): void => {
loadTheme();
loadUserTheme();
+ updateThemeIndicators();
// Set theme select value to current theme
const themeSelect = document.getElementById(
@@ -210,6 +228,7 @@ Alpine.data("theme", () => ({
loadTheme,
loadUserTheme,
loadWoodPaneling,
+ updateThemeIndicators,
updateWoodPanelingIndicators,
}));
@@ -221,6 +240,7 @@ export {
loadTheme,
loadUserTheme,
loadWoodPaneling,
+ updateThemeIndicators,
updateWoodPanelingIndicators,
};
export type { ThemeType };