autoworkflow 3.1.5 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +26 -0
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/post-edit.sh +190 -17
- package/.claude/hooks/pre-edit.sh +221 -0
- package/.claude/hooks/session-check.sh +90 -0
- package/.claude/settings.json +56 -6
- package/.claude/settings.local.json +5 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +163 -52
- package/package.json +1 -1
- package/system/triggers.md +256 -17
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# Blazor Skill
|
|
2
|
+
|
|
3
|
+
## Component with Parameters
|
|
4
|
+
\`\`\`razor
|
|
5
|
+
@page "/users/{Id}"
|
|
6
|
+
@inject IUserService UserService
|
|
7
|
+
@inject NavigationManager Navigation
|
|
8
|
+
|
|
9
|
+
<PageTitle>User Details</PageTitle>
|
|
10
|
+
|
|
11
|
+
@if (_loading)
|
|
12
|
+
{
|
|
13
|
+
<LoadingSpinner />
|
|
14
|
+
}
|
|
15
|
+
else if (_user is null)
|
|
16
|
+
{
|
|
17
|
+
<div class="alert alert-danger">User not found</div>
|
|
18
|
+
}
|
|
19
|
+
else
|
|
20
|
+
{
|
|
21
|
+
<div class="card">
|
|
22
|
+
<h1>@_user.Name</h1>
|
|
23
|
+
<p>@_user.Email</p>
|
|
24
|
+
<Badge IsActive="@_user.IsActive" />
|
|
25
|
+
<button class="btn btn-primary" @onclick="HandleEdit">Edit</button>
|
|
26
|
+
</div>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@code {
|
|
30
|
+
[Parameter]
|
|
31
|
+
public string Id { get; set; } = null!;
|
|
32
|
+
|
|
33
|
+
[Parameter]
|
|
34
|
+
public EventCallback<User> OnUserUpdated { get; set; }
|
|
35
|
+
|
|
36
|
+
private User? _user;
|
|
37
|
+
private bool _loading = true;
|
|
38
|
+
|
|
39
|
+
protected override async Task OnInitializedAsync()
|
|
40
|
+
{
|
|
41
|
+
await LoadUser();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected override async Task OnParametersSetAsync()
|
|
45
|
+
{
|
|
46
|
+
// Called when parameters change (e.g., route changes)
|
|
47
|
+
await LoadUser();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private async Task LoadUser()
|
|
51
|
+
{
|
|
52
|
+
_loading = true;
|
|
53
|
+
try
|
|
54
|
+
{
|
|
55
|
+
_user = await UserService.GetByIdAsync(Id);
|
|
56
|
+
}
|
|
57
|
+
finally
|
|
58
|
+
{
|
|
59
|
+
_loading = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private void HandleEdit()
|
|
64
|
+
{
|
|
65
|
+
Navigation.NavigateTo($"/users/{Id}/edit");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
## Forms with Validation
|
|
71
|
+
\`\`\`razor
|
|
72
|
+
@page "/users/create"
|
|
73
|
+
@inject IUserService UserService
|
|
74
|
+
@inject NavigationManager Navigation
|
|
75
|
+
|
|
76
|
+
<h1>Create User</h1>
|
|
77
|
+
|
|
78
|
+
<EditForm Model="@_model" OnValidSubmit="HandleSubmit">
|
|
79
|
+
<DataAnnotationsValidator />
|
|
80
|
+
<ValidationSummary class="text-danger" />
|
|
81
|
+
|
|
82
|
+
<div class="mb-3">
|
|
83
|
+
<label class="form-label">Email</label>
|
|
84
|
+
<InputText @bind-Value="_model.Email" class="form-control" />
|
|
85
|
+
<ValidationMessage For="@(() => _model.Email)" class="text-danger" />
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="mb-3">
|
|
89
|
+
<label class="form-label">Name</label>
|
|
90
|
+
<InputText @bind-Value="_model.Name" class="form-control" />
|
|
91
|
+
<ValidationMessage For="@(() => _model.Name)" class="text-danger" />
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="mb-3">
|
|
95
|
+
<label class="form-label">Role</label>
|
|
96
|
+
<InputSelect @bind-Value="_model.Role" class="form-select">
|
|
97
|
+
<option value="">Select a role...</option>
|
|
98
|
+
@foreach (var role in _roles)
|
|
99
|
+
{
|
|
100
|
+
<option value="@role">@role</option>
|
|
101
|
+
}
|
|
102
|
+
</InputSelect>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="mb-3">
|
|
106
|
+
<label class="form-label">Active</label>
|
|
107
|
+
<InputCheckbox @bind-Value="_model.IsActive" class="form-check-input" />
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<button type="submit" class="btn btn-primary" disabled="@_submitting">
|
|
111
|
+
@if (_submitting)
|
|
112
|
+
{
|
|
113
|
+
<span class="spinner-border spinner-border-sm"></span>
|
|
114
|
+
}
|
|
115
|
+
Create User
|
|
116
|
+
</button>
|
|
117
|
+
|
|
118
|
+
@if (_error is not null)
|
|
119
|
+
{
|
|
120
|
+
<div class="alert alert-danger mt-3">@_error</div>
|
|
121
|
+
}
|
|
122
|
+
</EditForm>
|
|
123
|
+
|
|
124
|
+
@code {
|
|
125
|
+
private CreateUserModel _model = new();
|
|
126
|
+
private bool _submitting;
|
|
127
|
+
private string? _error;
|
|
128
|
+
private readonly string[] _roles = ["User", "Admin", "Moderator"];
|
|
129
|
+
|
|
130
|
+
private async Task HandleSubmit()
|
|
131
|
+
{
|
|
132
|
+
_submitting = true;
|
|
133
|
+
_error = null;
|
|
134
|
+
|
|
135
|
+
try
|
|
136
|
+
{
|
|
137
|
+
var user = await UserService.CreateAsync(_model);
|
|
138
|
+
Navigation.NavigateTo($"/users/{user.Id}");
|
|
139
|
+
}
|
|
140
|
+
catch (Exception ex)
|
|
141
|
+
{
|
|
142
|
+
_error = ex.Message;
|
|
143
|
+
}
|
|
144
|
+
finally
|
|
145
|
+
{
|
|
146
|
+
_submitting = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public class CreateUserModel
|
|
151
|
+
{
|
|
152
|
+
[Required, EmailAddress, MaxLength(255)]
|
|
153
|
+
public string Email { get; set; } = "";
|
|
154
|
+
|
|
155
|
+
[Required, MinLength(2), MaxLength(100)]
|
|
156
|
+
public string Name { get; set; } = "";
|
|
157
|
+
|
|
158
|
+
[Required]
|
|
159
|
+
public string Role { get; set; } = "";
|
|
160
|
+
|
|
161
|
+
public bool IsActive { get; set; } = true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
\`\`\`
|
|
165
|
+
|
|
166
|
+
## Component Communication
|
|
167
|
+
\`\`\`razor
|
|
168
|
+
// Parent Component
|
|
169
|
+
@page "/users"
|
|
170
|
+
|
|
171
|
+
<UserList Users="_users" OnUserSelected="HandleUserSelected" />
|
|
172
|
+
|
|
173
|
+
@if (_selectedUser is not null)
|
|
174
|
+
{
|
|
175
|
+
<UserDetails User="_selectedUser" OnDelete="HandleDelete" />
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@code {
|
|
179
|
+
private List<User> _users = new();
|
|
180
|
+
private User? _selectedUser;
|
|
181
|
+
|
|
182
|
+
protected override async Task OnInitializedAsync()
|
|
183
|
+
{
|
|
184
|
+
_users = await UserService.GetAllAsync();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private void HandleUserSelected(User user)
|
|
188
|
+
{
|
|
189
|
+
_selectedUser = user;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async Task HandleDelete(User user)
|
|
193
|
+
{
|
|
194
|
+
await UserService.DeleteAsync(user.Id);
|
|
195
|
+
_users.Remove(user);
|
|
196
|
+
_selectedUser = null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Child Component - UserList.razor
|
|
201
|
+
<ul class="list-group">
|
|
202
|
+
@foreach (var user in Users)
|
|
203
|
+
{
|
|
204
|
+
<li class="list-group-item @(user == SelectedUser ? "active" : "")"
|
|
205
|
+
@onclick="() => OnUserSelected.InvokeAsync(user)">
|
|
206
|
+
@user.Name
|
|
207
|
+
</li>
|
|
208
|
+
}
|
|
209
|
+
</ul>
|
|
210
|
+
|
|
211
|
+
@code {
|
|
212
|
+
[Parameter, EditorRequired]
|
|
213
|
+
public List<User> Users { get; set; } = null!;
|
|
214
|
+
|
|
215
|
+
[Parameter]
|
|
216
|
+
public User? SelectedUser { get; set; }
|
|
217
|
+
|
|
218
|
+
[Parameter]
|
|
219
|
+
public EventCallback<User> OnUserSelected { get; set; }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Child Component - UserDetails.razor
|
|
223
|
+
<div class="card">
|
|
224
|
+
<h3>@User.Name</h3>
|
|
225
|
+
<button class="btn btn-danger" @onclick="HandleDelete">Delete</button>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
@code {
|
|
229
|
+
[Parameter, EditorRequired]
|
|
230
|
+
public User User { get; set; } = null!;
|
|
231
|
+
|
|
232
|
+
[Parameter]
|
|
233
|
+
public EventCallback<User> OnDelete { get; set; }
|
|
234
|
+
|
|
235
|
+
private async Task HandleDelete()
|
|
236
|
+
{
|
|
237
|
+
await OnDelete.InvokeAsync(User);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
\`\`\`
|
|
241
|
+
|
|
242
|
+
## State Management with Cascading Values
|
|
243
|
+
\`\`\`razor
|
|
244
|
+
// App.razor or layout
|
|
245
|
+
<CascadingValue Value="_authState">
|
|
246
|
+
<Router AppAssembly="@typeof(App).Assembly">
|
|
247
|
+
<!-- ... -->
|
|
248
|
+
</Router>
|
|
249
|
+
</CascadingValue>
|
|
250
|
+
|
|
251
|
+
@code {
|
|
252
|
+
private AuthState _authState = new();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Consuming component
|
|
256
|
+
@code {
|
|
257
|
+
[CascadingParameter]
|
|
258
|
+
public AuthState Auth { get; set; } = null!;
|
|
259
|
+
|
|
260
|
+
private bool IsAuthenticated => Auth.User is not null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// State container service (for complex state)
|
|
264
|
+
public class AppState
|
|
265
|
+
{
|
|
266
|
+
public User? CurrentUser { get; private set; }
|
|
267
|
+
public event Action? OnChange;
|
|
268
|
+
|
|
269
|
+
public void SetUser(User user)
|
|
270
|
+
{
|
|
271
|
+
CurrentUser = user;
|
|
272
|
+
NotifyStateChanged();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
public void ClearUser()
|
|
276
|
+
{
|
|
277
|
+
CurrentUser = null;
|
|
278
|
+
NotifyStateChanged();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private void NotifyStateChanged() => OnChange?.Invoke();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Register as scoped service
|
|
285
|
+
builder.Services.AddScoped<AppState>();
|
|
286
|
+
|
|
287
|
+
// Usage in component
|
|
288
|
+
@inject AppState State
|
|
289
|
+
@implements IDisposable
|
|
290
|
+
|
|
291
|
+
@code {
|
|
292
|
+
protected override void OnInitialized()
|
|
293
|
+
{
|
|
294
|
+
State.OnChange += StateHasChanged;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public void Dispose()
|
|
298
|
+
{
|
|
299
|
+
State.OnChange -= StateHasChanged;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
\`\`\`
|
|
303
|
+
|
|
304
|
+
## JavaScript Interop
|
|
305
|
+
\`\`\`razor
|
|
306
|
+
@inject IJSRuntime JS
|
|
307
|
+
|
|
308
|
+
<button @onclick="ShowAlert">Show Alert</button>
|
|
309
|
+
<button @onclick="CopyToClipboard">Copy</button>
|
|
310
|
+
|
|
311
|
+
<div @ref="_chartContainer"></div>
|
|
312
|
+
|
|
313
|
+
@code {
|
|
314
|
+
private ElementReference _chartContainer;
|
|
315
|
+
|
|
316
|
+
private async Task ShowAlert()
|
|
317
|
+
{
|
|
318
|
+
await JS.InvokeVoidAsync("alert", "Hello from Blazor!");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private async Task CopyToClipboard()
|
|
322
|
+
{
|
|
323
|
+
await JS.InvokeVoidAsync("navigator.clipboard.writeText", "Copied text");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
327
|
+
{
|
|
328
|
+
if (firstRender)
|
|
329
|
+
{
|
|
330
|
+
// Initialize JS library after first render
|
|
331
|
+
await JS.InvokeVoidAsync("initChart", _chartContainer);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Calling .NET from JS
|
|
336
|
+
[JSInvokable]
|
|
337
|
+
public static Task<string> GetDataFromDotNet()
|
|
338
|
+
{
|
|
339
|
+
return Task.FromResult("Data from .NET");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Instance method
|
|
343
|
+
private DotNetObjectReference<MyComponent>? _dotNetRef;
|
|
344
|
+
|
|
345
|
+
protected override void OnInitialized()
|
|
346
|
+
{
|
|
347
|
+
_dotNetRef = DotNetObjectReference.Create(this);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
[JSInvokable]
|
|
351
|
+
public void HandleJsCallback(string data)
|
|
352
|
+
{
|
|
353
|
+
// Handle callback from JS
|
|
354
|
+
StateHasChanged();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
public void Dispose()
|
|
358
|
+
{
|
|
359
|
+
_dotNetRef?.Dispose();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
\`\`\`
|
|
363
|
+
|
|
364
|
+
## Lifecycle Methods
|
|
365
|
+
\`\`\`razor
|
|
366
|
+
@implements IAsyncDisposable
|
|
367
|
+
|
|
368
|
+
@code {
|
|
369
|
+
// Called once when component is initialized
|
|
370
|
+
protected override void OnInitialized() { }
|
|
371
|
+
protected override async Task OnInitializedAsync() { }
|
|
372
|
+
|
|
373
|
+
// Called when parameters are set/changed
|
|
374
|
+
protected override void OnParametersSet() { }
|
|
375
|
+
protected override async Task OnParametersSetAsync() { }
|
|
376
|
+
|
|
377
|
+
// Called after each render
|
|
378
|
+
protected override void OnAfterRender(bool firstRender) { }
|
|
379
|
+
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
380
|
+
{
|
|
381
|
+
if (firstRender)
|
|
382
|
+
{
|
|
383
|
+
// Only runs once after first render
|
|
384
|
+
// Good for JS interop initialization
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Control when component re-renders
|
|
389
|
+
protected override bool ShouldRender()
|
|
390
|
+
{
|
|
391
|
+
return _dataChanged; // Return false to skip render
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Cleanup
|
|
395
|
+
public async ValueTask DisposeAsync()
|
|
396
|
+
{
|
|
397
|
+
// Cleanup subscriptions, JS interop, etc.
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
\`\`\`
|
|
401
|
+
|
|
402
|
+
## Render Fragments and Templated Components
|
|
403
|
+
\`\`\`razor
|
|
404
|
+
// DataGrid.razor - Templated component
|
|
405
|
+
@typeparam TItem
|
|
406
|
+
|
|
407
|
+
<table class="table">
|
|
408
|
+
<thead>
|
|
409
|
+
<tr>@HeaderTemplate</tr>
|
|
410
|
+
</thead>
|
|
411
|
+
<tbody>
|
|
412
|
+
@foreach (var item in Items)
|
|
413
|
+
{
|
|
414
|
+
<tr>@RowTemplate(item)</tr>
|
|
415
|
+
}
|
|
416
|
+
</tbody>
|
|
417
|
+
</table>
|
|
418
|
+
|
|
419
|
+
@code {
|
|
420
|
+
[Parameter, EditorRequired]
|
|
421
|
+
public List<TItem> Items { get; set; } = null!;
|
|
422
|
+
|
|
423
|
+
[Parameter, EditorRequired]
|
|
424
|
+
public RenderFragment HeaderTemplate { get; set; } = null!;
|
|
425
|
+
|
|
426
|
+
[Parameter, EditorRequired]
|
|
427
|
+
public RenderFragment<TItem> RowTemplate { get; set; } = null!;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Usage
|
|
431
|
+
<DataGrid Items="_users">
|
|
432
|
+
<HeaderTemplate>
|
|
433
|
+
<th>Name</th>
|
|
434
|
+
<th>Email</th>
|
|
435
|
+
</HeaderTemplate>
|
|
436
|
+
<RowTemplate Context="user">
|
|
437
|
+
<td>@user.Name</td>
|
|
438
|
+
<td>@user.Email</td>
|
|
439
|
+
</RowTemplate>
|
|
440
|
+
</DataGrid>
|
|
441
|
+
\`\`\`
|
|
442
|
+
|
|
443
|
+
## ✅ DO
|
|
444
|
+
- Use \`@key\` directive in loops for optimal diffing
|
|
445
|
+
- Use \`EventCallback<T>\` for component communication
|
|
446
|
+
- Use \`[EditorRequired]\` for required parameters
|
|
447
|
+
- Use \`OnAfterRenderAsync\` for JS interop initialization
|
|
448
|
+
- Dispose event handlers and JS references
|
|
449
|
+
- Use \`StateHasChanged()\` only when necessary
|
|
450
|
+
|
|
451
|
+
## ❌ DON'T
|
|
452
|
+
- Don't call \`StateHasChanged()\` in lifecycle methods (automatic)
|
|
453
|
+
- Don't do heavy work in \`OnInitialized\` - use \`OnInitializedAsync\`
|
|
454
|
+
- Don't use \`@bind\` and \`@onchange\` on same element
|
|
455
|
+
- Don't forget \`@implements IDisposable\` when subscribing to events
|
|
456
|
+
- Don't use \`IJSRuntime\` in \`OnInitialized\` - wait for \`OnAfterRender\`
|