« MudBlazor » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(→Theme) |
|||
Ligne 5 : | Ligne 5 : | ||
= Components = | = Components = | ||
== [https://mudblazor.com/components/dialog Dialog] == | == [https://mudblazor.com/components/dialog Dialog] == | ||
<filebox fn='Pages/MyPage.razor.cs'> | <filebox fn='Pages/MyPage.razor.cs'> | ||
Ligne 70 : | Ligne 40 : | ||
void Cancel() => MudDialog.Cancel(); | void Cancel() => MudDialog.Cancel(); | ||
} | } | ||
</filebox> | </filebox> | ||
Ligne 98 : | Ligne 50 : | ||
OnClick="@(() => OnDeleteAsync(@context.Id))" | OnClick="@(() => OnDeleteAsync(@context.Id))" | ||
Href="@($"items/{context.Id}")" /> | Href="@($"items/{context.Id}")" /> | ||
</kode> | |||
== [https://mudblazor.com/components/image Image] == | |||
<kode lang='razor'> | |||
<MudImage Src="images/MyImage.svg" Alt="MyImage" Width="20" /> | |||
</kode> | |||
== [https://mudblazor.com/components/link Link] == | |||
<kode lang='razor'> | |||
<MudLink Href="" Typo="Typo.h3" Underline="Underline.None">Text</MudLink> | |||
</kode> | </kode> | ||
Ligne 118 : | Ligne 80 : | ||
</kode> | </kode> | ||
== [https://mudblazor.com/components/ | == [https://mudblazor.com/components/messagebox MessageBox] == | ||
< | <filebox fn='Pages/MyPage.razor'> | ||
< | <MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="OnDeleteAsync">Delete</MudButton> | ||
< | |||
</ | <MudMessageBox @ref="deleteItemDialog" Title="Item deletion"> | ||
</ | <MessageContent> | ||
Are you sure you want to delete the selected item? | |||
</MessageContent> | |||
<YesButton> | |||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.DeleteForever">Delete</MudButton> | |||
</YesButton> | |||
</MudMessageBox> | |||
</filebox> | |||
<filebox fn='Pages/MyPage.razor.cs'> | |||
private async Task OnDeleteAsync() | |||
{ | |||
var dialogOptions = new DialogOptions | |||
{ | |||
CloseButton = true, | |||
DisableBackdropClick = true // modal | |||
}; | |||
var result = await deleteItemDialog.Show(dialogOptions); | |||
if (result != true) | |||
{ | |||
return; | |||
} | |||
</filebox> | |||
== [https://mudblazor.com/components/progress Progress] == | == [https://mudblazor.com/components/progress Progress] == | ||
Ligne 136 : | Ligne 121 : | ||
</kode> | </kode> | ||
== [https://mudblazor.com/components/ | == [https://mudblazor.com/components/snackbar SnackBar] == | ||
< | <filebox fn='Pages/MyPage.razor.cs'> | ||
[Inject] | |||
</ | private ISnackbar snackbar { get; set; } = default!; | ||
snackbar.Add("Success!!!", Severity.Success); | |||
snackbar.Add($"Error!!!<br>Error message:<br>{operationStatus.ErrorMessage}", Severity.Error); | |||
</filebox> | |||
<filebox fn='Program.cs'> | |||
builder.Services.AddMudServices(config => | |||
{ | |||
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight; | |||
config.SnackbarConfiguration.PreventDuplicates = true; | |||
config.SnackbarConfiguration.HideTransitionDuration = 100; | |||
}); | |||
</filebox> | |||
== [https://mudblazor.com/components/table Table] == | == [https://mudblazor.com/components/table Table] == | ||
Ligne 292 : | Ligne 290 : | ||
// if you change the Component you have to call StateHasChanged to update it | // if you change the Component you have to call StateHasChanged to update it | ||
StateHasChanged(); | StateHasChanged(); | ||
</kode> | |||
== [https://mudblazor.com/components/tooltip Tooltip] == | |||
<kode lang='razor'> | |||
<MudTooltip Text="Delete" Placement="Placement.Right" Arrow="true" Duration="1000"> | |||
<MudIconButton Icon="@Icons.Material.Filled.Delete" /> | |||
</MudTooltip> | |||
</kode> | </kode> | ||
Version du 28 octobre 2023 à 11:49
Links
Components
Dialog
Pages/MyPage.razor.cs |
[Inject] private IDialogService DialogService { get; set; } private void OnDisplayDialog() { var dialogOptions = new DialogOptions() { CloseButton = true }; var parameters = new DialogParameters<MyDialog> { { x => x.MyProperty, propertyValue } }; DialogService.Show<MyDialog>("Title", parameters, dialogOptions); |
Dialogs/MyDialog.razor |
<MudDialog> <DialogContent> </DialogContent> <DialogActions> <MudButton OnClick="Cancel">Cancel</MudButton> <MudButton Color="Color.Success" Variant="Variant.Filled" OnClick="Submit">Ok</MudButton> </DialogActions> </MudDialog> @code { [Parameter] MyDto MyProperty { get; set; } [CascadingParameter] MudDialogInstance MudDialog { get; set; } void Submit() => MudDialog.Close(DialogResult.Ok(true)); void Cancel() => MudDialog.Cancel(); } |
IconButton
<MudIconButton Icon="@Icons.Material.Filled.DeleteForever" Size="Size.Small" Color="Color.Error" aria-label="delete" OnClick="@(() => OnDeleteAsync(@context.Id))" Href="@($"items/{context.Id}")" /> |
Image
<MudImage Src="images/MyImage.svg" Alt="MyImage" Width="20" /> |
Link
<MudLink Href="" Typo="Typo.h3" Underline="Underline.None">Text</MudLink> |
Menu
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Size="Size.Small" AnchorOrigin="Origin.CenterLeft" TransformOrigin="Origin.CenterRight"> <MudMenuItem> <MudIconButton Icon="@Icons.Material.Filled.DeleteForever" Size="Size.Small" Color="Color.Error" aria-label="delete" OnClick="@(() => OnDeleteAsync(@context.Id))" /> </MudMenuItem> </MudMenu> |
MessageBox
Pages/MyPage.razor |
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="OnDeleteAsync">Delete</MudButton> <MudMessageBox @ref="deleteItemDialog" Title="Item deletion"> <MessageContent> Are you sure you want to delete the selected item? </MessageContent> <YesButton> <MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.DeleteForever">Delete</MudButton> </YesButton> </MudMessageBox> |
Pages/MyPage.razor.cs |
private async Task OnDeleteAsync() { var dialogOptions = new DialogOptions { CloseButton = true, DisableBackdropClick = true // modal }; var result = await deleteItemDialog.Show(dialogOptions); if (result != true) { return; } |
Progress
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true" /> <MudProgressCircular Color="Color.Primary" Size="Size.Small" Value="@progressValue" /> |
private double progressValue; // between 0 and 100 |
SnackBar
Pages/MyPage.razor.cs |
[Inject] private ISnackbar snackbar { get; set; } = default!; snackbar.Add("Success!!!", Severity.Success); snackbar.Add($"Error!!!<br>Error message:<br>{operationStatus.ErrorMessage}", Severity.Error); |
Program.cs |
builder.Services.AddMudServices(config => { config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight; config.SnackbarConfiguration.PreventDuplicates = true; config.SnackbarConfiguration.HideTransitionDuration = 100; }); |
Table
No validation in inline editing mode |
<MudTable @ref="table" ServerData="@(new Func<TableState, Task<TableData<MyNamespace.ItemResponse>>>(ServerReloadAsync))" Hover="true" Loading="@isSearching" LoadingProgressColor="Color.Info" Class="flex-column"> <ToolBarContent> <MudFab Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" Size="Size.Small" Href="item/new" /> <MudTextField T="string" ValueChanged="@(s => OnSearch(s))" Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0 mx-16"> </MudTextField> </ToolBarContent> <HeaderContent> <MudTh Style="text-align:right">Name</MudTh> <MudTh></MudTh> </HeaderContent> <RowTemplate> <MudTd DataLabel="Name" Style="text-align:right">@context.Name</MudTd> <MudTd> <MudMenu Icon="@Icons.Material.Filled.MoreVert" Size="Size.Small" AnchorOrigin="Origin.CenterLeft" TransformOrigin="Origin.CenterRight"> <MudMenuItem> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Small" Color="Color.Primary" aria-label="edit" Href="@($"transaction/{context.Id}")" /> </MudMenuItem> <MudMenuItem> <MudIconButton Icon="@Icons.Material.Filled.DeleteForever" Size="Size.Small" Color="Color.Error" aria-label="delete" OnClick="@(() => OnDeleteAsync(context.Id))" /> </MudMenuItem> </MudMenu> </MudTd> </RowTemplate> <NoRecordsContent> <MudText>No matching items found</MudText> </NoRecordsContent> <LoadingContent> <MudText>Loading...</MudText> </LoadingContent> <PagerContent> <MudTablePager PageSizeOptions="new int[] { 10 }" HorizontalAlignment="HorizontalAlignment.Left" HideRowsPerPage="true" /> </PagerContent> </MudTable> |
private bool isSearching; private async Task<TableData<ItemResponse>> ServerReloadAsync(TableState state) { ArgumentNullException.ThrowIfNull(state); errors.Clear(); isSearching = true; var query = itemQueryBuildService.BuildQuery(searchText, state.Page + 1, state.PageSize); var result = await itemClient.GetPagedAsync(query, CancellationToken.None); TableData<ItemResponse> data; if (!result.IsValid) { errors.AddRange(result.ErrorMessages); data = new TableData<ItemResponse> { Items = Enumerable.Empty<ItemResponse>() }; } else { data = new TableData<ItemResponse> { TotalItems = result.Result.TotalCount, Items = result.Result.Response }; } isSearching = false; return data; } private void OnSearch(string searchText) { this.searchText = searchText; table.ReloadServerData(); } private async Task OnDeleteAsync(uint ItemId) { var dialogOptions = new DialogOptions { CloseButton = true }; var result = await deleteItemDialog.Show(dialogOptions); if (result != true) { return; } var operationStatus = await itemClient.DeleteItemAsync(itemId, CancellationToken.None); if (operationStatus.Success) { snackbar.Add("The item has been well deleted", Severity.Success); } else { snackbar.Add( $"The item has not been deleted.<br>Error message:<br>{operationStatus.ErrorMessage}", Severity.Error); } } |
RowEditCommit and async method
<MudTable RowEditCommit="SaveChangesAsync"> |
private async void SaveChangesAsync(object element) { var operationStatus = await itemClient .UpdateItemAsync(item.Id, item, CancellationToken.None); // because RowEditCommit is an Action<object> it doesn't return a Task and it doesn't wait the task // if you change the Component you have to call StateHasChanged to update it StateHasChanged(); |
Tooltip
<MudTooltip Text="Delete" Placement="Placement.Right" Arrow="true" Duration="1000"> <MudIconButton Icon="@Icons.Material.Filled.Delete" /> </MudTooltip> |
Form
Pages/ItemForm.razor |
@if (Item is not null) { <MudCard> <MudForm @ref="@form" Model="@Item" Validation="@(itemViewModelValidator.ValidateValue)"> <MudCardContent> <MudTextField @bind-Value="Item.Name" For="@(() => Item.Name)" Label="Name" DebounceInterval="500" AutoFocus="true" /> </MudCardContent> </MudForm> <MudCardActions> <MudButton Variant="Variant.Filled" Color="Color.Default" Class="ml-auto mr-2" OnClick="@CancelRequest"> Cancel </MudButton> <MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mr-4" OnClick="@SubmitAsync"> Save </MudButton> </MudCardActions> </MudCard> } |
Pages/ItemForm.razor.cs |
[Parameter] public CreateUpdateItemQuery Item { get; set; } = default!; [Parameter] public EventCallback<bool> ValidationResult { get; set; } [Parameter] public EventCallback CancelRequest { get; set; } private IForm form = default!; private readonly CreateUpdateItemQueryValidator itemQueryValidator = new(); private Task SubmitAsync() => ValidationResult.InvokeAsync(form.IsValid); |
Validators/CreateUpdateItemQueryValidator.cs |
public class CreateUpdateItemQueryValidator : AbstractValidator<CreateUpdateItemQuery> { public CreateUpdateItemQueryValidator() { RuleFor(x => x.Name).NotEmpty(); } public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => { var result = await ValidateAsync(ValidationContext<CreateUpdateItemQuery>.CreateWithOptions( (CreateUpdateItemQuery)model, x => x.IncludeProperties(propertyName))); return result.IsValid ? (IEnumerable<string>)Array.Empty<string>() : result.Errors.Select(e => e.ErrorMessage); }; } |
Pages/CreateItem.razor |
<ItemForm Item="@item" ValidationResult="@(async (success) => await ValidationResultAsync(success))" CancelRequest="@(() => NavigationManager.NavigateTo("item"))" /> |
Pages/CreateItem.razor.cs |
private async Task ValidationResultAsync(bool isValid) { errors.Clear(); if (!isValid) { return; } var operationStatus = await ItemClient.CreateAsync(item, CancellationToken.None); if (operationStatus.Success) { Snackbar.Add("The item has been well created", Severity.Success); NavigationManager.NavigateTo("item"); } else { // server validation errors errors.AddRange(operationStatus.Errors.SelectMany(x => x.Value)); } } |
Add the package FluentValidation to use it. |
Radio buttons
<MudField Label="Type" Class="mt-2" Variant="Variant.Text" InnerPadding="false"> <MudRadioGroup @bind-SelectedOption="item.Type"> <MudRadio Option="@("type1")" Color="Color.Primary">Type1</MudRadio> <MudRadio Option="@("type2")" Color="Color.Primary">Type2</MudRadio> </MudRadioGroup> </MudField> |
Numeric field
<MudNumericField @bind-Value="item.Quantity" Label="Quantity" Variant="Variant.Text" Min="1" HideSpinButtons="true" /> |
Date picker
<MudDatePicker Label="Date" @bind-Date="item.Date" DisableToolbar="true" Culture="@frCulture" /> @code { CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR"); } |
Time picker
<MudTimePicker Label="Time" @bind-Time="item.Time" Culture="@frCulture" /> |
Autocomplete list
<MudAutocomplete T="MyNamespace.CurrencyResponse" Label="Currency" @bind-Value="item.Currency" SearchFuncWithCancel="@SearchCurrenciesByCodeAsync" ToStringFunc="@(x => x == null ? null : x.Code)" Strict="false" ShowProgressIndicator="true"> <BeforeItemsTemplate> <MudText Class="px-4 py-1">3 characters min.</MudText> </BeforeItemsTemplate> <NoItemsTemplate> <MudText Align="Align.Center" Class="px-4 py-1"> No matching currencies found </MudText> </NoItemsTemplate> |
// call to the web API private async Task<IEnumerable<CurrencyResponse>> SearchCurrenciesByCodeAsync(string value, CancellationToken cancellationToken) => await CurrencyClient.GetCurrenciesByCodeAsync(value, cancellationToken); // cache all the currencies private async Task<IEnumerable<CurrencyResponse>> SearchCurrenciesByCodeAsync(string value) { var currencies = await CurrencyClient.GetAllAsync(); // if text is null or empty, show complete list return string.IsNullOrEmpty(value) ? (IEnumerable<CurrencyResponse>)currencies : currencies.Where(x => x.Code.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); } |
Select
<MudSelect T="Namespace.Item" @bind-Value="@selectedItem" Label="Item" AnchorOrigin="Origin.BottomCenter"> @foreach (var item in items) { <MudSelectItem Value="@item">@item.Description</MudSelectItem> } </MudSelect> |
private readonly List<Item> items = new(); private Item selectedItem = default!; protected override async Task OnInitializedAsync() { // fetch items var item = items.FirstOrDefault(x => x.Description == "best item"); if (item is not null) { selectedItem = item; } StateHasChanged(); } |
File Upload
<MudFileUpload T="IReadOnlyList<IBrowserFile>" Accept=".pdf, .csv" AppendMultipleFiles MaximumFileCount="@(int.MaxValue)" FilesChanged="UploadFileAsync" Class="mt-0"> <ButtonTemplate> <MudButton HtmlTag="label" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.UploadFile" for="@context"> Load </MudButton> </ButtonTemplate> </MudFileUpload> |
private async Task UploadFileContentAsync(IReadOnlyList<IBrowserFile> files) { var filesTooBig = files.Where(x => x.Size > 512000).ToArray(); errors.AddRange(filesTooBig.Select(GetFileTooBigErrorMessage)); var tasks = files .Except(filesTooBig) .Select( async x => new { name = x.Name, content = await GetFileContentAsync(x) }); var fileContents = await Task.WhenAll(tasks); static async Task<byte[]> GetFileContentAsync(IBrowserFile file) { using var stream = file.OpenReadStream(); using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream); return memoryStream.ToArray(); } } |
Icons
<MudIcon Icon="@Icons.Material.Filled.Sell" Title="Favorite" Color="Color.Primary" Size="Size.Small" /> <MudIconButton Icon="@Icons.Material.Filled.Home" /> <MudIconButton Icon="@myIcon" /> @code { string myIcon = MudBlazor.Icons.Material.Filled.Home; } |
CSS utilities
Spacing
Code | Description |
---|---|
m p | margin padding |
t b r l | top bottom right left |
x | left and right |
y | top and bottom |
a | all 4 sides |
0 1 n1 auto | 0 4px -4px auto |
Border
Code | Description |
---|---|
border | border-width: 1px; |
border-solid | border-style: solid; |
mud-border-primary |
Cursor / Mouse
<MudPaper Class="cursor-help">Help</MudPaper> |
Colors
<MudPaper Style="@($"color:{theme.Palette.Dark}; background:{theme.Palette.Warning};")"> C# and theme colors </MudPaper> <MudPaper Class="blue lighten-4 red-text .text-darken-4"> Material colors background: blue lighten4; color: red darken4 </MudPaper> <MudPaper Style="@($"color:{Colors.Blue.Lighten4}; background:{Colors.Red.Darken4};")"> C# and material colors </MudPaper> @code { private MudTheme theme = new MudTheme(); } |
Theme
Shared/MainLayout.razor |
<MudThemeProvider IsDarkMode="true" Theme="customTheme" /> @code { MudTheme customTheme = new MudTheme { PaletteDark = new PaletteDark { Background = "#222" }, LayoutProperties = new LayoutProperties { DrawerWidthLeft = "160px" // --mud-drawer-width-left 240px } }; } |
wwwroot/css/theme.css |
.mud-icon-default { color: var(--mud-palette-primary); } |
CSS isolation
|
Installation
New project
# install MudBlazor templates dotnet new install MudBlazor.Templates # help on new project dotnet new mudblazor --help # new server project named MudBlazor dotnet new mudblazor --host server --output MudBlazor # --no-https is not available |
No HTTPS
Program.cs |
// remove // app.UseHttpsRedirection(); |
Properties/launchSettings.json |
{ "profiles": { "MudBlazorThemeManager": { "applicationUrl": "http://localhost:5000", // remove https url |
.NET 7
By default the template set the project to .NET 6
MYproject.csproj |
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net7.0</TargetFramework> <ItemGroup> <PackageReference Include="MudBlazor" Version="6.7.0" /> |
.vscode/launch.json |
{ "configurations": [ { "program": "${workspaceFolder}/bin/Debug/net7.0/MyProject.dll", |
Already existing project
dotnet add package MudBlazor |
_Imports.razor |
@using MudBlazor |
Pages/_Host.cshtml |
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /> <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" /> <script src="_content/MudBlazor/MudBlazor.min.js"></script> |
Program.cs |
builder.Services.AddMudServices(); |
Shared/MainLayout.razor |
<MudThemeProvider/> <MudDialogProvider/> <MudSnackbarProvider/> |