Skip to content

Commit 8f1853c

Browse files
committed
Add comprehensive Blazor documentation for MudBlazor setup, server-side tables, and component patterns
- Introduced setup-layout.md for MudBlazor configuration and theme management. - Added tables-data.md detailing server-side data handling with MudTable. - Created SKILL.md outlining Blazor component development conventions and best practices. - Documented authorization and tenant-aware components in auth-tenant.md. - Established component anatomy guidelines in component-anatomy.md. - Explained forms, tables, and dialogs usage in forms-dialogs.md. - Compiled common pitfalls in Blazor components in pitfalls.md. - Described reactive data loading with ReactiveQuery<T> in reactive-query.md.
1 parent c7dc333 commit 8f1853c

13 files changed

Lines changed: 2148 additions & 0 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
name: blazor-mudblazor
3+
description: Use MudBlazor components correctly in Blazor applications — covering setup (providers, theme, dark-mode), layout shell (MudLayout/MudAppBar/MudDrawer), data tables (MudTable with ServerData, server-side paging, search), forms and inputs (MudForm, MudTextField, MudAutocomplete, MudSelect, MudNumericField, MudChipSet), dialogs (MudDialog, IDialogService, IMudDialogInstance), feedback (ISnackbar, MudAlert, MudSkeleton, MudProgressCircular), navigation menus (MudMenu, MudIconButton), and layout primitives (MudGrid, MudStack, MudPaper, MudContainer, MudText, Icons.Material). Trigger whenever the user writes, reviews, or asks about MudBlazor components, MudForm validation, MudTable server-side data, MudDialog patterns, MudAutocomplete, theming, dark mode, snackbar notifications, Material icons, or any UI component work in a Blazor project using MudBlazor — even if they don't explicitly say "MudBlazor". Always prefer this skill over guessing; component APIs, the IMudDialogInstance cascading parameter, and the IsDarkMode wiring have non-obvious failure modes.
4+
---
5+
6+
# MudBlazor — Component Patterns
7+
8+
MudBlazor is the UI component library for BookStore's Blazor frontend. Components follow Material Design and are built for interactive Blazor Server (`@rendermode InteractiveServer`).
9+
10+
## Quick Reference
11+
12+
| Topic | Reference |
13+
|---|---|
14+
| Providers, theme, dark mode, MudLayout shell | `references/setup-layout.md` |
15+
| MudTable (server-side paging, search, sort) | `references/tables-data.md` |
16+
| MudForm, inputs, MudAutocomplete, MudSelect | `references/forms-inputs.md` |
17+
| MudDialog open/close/parameters, ISnackbar, MudAlert | `references/dialogs-feedback.md` |
18+
| MudGrid, MudStack, MudPaper, MudText, Icons | `references/layout-primitives.md` |
19+
20+
Related skills: `../csharp-blazor/SKILL.md` (ReactiveQuery, SSE subscriptions, full page skeleton), `../bunit/SKILL.md` (testing), `../etag/SKILL.md` (optimistic concurrency in dialogs).
21+
22+
---
23+
24+
## Essential Rules
25+
26+
- `MudThemeProvider`, `MudPopoverProvider`, `MudDialogProvider`, `MudSnackbarProvider` go in `Routes.razor` (root), **not** in `MainLayout.razor`.
27+
- Dialog components do **not** declare `@rendermode` — they inherit it from the parent page.
28+
- Dialog code-behind uses `[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;`**not** `MudDialogInstance`.
29+
- Always pass `DialogParameters<TDialog>` with a typed lambda: `{ x => x.MyParam, value }` — this is compile-time safe.
30+
- `MudForm` owns validation state (`@bind-IsValid`). Don't mix with `EditContext`/`DataAnnotations` unless you need both.
31+
- All text components use `MudText` with a `Typo` enum value — never raw HTML `<h1>`/`<p>` tags inside MudBlazor layouts.
32+
- Icons come from `Icons.Material.Filled.*`, `Icons.Material.Outlined.*`, or `Icons.Material.TwoTone.*`.
33+
34+
---
35+
36+
## Minimal Admin Page Pattern
37+
38+
Every admin management page follows this shell (details in `../csharp-blazor/SKILL.md`):
39+
40+
```razor
41+
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8 mb-12">
42+
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-6">
43+
<MudText Typo="Typo.h4">Widget Management</MudText>
44+
<MudSpacer/>
45+
<MudButton Variant="Variant.Filled" Color="Color.Primary"
46+
StartIcon="@Icons.Material.Filled.Add"
47+
OnClick="OpenAddDialog">
48+
Add Widget
49+
</MudButton>
50+
</MudStack>
51+
52+
@* MudTable or MudDataGrid here — see references/tables-data.md *@
53+
</MudContainer>
54+
```
55+
56+
---
57+
58+
## Common Gotchas
59+
60+
- `MudTable<T>` search: use a **property** with a setter that calls `_table.ReloadServerData()`, not field + `@bind-Value`. See `references/tables-data.md`.
61+
- `MudAutocomplete<T>` needs both `SearchFunc` and `ToStringFunc` for object types.
62+
- `ISnackbar.Add(message, Severity.X)` — inject `ISnackbar Snackbar` via `@inject`.
63+
- `MudDialog` requires `MudDialogProvider` to be registered in `Routes.razor`.
64+
- Closing a dialog: `MudDialog.Close(DialogResult.Ok(true))` or `MudDialog.Cancel()`.
65+
- `MudChipSet<T>` — bind chips via `Value` and `Text`; use `OnClose` to remove an item from a collection.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"skill_name": "csharp-blazor-mudblazor",
3+
"evals": [
4+
{
5+
"id": 1,
6+
"prompt": "Add a new admin page to manage discount codes. It needs a MudTable with server-side paging, a search box, and a toolbar button to open a 'Create Discount Code' dialog. The dialog has fields for Code (string), Discount Percentage (decimal 0–100), and an Expiry Date picker. Use the BookStore patterns.",
7+
"expected_output": "A DiscountCodeManagement.razor page with MudTable + ServerData, search string as a property, SSE listener, and a CreateDiscountCodeDialog.razor with MudForm, MudTextField, MudNumericField, and MudDatePicker. MudDialog uses IMudDialogInstance cascading parameter. ISnackbar for feedback.",
8+
"files": []
9+
},
10+
{
11+
"id": 2,
12+
"prompt": "I need to add a dark/light mode toggle button to the navbar. When clicked it should cycle through System → Light → Dark modes. I already have a ThemeService injected in MainLayout. Show me the component.",
13+
"expected_output": "A small MudMenu or MudIconButton component that injects ThemeService, registers/unregisters OnChange event, and calls SetThemeModeAsync. Uses Icons.Material.Filled.LightMode / DarkMode / SettingsBrightness. Implements IDisposable.",
14+
"files": []
15+
},
16+
{
17+
"id": 3,
18+
"prompt": "Show me how to create the 'Edit Author' dialog. It receives an AdminAuthorDto and an ETag as parameters, pre-populates a MudTextField with the author's name, calls UpdateAuthorAsync on submit, and handles concurrency conflict errors by showing a warning snackbar.",
19+
"expected_output": "UpdateAuthorDialog.razor without @rendermode, with [CascadingParameter] IMudDialogInstance, [Parameter] properties for AdminAuthorDto and ETag, OnParametersSet to populate the model, MudForm with MudTextField, Submit method calling WidgetsClient (or AuthorsClient), error handling for ConcurrencyConflict.",
20+
"files": []
21+
},
22+
{
23+
"id": 4,
24+
"prompt": "I want to display a responsive book catalog grid — 4 columns on desktop, 2 on tablet, 1 on mobile. Each card shows the book title, author name, price, and an 'Add to Cart' button. Use MudGrid and MudCard.",
25+
"expected_output": "MudGrid with MudItem xs=12 sm=6 md=3 breakpoints. Each MudItem contains a MudCard with MudCardContent (MudText for title, author, price) and MudCardActions with a MudButton. Follows layout-primitives patterns.",
26+
"files": []
27+
}
28+
]
29+
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Dialogs and User Feedback
2+
3+
## Opening a Dialog (parent / page side)
4+
5+
```csharp
6+
@inject IDialogService DialogService
7+
8+
private async Task OpenAddDialog()
9+
{
10+
var options = new DialogOptions
11+
{
12+
CloseOnEscapeKey = true,
13+
MaxWidth = MaxWidth.Small,
14+
FullWidth = true
15+
};
16+
var dialog = await DialogService.ShowAsync<CreateWidgetDialog>("New Widget", options);
17+
var result = await dialog.Result;
18+
19+
if (result is { Canceled: false })
20+
await _table.ReloadServerData();
21+
}
22+
```
23+
24+
### Passing Parameters to a Dialog
25+
26+
Use the strongly-typed `DialogParameters<TDialog>` overload — compile-time safe:
27+
28+
```csharp
29+
private async Task EditWidget(AdminWidgetDto widget)
30+
{
31+
var parameters = new DialogParameters<UpdateWidgetDialog>
32+
{
33+
{ x => x.Widget, widget },
34+
{ x => x.ETag, widget.ETag }
35+
};
36+
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
37+
var dialog = await DialogService.ShowAsync<UpdateWidgetDialog>("Edit Widget", parameters, options);
38+
var result = await dialog.Result;
39+
40+
if (result is { Canceled: false })
41+
await _table.ReloadServerData();
42+
}
43+
```
44+
45+
---
46+
47+
## Dialog Component
48+
49+
Dialog components **do not** declare `@rendermode` — they inherit it from their parent page. The cascading parameter is `IMudDialogInstance` (not `MudDialogInstance`).
50+
51+
```razor
52+
@* CreateWidgetDialog.razor — NO @rendermode directive *@
53+
@inject IWidgetsClient WidgetsClient
54+
@inject ISnackbar Snackbar
55+
56+
<MudDialog>
57+
<TitleContent>
58+
<MudText Typo="Typo.h6">
59+
<MudIcon Icon="@Icons.Material.Filled.Widgets" Class="mr-3 mb-n1"/>
60+
New Widget
61+
</MudText>
62+
</TitleContent>
63+
<DialogContent>
64+
<MudForm @ref="_form" @bind-IsValid="_success">
65+
<MudTextField T="string"
66+
Label="Name"
67+
@bind-Value="_model.Name"
68+
Required="true"
69+
RequiredError="Name is required!"
70+
Variant="Variant.Outlined"
71+
Immediate="true"/>
72+
</MudForm>
73+
</DialogContent>
74+
<DialogActions>
75+
<MudButton OnClick="Cancel">Cancel</MudButton>
76+
<MudButton Color="Color.Primary"
77+
Variant="Variant.Filled"
78+
OnClick="Submit"
79+
Disabled="@(!_success)">Create</MudButton>
80+
</DialogActions>
81+
</MudDialog>
82+
83+
@code {
84+
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
85+
86+
private MudForm _form = null!;
87+
private bool _success;
88+
private CreateWidgetRequest _model = new("");
89+
90+
private void Cancel() => MudDialog.Cancel();
91+
92+
private async Task Submit()
93+
{
94+
await _form.Validate();
95+
if (!_success) return;
96+
97+
var result = await WidgetsClient.CreateWidgetAsync(_model);
98+
if (result.IsSuccess)
99+
MudDialog.Close(DialogResult.Ok(true));
100+
else
101+
Snackbar.Add(result.Error.Message, Severity.Error);
102+
}
103+
}
104+
```
105+
106+
### Dialog with pre-populated model (edit)
107+
108+
```razor
109+
@code {
110+
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
111+
[Parameter] public AdminWidgetDto Widget { get; set; } = null!;
112+
[Parameter] public string ETag { get; set; } = "";
113+
114+
protected override void OnParametersSet()
115+
{
116+
_model = new UpdateWidgetRequest(Widget.Name, Widget.Description);
117+
}
118+
}
119+
```
120+
121+
---
122+
123+
## Confirmation Dialog
124+
125+
For destructive actions, use `IDialogService` with a simple confirm dialog:
126+
127+
```csharp
128+
private async Task DeleteWidget(Guid id, string etag)
129+
{
130+
var confirm = await DialogService.ShowMessageBox(
131+
"Delete Widget",
132+
"Are you sure you want to delete this widget? This cannot be undone.",
133+
yesText: "Delete", cancelText: "Cancel");
134+
135+
if (confirm != true) return;
136+
137+
var result = await WidgetsClient.DeleteWidgetAsync(id, etag);
138+
if (result.IsSuccess)
139+
Snackbar.Add("Widget deleted.", Severity.Success);
140+
else if (result.Error.Code == ErrorCode.ConcurrencyConflict)
141+
Snackbar.Add("Another user modified this item. Please refresh.", Severity.Warning);
142+
else
143+
Snackbar.Add(result.Error.Message, Severity.Error);
144+
145+
await _table.ReloadServerData();
146+
}
147+
```
148+
149+
---
150+
151+
## ISnackbar — Toast Notifications
152+
153+
Inject once per component: `@inject ISnackbar Snackbar`
154+
155+
```csharp
156+
Snackbar.Add("Operation successful!", Severity.Success);
157+
Snackbar.Add("Something went wrong.", Severity.Error);
158+
Snackbar.Add("Check your input.", Severity.Warning);
159+
Snackbar.Add("Refreshing in background...", Severity.Info);
160+
```
161+
162+
---
163+
164+
## MudAlert — Inline Alerts
165+
166+
```razor
167+
<MudAlert Severity="Severity.Warning" Dense="true">
168+
At least one author is required.
169+
</MudAlert>
170+
171+
<MudAlert Severity="Severity.Error" Variant="Variant.Filled">
172+
@_errorMessage
173+
</MudAlert>
174+
```
175+
176+
Use `Dense="true"` inside dialogs and cards; `Variant="Variant.Filled"` for high-emphasis alerts.
177+
178+
---
179+
180+
## MudSkeleton — Loading States
181+
182+
Show while initial data is loading (before `ReactiveQuery<T>` resolves):
183+
184+
```razor
185+
@if (_query?.IsLoading == true && _query.Data == null)
186+
{
187+
<MudStack>
188+
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="40px"/>
189+
<MudSkeleton Width="60%"/>
190+
<MudSkeleton Width="80%"/>
191+
</MudStack>
192+
}
193+
```
194+
195+
---
196+
197+
## MudProgressCircular — Spinner
198+
199+
Inline spinner (e.g., in AppBar while tenant loads):
200+
201+
```razor
202+
@if (TenantService.IsLoading)
203+
{
204+
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2"/>
205+
}
206+
```

0 commit comments

Comments
 (0)