Skip to content

Commit ad5f848

Browse files
committed
feat: implement shared ThemeState for consistent theming across ThemeToggle components
1 parent 2a9be57 commit ad5f848

3 files changed

Lines changed: 47 additions & 12 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace ShellDocs.Components.Chrome;
2+
3+
/* Shared theme state for every ThemeToggle instance on the page.
4+
Without this, each toggle held its own bool and the second toggle stayed
5+
stale when the first one flipped the theme. */
6+
public class ThemeState
7+
{
8+
public bool IsDark { get; private set; }
9+
public bool IsInitialized { get; private set; }
10+
public event Action? OnChange;
11+
12+
// Called once from the first ThemeToggle that hydrates — reads the actual
13+
// <html> class the pre-Blazor head script set. Later toggles skip re-init.
14+
public void Init(bool isDark)
15+
{
16+
if (IsInitialized) return;
17+
IsDark = isDark;
18+
IsInitialized = true;
19+
OnChange?.Invoke();
20+
}
21+
22+
public void Set(bool isDark)
23+
{
24+
if (IsDark == isDark) return;
25+
IsDark = isDark;
26+
OnChange?.Invoke();
27+
}
28+
29+
public void Toggle() => Set(!IsDark);
30+
}
Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,44 @@
11
@inject IJSRuntime JS
2+
@inject ThemeState Theme
3+
@implements IDisposable
24

3-
<button type="button" class="theme-toggle" @onclick="Toggle" aria-label="Toggle theme">
4-
@if (_isDark)
5+
<button type="button" class="theme-toggle" @onclick="OnClick" aria-label="Toggle theme">
6+
@if (Theme.IsDark)
57
{
6-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>
8+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
79
}
810
else
911
{
10-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
12+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>
1113
}
1214
</button>
1315

1416
@code {
15-
private bool _isDark;
17+
protected override void OnInitialized() => Theme.OnChange += StateHasChanged;
1618

1719
protected override async Task OnAfterRenderAsync(bool firstRender)
1820
{
19-
if (!firstRender) return;
21+
if (!firstRender || Theme.IsInitialized) return;
2022
try
2123
{
22-
var current = await JS.InvokeAsync<string>("eval", "document.documentElement.classList.contains('dark') ? 'dark' : 'light'");
23-
_isDark = current == "dark";
24-
StateHasChanged();
24+
var current = await JS.InvokeAsync<string>("eval",
25+
"document.documentElement.classList.contains('dark') ? 'dark' : 'light'");
26+
Theme.Init(current == "dark");
2527
}
2628
catch { }
2729
}
2830

29-
private async Task Toggle()
31+
private async Task OnClick()
3032
{
31-
_isDark = !_isDark;
33+
Theme.Toggle();
3234
try
3335
{
34-
await JS.InvokeVoidAsync("eval", _isDark
36+
await JS.InvokeVoidAsync("eval", Theme.IsDark
3537
? "document.documentElement.classList.add('dark'); try { localStorage.setItem('shelldocs-theme','dark'); } catch{}"
3638
: "document.documentElement.classList.remove('dark'); try { localStorage.setItem('shelldocs-theme','light'); } catch{}");
3739
}
3840
catch { }
3941
}
42+
43+
public void Dispose() => Theme.OnChange -= StateHasChanged;
4044
}

src/ShellDocs.Components/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public static IServiceCollection AddShellDocs(this IServiceCollection services,
1414
services.AddSingleton(options);
1515

1616
services.AddScoped<MobileNavState>();
17+
services.AddScoped<ThemeState>();
1718
services.AddSingleton<TypeRegistry>(_ => options.BuildTypeRegistry());
1819
services.AddSingleton<MarkdownRenderer>(sp => new MarkdownRenderer(sp.GetRequiredService<TypeRegistry>()));
1920

0 commit comments

Comments
 (0)