Skip to content

Commit 3ef658c

Browse files
committed
ThemeSwitcher component initial setup
1 parent 4f7d631 commit 3ef658c

File tree

7 files changed

+203
-1
lines changed

7 files changed

+203
-1
lines changed

BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<BlazorBootstrapLayout>
55
<HeaderSection>
6-
6+
<ThemeSwitcher />
77
</HeaderSection>
88
<SidebarSection>
99
<Sidebar2 Href="/"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@namespace BlazorBootstrap
2+
@inherits BlazorBootstrapComponentBase
3+
4+
<button class="btn btn-link nav-link py-2 px-0 px-lg-2 dropdown-toggle d-flex align-items-center" id="bd-theme" type="button" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static" aria-label="Toggle theme (light)">
5+
<span class="blazorbootstrap-theme-indicator"><i class="bi bi-sun-fill"></i></span>
6+
<span class="d-lg-none ms-2" id="bd-theme-text">Toggle theme</span>
7+
</button>
8+
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
9+
<li class="blazorbootstrap-theme-item px-1">
10+
<button type="button" class="dropdown-item d-flex align-items-center active rounded" data-bs-theme-value="light" aria-pressed="true" @onclick="SetLightTheme">
11+
<i class="bi bi-sun-fill me-2"></i>Light<i class="bi bi-check2 ms-auto"></i>
12+
</button>
13+
</li>
14+
<li class="blazorbootstrap-theme-item px-1">
15+
<button type="button" class="dropdown-item d-flex align-items-center rounded" data-bs-theme-value="dark" aria-pressed="false" @onclick="SetDarkTheme">
16+
<i class="bi bi-moon-stars-fill me-2"></i>Dark<i class="bi bi-check2 d-none ms-auto"></i>
17+
</button>
18+
</li>
19+
<li class="blazorbootstrap-theme-item px-1">
20+
<button type="button" class="dropdown-item d-flex align-items-center rounded" data-bs-theme-value="auto" aria-pressed="false" @onclick="SetAutoTheme">
21+
<i class="bi bi-circle-half me-2"></i>Auto<i class="bi bi-check2 d-none ms-auto"></i>
22+
</button>
23+
</li>
24+
</ul>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace BlazorBootstrap;
2+
3+
public partial class ThemeSwitcher : BlazorBootstrapComponentBase
4+
{
5+
#region Methods
6+
7+
protected override async Task OnAfterRenderAsync(bool firstRender)
8+
{
9+
if (firstRender)
10+
await ThemeSwitcherJsInterop.InitializeAsync();
11+
12+
await base.OnAfterRenderAsync(firstRender);
13+
}
14+
15+
internal Task SetAutoTheme() => ThemeSwitcherJsInterop.SetAutoThemeAsync();
16+
17+
internal Task SetDarkTheme() => ThemeSwitcherJsInterop.SetDarkThemeAsync();
18+
19+
internal Task SetLightTheme() => ThemeSwitcherJsInterop.SetLightThemeAsync();
20+
21+
#endregion
22+
23+
#region Properties, Indexers
24+
25+
[Inject] private ThemeSwitcherJsInterop ThemeSwitcherJsInterop { get; set; } = default!;
26+
27+
#endregion
28+
}

blazorbootstrap/Components/ThemeSwitcher/ThemeSwitcher.razor.css

Whitespace-only changes.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
namespace BlazorBootstrap;
2+
3+
public class ThemeSwitcherJsInterop : IAsyncDisposable
4+
{
5+
#region Fields and Constants
6+
7+
private readonly Lazy<Task<IJSObjectReference>> moduleTask;
8+
9+
#endregion
10+
11+
#region Constructors
12+
13+
public ThemeSwitcherJsInterop(IJSRuntime jsRuntime)
14+
{
15+
moduleTask = new Lazy<Task<IJSObjectReference>>(() => jsRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/Blazor.Bootstrap/blazor.bootstrap.theme-switcher.js").AsTask());
16+
}
17+
18+
#endregion
19+
20+
#region Methods
21+
22+
public async ValueTask DisposeAsync()
23+
{
24+
if (moduleTask.IsValueCreated)
25+
{
26+
var module = await moduleTask.Value;
27+
await module.DisposeAsync();
28+
}
29+
}
30+
31+
public async Task InitializeAsync()
32+
{
33+
var module = await moduleTask.Value;
34+
await module.InvokeVoidAsync("initializeTheme");
35+
}
36+
37+
internal Task SetAutoThemeAsync() => SetThemeAsync("system");
38+
39+
internal Task SetDarkThemeAsync() => SetThemeAsync("dark");
40+
41+
internal Task SetLightThemeAsync() => SetThemeAsync("light");
42+
43+
internal async Task SetThemeAsync(string themeName)
44+
{
45+
var module = await moduleTask.Value;
46+
await module.InvokeVoidAsync("setTheme", themeName);
47+
}
48+
49+
#endregion
50+
}

blazorbootstrap/Config.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static IServiceCollection AddBlazorBootstrap(this IServiceCollection serv
2121

2222
services.AddScoped<PdfViewerJsInterop>();
2323
services.AddScoped<SortableListJsInterop>();
24+
services.AddScoped<ThemeSwitcherJsInterop>();
2425

2526
return services;
2627
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// THEMES
2+
const STORAGE_KEY = "blazorbootstrap-theme";
3+
const DEFAULT_THEME = "light";
4+
const SYSTEM_THEME = "system";
5+
6+
const state = {
7+
chosenTheme: SYSTEM_THEME, // light|dark|system
8+
appliedTheme: DEFAULT_THEME // light|dark
9+
};
10+
11+
const showActiveTheme = () => {
12+
let $themeIndicator = document.querySelector(".blazorbootstrap-theme-indicator>i");
13+
if ($themeIndicator) {
14+
if (state.appliedTheme === "light") {
15+
$themeIndicator.className = "bi bi-sun-fill";
16+
} else if (state.appliedTheme === "dark") {
17+
$themeIndicator.className = "bi bi-moon-stars-fill";
18+
} else {
19+
$themeIndicator.className = "bi bi-circle-half";
20+
}
21+
}
22+
23+
let $themeSwitchers = document.querySelectorAll(".blazorbootstrap-theme-item>button");
24+
if ($themeSwitchers) {
25+
$themeSwitchers.forEach((el) => {
26+
const bsThemeValue = el.dataset.bsThemeValue;
27+
const iEl = el.querySelector(".bi.bi-check2");
28+
if (state.chosenTheme === bsThemeValue) {
29+
el.classList.add("active");
30+
if (iEl)
31+
iEl.classList.remove("d-none");
32+
} else {
33+
el.classList.remove("active");
34+
if (iEl)
35+
iEl.classList.add("d-none");
36+
}
37+
});
38+
}
39+
};
40+
41+
export function setTheme(theme, save = true) {
42+
state.chosenTheme = theme;
43+
state.appliedTheme = theme;
44+
45+
if (theme === SYSTEM_THEME) {
46+
state.appliedTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
47+
}
48+
49+
document.documentElement.setAttribute("data-bs-theme", state.appliedTheme);
50+
if (save) {
51+
window.localStorage.setItem(STORAGE_KEY, state.chosenTheme);
52+
}
53+
showActiveTheme();
54+
updateDemoCodeThemeCss(state.appliedTheme);
55+
};
56+
57+
export function initializeTheme() {
58+
const localTheme = window.localStorage.getItem(STORAGE_KEY);
59+
if (localTheme) {
60+
setTheme(localTheme, false);
61+
} else {
62+
setTheme(SYSTEM_THEME);
63+
}
64+
65+
// register events
66+
window
67+
.matchMedia("(prefers-color-scheme: dark)")
68+
.addEventListener("change", (event) => {
69+
const theme = event.matches ? "dark" : "light";
70+
setTheme(theme);
71+
});
72+
}
73+
74+
export function updateDemoCodeThemeCss(theme) {
75+
if (theme === "dark") {
76+
let prismThemeLightLinkEl = document.getElementById('prismThemeLightLink');
77+
if (prismThemeLightLinkEl)
78+
prismThemeLightLinkEl?.remove();
79+
80+
let prismThemeDarkLinkEl = document.createElement("link");
81+
prismThemeDarkLinkEl.setAttribute("rel", "stylesheet");
82+
prismThemeDarkLinkEl.setAttribute("href", "/_content/BlazorBootstrap.Demo.RCL/css/prism-vsc-dark-plus.min.css");
83+
prismThemeDarkLinkEl.setAttribute("id", "prismThemeDarkLink");
84+
85+
document.head.append(prismThemeDarkLinkEl);
86+
}
87+
else if (theme === "light") {
88+
let prismThemeDarkLinkEl = document.getElementById('prismThemeDarkLink');
89+
if (prismThemeDarkLinkEl)
90+
prismThemeDarkLinkEl?.remove();
91+
92+
let prismThemeLightLinkEl = document.createElement("link");
93+
prismThemeLightLinkEl.setAttribute("rel", "stylesheet");
94+
prismThemeLightLinkEl.setAttribute("href", "/_content/BlazorBootstrap.Demo.RCL/css/prism-vs.min.css");
95+
prismThemeLightLinkEl.setAttribute("id", "prismThemeLightLink");
96+
97+
document.head.append(prismThemeLightLinkEl);
98+
}
99+
}

0 commit comments

Comments
 (0)