Skip to content

Commit a59b00e

Browse files
committed
feat: Add Todo List and User Profile pages with state management
- Implemented Todo List page demonstrating immutable collections and state transformations. - Added User Profile page showcasing async actions and loading states. - Created ShoppingCartState and ThemeState for shopping cart and theme management. - Enhanced Program.cs to register new stores with persistence and diagnostics. - Updated StoreServiceExtensions to support async state methods. - Introduced persistence middleware for automatic state loading in Blazor WebAssembly.
1 parent 59f048c commit a59b00e

19 files changed

Lines changed: 2023 additions & 26 deletions
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
@using EasyAppDev.Blazor.Store.Sample.State
2+
@inherits SelectorStoreComponent<ThemeState>
3+
4+
<div class="form-check form-switch mb-2">
5+
<input class="form-check-input"
6+
type="checkbox"
7+
id="animations"
8+
checked="@SelectedSettings.Animations"
9+
@onchange="@(() => Update(s => s.ToggleAnimations()))" />
10+
<label class="form-check-label" for="animations">
11+
Enable Animations
12+
</label>
13+
</div>
14+
15+
<div class="form-check form-switch mb-2">
16+
<input class="form-check-input"
17+
type="checkbox"
18+
id="reducedMotion"
19+
checked="@SelectedSettings.ReducedMotion"
20+
@onchange="@(() => Update(s => s.ToggleReducedMotion()))" />
21+
<label class="form-check-label" for="reducedMotion">
22+
Reduced Motion
23+
</label>
24+
</div>
25+
26+
<div class="form-check form-switch mb-2">
27+
<input class="form-check-input"
28+
type="checkbox"
29+
id="highContrast"
30+
checked="@SelectedSettings.HighContrast"
31+
@onchange="@(() => Update(s => s.ToggleHighContrast()))" />
32+
<label class="form-check-label" for="highContrast">
33+
High Contrast Mode
34+
</label>
35+
</div>
36+
37+
<small class="text-muted d-block mt-2">
38+
Component renders: @_renderCount (only on accessibility changes)
39+
</small>
40+
41+
@code {
42+
private int _renderCount = 0;
43+
44+
// Custom selector type for multiple accessibility properties
45+
public record AccessibilitySettings(bool Animations, bool ReducedMotion, bool HighContrast);
46+
47+
private AccessibilitySettings SelectedSettings => (AccessibilitySettings)SelectState(Store.GetState());
48+
49+
protected override object SelectState(ThemeState state)
50+
=> new AccessibilitySettings(state.Animations, state.ReducedMotion, state.HighContrast);
51+
52+
protected override void OnAfterRender(bool firstRender)
53+
{
54+
base.OnAfterRender(firstRender);
55+
_renderCount++;
56+
}
57+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@using EasyAppDev.Blazor.Store.Sample.State
2+
@inherits SelectorStoreComponent<ThemeState>
3+
4+
<div class="d-flex align-items-center gap-2">
5+
<button class="btn btn-outline-secondary"
6+
@onclick="@(() => Update(s => s.DecreaseFontSize()))"
7+
disabled="@(SelectedFontSize <= 12)">
8+
A-
9+
</button>
10+
<div class="flex-grow-1 text-center">
11+
<strong style="font-size: @(SelectedFontSize)px;">@SelectedFontSize px</strong>
12+
</div>
13+
<button class="btn btn-outline-secondary"
14+
@onclick="@(() => Update(s => s.IncreaseFontSize()))"
15+
disabled="@(SelectedFontSize >= 24)">
16+
A+
17+
</button>
18+
</div>
19+
<input type="range"
20+
class="form-range mt-2"
21+
min="12"
22+
max="24"
23+
step="2"
24+
value="@SelectedFontSize"
25+
@oninput="@((e) => Update(s => s.SetFontSize(int.Parse(e.Value?.ToString() ?? "16"))))" />
26+
<small class="text-muted d-block mt-2">
27+
Component renders: @_renderCount (only on font size change)
28+
</small>
29+
30+
@code {
31+
private int _renderCount = 0;
32+
private int SelectedFontSize => (int)SelectState(Store.GetState());
33+
34+
protected override object SelectState(ThemeState state) => state.FontSize;
35+
36+
protected override void OnAfterRender(bool firstRender)
37+
{
38+
base.OnAfterRender(firstRender);
39+
_renderCount++;
40+
}
41+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
@using EasyAppDev.Blazor.Store.Sample.State
2+
@inherits StoreComponent<ThemeState>
3+
4+
<div class="alert alert-secondary mb-0">
5+
<h6>This component uses full StoreComponent</h6>
6+
<p class="mb-2">It re-renders on <strong>ANY</strong> state change:</p>
7+
<ul class="mb-2">
8+
<li>Theme: @State.CurrentTheme</li>
9+
<li>Font Size: @State.FontSize px</li>
10+
<li>Animations: @(State.Animations ? "" : "")</li>
11+
<li>Reduced Motion: @(State.ReducedMotion ? "" : "")</li>
12+
<li>High Contrast: @(State.HighContrast ? "" : "")</li>
13+
</ul>
14+
@if (State.LastChanged.HasValue)
15+
{
16+
<small class="text-muted">
17+
Last changed: @State.LastChanged.Value.ToString("h:mm:ss.fff tt")
18+
</small>
19+
}
20+
<div class="mt-2">
21+
<strong class="text-danger">
22+
Render count: @_renderCount
23+
</strong>
24+
<small class="d-block text-muted">
25+
(This increases on ANY change - compare with other components!)
26+
</small>
27+
</div>
28+
</div>
29+
30+
@code {
31+
private int _renderCount = 0;
32+
33+
protected override void OnAfterRender(bool firstRender)
34+
{
35+
base.OnAfterRender(firstRender);
36+
_renderCount++;
37+
}
38+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
@using EasyAppDev.Blazor.Store.Sample.State
2+
@inherits StoreComponent<ThemeState>
3+
4+
<div class="@State.ThemeClass p-4 rounded" style="@State.FontSizeStyle">
5+
<h5>Theme Preview</h5>
6+
<p>This is how your content will look with the current settings.</p>
7+
<p class="mb-0">
8+
<strong>Current theme:</strong> @State.CurrentTheme<br />
9+
<strong>Font size:</strong> @State.FontSize px<br />
10+
<strong>Animations:</strong> @(State.AnimationsDisabled ? "Disabled" : "Enabled")
11+
</p>
12+
</div>
13+
<small class="text-muted d-block mt-2">
14+
Component renders: @_renderCount
15+
</small>
16+
17+
<style>
18+
.theme-light {
19+
background-color: #ffffff;
20+
color: #000000;
21+
border: 2px solid #dee2e6;
22+
}
23+
24+
.theme-dark {
25+
background-color: #212529;
26+
color: #ffffff;
27+
border: 2px solid #495057;
28+
}
29+
30+
.theme-auto {
31+
background: linear-gradient(135deg, #ffffff 0%, #ffffff 50%, #212529 50%, #212529 100%);
32+
color: #000000;
33+
border: 2px solid #6c757d;
34+
}
35+
</style>
36+
37+
@code {
38+
private int _renderCount = 0;
39+
40+
protected override void OnAfterRender(bool firstRender)
41+
{
42+
base.OnAfterRender(firstRender);
43+
_renderCount++;
44+
}
45+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@using EasyAppDev.Blazor.Store.Sample.State
2+
@inherits SelectorStoreComponent<ThemeState>
3+
4+
<div class="btn-group w-100">
5+
<button class="btn @(SelectedTheme == Theme.Light ? "btn-primary" : "btn-outline-primary")"
6+
@onclick="@(() => Update(s => s.SetTheme(Theme.Light)))">
7+
☀️ Light
8+
</button>
9+
<button class="btn @(SelectedTheme == Theme.Dark ? "btn-primary" : "btn-outline-primary")"
10+
@onclick="@(() => Update(s => s.SetTheme(Theme.Dark)))">
11+
🌙 Dark
12+
</button>
13+
<button class="btn @(SelectedTheme == Theme.Auto ? "btn-primary" : "btn-outline-primary")"
14+
@onclick="@(() => Update(s => s.SetTheme(Theme.Auto)))">
15+
🔄 Auto
16+
</button>
17+
</div>
18+
<small class="text-muted d-block mt-2">
19+
Component renders: @_renderCount (only on theme change)
20+
</small>
21+
22+
@code {
23+
private int _renderCount = 0;
24+
private Theme SelectedTheme => (Theme)SelectState(Store.GetState());
25+
26+
protected override object SelectState(ThemeState state) => state.CurrentTheme;
27+
28+
protected override void OnAfterRender(bool firstRender)
29+
{
30+
base.OnAfterRender(firstRender);
31+
_renderCount++;
32+
}
33+
}

samples/EasyAppDev.Blazor.Store.Sample/Layout/NavMenu.razor

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,47 @@
1111
<nav class="flex-column">
1212
<div class="nav-item px-3">
1313
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
14-
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
14+
<span aria-hidden="true">🏠</span> Home
1515
</NavLink>
1616
</div>
17+
18+
<div class="nav-item px-3 mt-3">
19+
<small class="text-muted ps-2">EXAMPLES</small>
20+
</div>
21+
1722
<div class="nav-item px-3">
1823
<NavLink class="nav-link" href="counter">
19-
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
24+
<span aria-hidden="true">🔢</span> Counter
25+
</NavLink>
26+
</div>
27+
<div class="nav-item px-3">
28+
<NavLink class="nav-link" href="todos">
29+
<span aria-hidden="true">📝</span> Todo List
2030
</NavLink>
2131
</div>
2232
<div class="nav-item px-3">
23-
<NavLink class="nav-link" href="weather">
24-
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
33+
<NavLink class="nav-link" href="profile">
34+
<span aria-hidden="true">👤</span> User Profile
2535
</NavLink>
2636
</div>
37+
<div class="nav-item px-3">
38+
<NavLink class="nav-link" href="cart">
39+
<span aria-hidden="true">🛒</span> Shopping Cart
40+
</NavLink>
41+
</div>
42+
<div class="nav-item px-3">
43+
<NavLink class="nav-link" href="theme">
44+
<span aria-hidden="true">🎨</span> Theme Settings
45+
</NavLink>
46+
</div>
47+
48+
<div class="nav-item px-3 mt-3">
49+
<small class="text-muted ps-2">TOOLS</small>
50+
</div>
51+
2752
<div class="nav-item px-3">
2853
<NavLink class="nav-link" href="diagnostics">
29-
<span class="bi bi-speedometer2" aria-hidden="true"></span> Diagnostics
54+
<span aria-hidden="true">🔍</span> Diagnostics
3055
</NavLink>
3156
</div>
3257
</nav>

0 commit comments

Comments
 (0)