« MudBlazor » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(134 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[Category:Blazor]] | [[Category:Blazor]] | ||
= Links = | = Links = | ||
* [https://mudblazor.com/getting-started | * [https://mudblazor.com MudBlazor] | ||
* [https://github.com/MudBlazor/MudBlazor GitHub] | |||
= [https://mudblazor.com/getting-started/layouts Layouts] = | |||
<filebox fn='Shared/MainLayout.razor'> | |||
@inherits LayoutComponentBase | |||
<PageTitle>My Application</PageTitle> | |||
<MudThemeProvider IsDarkMode="true" Theme="customTheme" /> | |||
<MudPopoverProvider /> | |||
<MudDialogProvider /> | |||
<MudSnackbarProvider /> | |||
<div> | |||
@*<MudAppBar> | |||
My Application | |||
</MudAppBar>*@ | |||
<MudLayout> | |||
<MudDrawer Open="true"> | |||
<MudDrawerHeader Class="cursor-help mb-1 mx-auto"> | |||
<MudTooltip Text="@($"Version {AssemblyVersion}")" | |||
Placement="Placement.Right" | |||
Arrow="true" | |||
Duration="1000"> | |||
<MudText Typo="Typo.h4" Color="Color.Primary">My Application</MudText> | |||
</MudTooltip> | |||
</MudDrawerHeader> | |||
<NavMenu /> | |||
</MudDrawer> | |||
<MudMainContent Class="py-2"> | |||
<MudContainer MaxWidth="MaxWidth.Large"> | |||
@Body | |||
</MudContainer> | |||
</MudMainContent> | |||
</MudLayout> | |||
</div> | |||
</filebox> | |||
<filebox fn='Shared/MainLayout.razor.css'> | |||
::deep .mud-drawer-content { | |||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); | |||
} | |||
</filebox> | |||
<filebox fn='Shared/NavMenu.razor'> | |||
<div> | |||
<MudNavMenu> | |||
<MudNavLink Href="/" | |||
Match="NavLinkMatch.All" | |||
Icon="@Icons.Material.Filled.Home" | |||
IconColor="Color.Primary"> | |||
Home | |||
</MudNavLink> | |||
<MudNavLink Href="/menu1" | |||
Icon="@Icons.Material.Filled.Diversity1" | |||
IconColor="Color.Secondary"> | |||
Menu 1 | |||
</MudNavLink> | |||
</MudNavMenu> | |||
</div> | |||
</filebox> | |||
<filebox fn='Shared/NavMenu.razor.css'> | |||
::deep .mud-nav-item { | |||
font-size: 0.9rem; | |||
padding: 0 1rem 0.5rem; | |||
} | |||
::deep .mud-nav-item a { | |||
height: 3rem; | |||
border-radius: 4px; | |||
display: flex; | |||
align-items: center; | |||
} | |||
::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) { | |||
background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */ | |||
} | |||
::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) { | |||
background-color: rgba(255,255,255,0.1); | |||
color: inherit; | |||
} | |||
::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled), | |||
::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) { | |||
background-color: rgba(255,255,255,0.1); | |||
} | |||
</filebox> | |||
= Components = | = Components = | ||
== [https://mudblazor.com/components/button Button] == | |||
<kode lang='razor'> | |||
<MudButton Variant="Variant.Filled" | |||
StartIcon="@Icons.Material.Filled.Delete" | |||
Color="Color.Error">Delete</MudButton> | |||
<MudButton Href="https://github.com/MudBlazor/MudBlazor" | |||
Target="_blank" | |||
Variant="Variant.Filled" | |||
EndIcon="@Icons.Custom.Brands.GitHub" | |||
Color="Color.Primary"> | |||
Link | |||
</MudButton> | |||
<MudButton Disabled="@processing" | |||
OnClick="Process" | |||
Variant="Variant.Filled" | |||
Color="Color.Primary"> | |||
@if (processing) | |||
{ | |||
<MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true" /> | |||
<MudText Class="ms-2">Processing</MudText> | |||
} | |||
else | |||
{ | |||
<MudText>Click me</MudText> | |||
} | |||
</MudButton> | |||
@code { | |||
private bool processing = false; | |||
async Task ProcessAsync() | |||
{ | |||
processing = true; | |||
await Task.Delay(2000); | |||
processing = false; | |||
} | |||
} | |||
</kode> | |||
== [https://mudblazor.com/components/dialog Dialog] == | |||
<filebox fn='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); | |||
</filebox> | |||
<filebox fn='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(); | |||
} | |||
</filebox> | |||
== [https://mudblazor.com/components/iconbutton IconButton] == | |||
<kode lang='razor'> | |||
<MudIconButton Icon="@Icons.Material.Filled.DeleteForever" | |||
Size="Size.Small" | |||
Color="Color.Error" | |||
aria-label="delete" | |||
OnClick="@(() => OnDeleteAsync(@context.Id))" | |||
Href="@($"items/{context.Id}")" /> | |||
</kode> | |||
== [https://mudblazor.com/components/image Image] == | |||
<kode lang='razor'> | |||
<MudImage Src="images/MyImage.svg" | |||
Alt="MyImage" | |||
Width="200" | |||
Elevation="10" | |||
Class="rounded-lg" /> | |||
</kode> | |||
== [https://mudblazor.com/components/link Link] == | |||
<kode lang='razor'> | |||
<MudLink Href="" | |||
Typo="Typo.h3" | |||
Underline="Underline.None" | |||
Color="Color.Default" | |||
Class="purple-text text-lighten-3">Text</MudLink> | |||
</kode> | |||
== [https://mudblazor.com/components/list List] == | |||
<kode lang='razor'> | |||
<MudPaper MaxHeight="200px" Class="mt-1 overflow-y-auto border-solid border mud-border-error"> | |||
<MudList T="string" | |||
Dense="true" | |||
Class="mud-error-text"> | |||
@foreach (var error in Errors) | |||
{ | |||
<MudListItem Text="@error" Icon="@Icons.Material.Filled.ErrorOutline" IconColor="Color.Error" /> | |||
} | |||
</MudList> | |||
</MudPaper> | |||
@code { | |||
[Parameter] | |||
public IReadOnlyCollection<string> Errors { get; set; } = Array.Empty<string>(); | |||
} | |||
</kode> | |||
== [https://mudblazor.com/components/menu Menu] == | |||
<kode lang='razor'> | |||
<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> | |||
</kode> | |||
== [https://mudblazor.com/components/expansionpanels Expansion Panels] == | |||
<filebox fn='Pages/MyPage.razor'> | |||
<MudExpansionPanels MultiExpansion="true"> | |||
<MudExpansionPanel Text="Panel One"> | |||
Panel One Content | |||
</MudExpansionPanel> | |||
<MudExpansionPanel Text="Panel Two"> | |||
Panel Two Content | |||
</MudExpansionPanel> | |||
</MudExpansionPanels> | |||
</filebox> | |||
== [https://mudblazor.com/components/messagebox MessageBox] == | == [https://mudblazor.com/components/messagebox MessageBox] == | ||
<filebox fn='Pages/MyPage.razor'> | <filebox fn='Pages/MyPage.razor'> | ||
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="OnDeleteAsync" >Delete</MudButton> | <MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="OnDeleteAsync">Delete</MudButton> | ||
<MudMessageBox @ref="deleteItemDialog" Title="Item deletion"> | <MudMessageBox @ref="deleteItemDialog" Title="Item deletion"> | ||
Ligne 26 : | Ligne 272 : | ||
DisableBackdropClick = true // modal | DisableBackdropClick = true // modal | ||
}; | }; | ||
var result = await deleteItemDialog. | var result = await deleteItemDialog.ShowAsync(dialogOptions); | ||
if (result != true) | if (result != true) | ||
Ligne 33 : | Ligne 279 : | ||
} | } | ||
</filebox> | </filebox> | ||
== [https://mudblazor.com/components/navmenu Navigation Menu] == | |||
<kode lang='razor'> | |||
<MudNavMenu> | |||
<MudNavLink Href="" | |||
Match="NavLinkMatch.All" | |||
Icon="@Icons.Material.Filled.Home" | |||
IconColor="Color.Primary"> | |||
Home | |||
</MudNavLink> | |||
<MudNavLink Href="menu1" | |||
Icon="@Icons.Material.Filled.Diversity1" | |||
IconColor="Color.Secondary"> | |||
Menu 1 | |||
</MudNavLink> | |||
</MudNavMenu> | |||
</kode> | |||
== [https://mudblazor.com/components/progress Progress] == | |||
<kode lang='razor'> | |||
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true" /> | |||
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Value="@progressValue" /> | |||
</kode> | |||
<kode lang='cs'> | |||
private double progressValue; // between 0 and 100 | |||
</kode> | |||
== [https://mudblazor.com/components/snackbar SnackBar] == | == [https://mudblazor.com/components/snackbar SnackBar] == | ||
Ligne 52 : | Ligne 326 : | ||
</filebox> | </filebox> | ||
== [https://mudblazor.com/components/ | == [https://mudblazor.com/components/typography Text] == | ||
<kode lang='razor'> | |||
<MudText Typo="Typo.h6" | |||
Align="Align.Center"> | |||
Title | |||
</MudText> | |||
</kode> | |||
== [https://mudblazor.com/components/table Table] == | |||
* [https://mudblazor.com/api/table API] | |||
{{warn | [https://github.com/MudBlazor/MudBlazor/discussions/4210 No validation in inline editing mode]}} | |||
=== [https://mudblazor.com/components/table#sorting Sorting] === | |||
<kode lang='razor' collapsed> | |||
<MudTable Items="@items" | |||
SortLabel="Sort By" | |||
AllowUnsorted="false"> | |||
<HeaderContent> | |||
<MudTh>Name</MudTh> <!-- no sorting on this column --> | |||
<MudTh> | |||
<MudTableSortLabel SortBy="new Func<Item, object>(x => x.Price)" | |||
InitialDirection="SortDirection.Descending"> <!-- sorting by default on this column --> | |||
Price | |||
</MudTableSortLabel> | |||
</MudTh> | |||
</HeaderContent> | |||
</kode> | |||
=== [https://mudblazor.com/components/table#server-side-filtering,-sorting-and-pagination Server Side Filtering, Sorting and Pagination] === | |||
<kode lang='razor' collapsed> | |||
<MudTable @ref="table" | |||
ServerData="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> | |||
</kode> | |||
<kode lang='cs' collapsed> | |||
private bool isSearching; | |||
private async Task<TableData<ItemResponse>> ServerReloadAsync(TableState state, CancellationToken cancellationToken) | |||
{ | |||
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); | |||
} | |||
} | |||
</kode> | |||
=== RowEditCommit and async method === | |||
<kode lang='razor' collapsed> | |||
<MudTable | |||
RowEditCommit="SaveChangesAsync"> | |||
</kode> | |||
<kode lang='cs' collapsed> | |||
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(); | |||
</kode> | |||
== [https://mudblazor.com/components/tabs#simple-tab Tabs] == | |||
<kode lang='razor'> | |||
<MudTabs Elevation="2" Rounded="true" Centered="true" ApplyEffectsToContainer="true" PanelClass="pa-6"> | |||
<MudTabPanel Text="Tab text 1">...</MudTabPanel> | |||
<MudTabPanel Text="Tab text 2">...</MudTabPanel> | |||
</MudTabs> | |||
</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> | |||
== [https://mudblazor.com/components/form Form] == | |||
<filebox fn='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> | |||
} | |||
</filebox> | |||
<filebox fn='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); | |||
</filebox> | |||
<filebox fn='Validators/CreateUpdateItemQueryValidator.cs' collapsed> | |||
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); | |||
}; | |||
} | |||
</filebox> | |||
<filebox fn='Pages/CreateItem.razor'> | |||
<ItemForm | |||
Item="@item" | |||
ValidationResult="@(async (success) => await ValidationResultAsync(success))" | |||
CancelRequest="@(() => NavigationManager.NavigateTo("item"))" /> | |||
</filebox> | |||
<filebox fn='Pages/CreateItem.razor.cs' collapsed> | |||
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)); | |||
} | |||
} | |||
</filebox> | |||
{{info | Add the package {{boxx|FluentValidation}} to use it. }} | |||
=== [https://mudblazor.com/components/textfield Text field] === | |||
<kode lang='razor'> | |||
<MudTextField @ref="searchTextField" | |||
T="string" | |||
ValueChanged="@(s => OnSearch(s))" | |||
Placeholder="Search" | |||
Adornment="Adornment.Start" | |||
AdornmentIcon="@Icons.Material.Filled.Search" | |||
IconSize="Size.Medium" | |||
Clearable="true" | |||
Class="mt-0 mx-16"> | |||
</kode> | |||
=== [https://mudblazor.com/components/radio Radio buttons] === | |||
<kode lang='razor'> | |||
<MudField Label="Type" Class="mt-2" Variant="Variant.Text" InnerPadding="false"> | |||
<MudRadioGroup @bind-Value="item.Type"> | |||
<MudRadio Value="@("type1")" Color="Color.Primary">Type1</MudRadio> | |||
<MudRadio Value="@("type2")" Color="Color.Primary">Type2</MudRadio> | |||
</MudRadioGroup> | |||
</MudField> | |||
</kode> | |||
=== [https://mudblazor.com/components/numericfield Numeric field] === | |||
<kode lang='razor'> | |||
<MudNumericField | |||
@bind-Value="item.Quantity" | |||
Label="Quantity" | |||
Variant="Variant.Text" | |||
Min="1" | |||
HideSpinButtons="true" /> | |||
</kode> | |||
=== [https://mudblazor.com/components/datepicker Date picker] === | |||
<kode lang='razor'> | |||
<MudDatePicker Label="Date" | |||
@bind-Date="item.Date" | |||
Culture="@frCulture" /> | |||
@code { | |||
CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR"); | |||
} | |||
</kode> | |||
=== [https://mudblazor.com/components/timepicker Time picker] === | |||
<kode lang='razor'> | |||
<MudTimePicker Label="Time" | |||
@bind-Time="item.Time" | |||
Culture="@frCulture" /> | |||
@code { | |||
CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR"); | |||
} | |||
</kode> | |||
=== [https://mudblazor.com/components/autocomplete Autocomplete list] === | |||
<kode lang='razor'> | |||
<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> | |||
</kode> | |||
<kode lang='cs'> | |||
// 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)); | |||
} | |||
</kode> | |||
=== [https://mudblazor.com/components/select Select] === | |||
* [https://mudblazor.com/api/select API] | |||
<kode lang='razor'> | <kode lang='razor'> | ||
< | @* with an enum *@ | ||
<MudSelect @bind-Value="logLevel" Label="Log level"> | |||
@foreach (LogLevel logLevel in Enum.GetValues(typeof(LogLevel))) | |||
{ | |||
<MudSelectItem Value="@logLevel">@logLevel.ToString()</MudSelectItem> | |||
} | |||
</MudSelect> | |||
@* with a list of items *@ | |||
<MudSelect | |||
T="Namespace.Item" | |||
@bind-Value="@selectedItem" | |||
Label="Item" | |||
AnchorOrigin="Origin.BottomCenter"> | |||
@foreach (var item in items) | |||
{ | |||
<MudSelectItem Value="@item">@item.Description</MudSelectItem> | |||
} | |||
</MudSelect> | |||
@* with static items *@ | |||
<MudSelect @bind-Value="@selectedItem" Label="Item"> | |||
<MudSelectItem Value="@("Item1")">Item1</MudSelectItem> | |||
<MudSelectItem Value="@("Item2")">Item2</MudSelectItem> | |||
</MudSelect> | |||
</kode> | |||
<kode lang='cs'> | |||
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(); | |||
} | |||
</kode> | </kode> | ||
== [https://mudblazor.com/components/ | === [https://mudblazor.com/components/fileupload File Upload] === | ||
* [https://mudblazor.com/api/fileupload API] | |||
<kode lang='razor'> | <kode lang='razor'> | ||
< | <MudFileUpload T="IReadOnlyList<IBrowserFile>" | ||
Accept=".pdf, .csv" | |||
AppendMultipleFiles | |||
MaximumFileCount="@(int.MaxValue)" | |||
FilesChanged="UploadFileAsync" | |||
< | Class="mt-0"> | ||
< | <ActivatorContent> | ||
<MudButton Variant="Variant.Filled" | |||
Size= | Color="Color.Primary" | ||
StartIcon="@Icons.Material.Filled.UploadFile"> | |||
Load | |||
</MudButton> | |||
</ActivatorContent> | |||
</MudFileUpload> | |||
</kode> | |||
<kode lang='cs'> | |||
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(); | |||
} | |||
} | |||
</kode> | </kode> | ||
= [https://mudblazor.com/features/icons Icons] | = [https://mudblazor.com/components/icons Icons] = | ||
* [https://mudblazor.com/features/icons Icons list] | |||
<kode lang='razor'> | <kode lang='razor'> | ||
<MudIcon Icon="@Icons.Material.Filled.Sell" Title="Favorite" Color="Color.Primary" Size="Size.Small" /> | |||
<MudIconButton Icon="@Icons.Material.Filled.Home" /> | <MudIconButton Icon="@Icons.Material.Filled.Home" /> | ||
Ligne 91 : | Ligne 818 : | ||
</kode> | </kode> | ||
= | = CSS utilities = | ||
== [https://mudblazor.com/utilities/spacing | == [https://mudblazor.com/utilities/spacing Margin Padding] == | ||
{| class="wikitable wtp wtmono1" | {| class="wikitable wtp wtmono1 wtmono2" | ||
! Code | ! Code | ||
! Description | ! Description | ||
Ligne 108 : | Ligne 835 : | ||
|- | |- | ||
| 0 1 n1 auto || 0 4px -4px auto | | 0 1 n1 auto || 0 4px -4px auto | ||
|} | |||
<kode lang='html'> | |||
<MudPaper Class="pa-4 mr-16"> | |||
</kode> | |||
== [https://mudblazor.com/utilities/display Display] == | |||
{| class="wikitable wtp wtmono1 wtmono2" | |||
! Class | |||
! Property | |||
|- | |- | ||
| | | d-block || display: block; | ||
|- | |||
| d-inline || display: inline; | |||
|- | |||
| d-none || display: none; | |||
|} | |||
== [https://mudblazor.com/utilities/enable-flex Flexbox] == | |||
{| class="wikitable wtp wtmono1 wtmono2" | |||
! Class | |||
! Property | |||
|- | |||
| d-flex || display: flex; | |||
|- | |||
| justify-center || justify-content: center; | |||
|- | |||
| justify-space-around || justify-content: space-around; | |||
|} | |} | ||
* [https://mudblazor.com/utilities/justify-content justify-content] | |||
== [https://mudblazor.com/utilities/border-style Border] == | |||
{| class="wikitable wtp wtmono1 wtmono2" | |||
! Code | |||
! Description | |||
|- | |||
| border || border-width: 1px; | |||
|- | |||
| border-solid || border-style: solid; | |||
|- | |||
| mud-border-primary || | |||
|} | |||
== [https://mudblazor.com/utilities/cursor Cursor / Mouse] == | |||
<kode lang='razor'> | |||
<MudPaper Class="cursor-pointer">Hand with finger raised</MudPaper> | |||
<MudPaper Class="cursor-help">Question mark</MudPaper> | |||
</kode> | |||
= [https://mudblazor.com/features/colors Colors] = | |||
* [https://stackoverflow.com/questions/72336982/how-to-use-textprimary-textsecondary-color-values-in-mudblazor-theme-palette] | |||
<kode lang='razor'> | |||
<MudPaper Color="Color.Primary"> | |||
Color enum | |||
</MudPaper> | |||
<MudPaper Class="mud-info mud-secondary-text"> | |||
CSS and theme colors. background: theme info blue; color: theme secondary pink | |||
</MudPaper> | |||
<!-- mud-theme-primary vs mud-primary --> | |||
<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"> | |||
CSS and material colors background: blue lighten4; color: red darken4 | |||
</MudPaper> | |||
<MudPaper Style="@($"color:{Colors.Blue.Lighten4}; background:{Colors.Red.Darken4};")"> | |||
C# and material colors | |||
</MudPaper> | |||
<span style="color: var(--mud-palette-warning);">C# and CSS colors</span> | |||
@code { | |||
private MudTheme theme = new MudTheme(); | |||
} | |||
</kode> | |||
= [https://mudblazor.com/customization/default-theme Theme] = | = [https://mudblazor.com/customization/default-theme Theme] = | ||
* [https://mudblazor.com/customization/default-theme Default theme] | |||
* [https://mudblazor.com/customization/palette Palette] | |||
<filebox fn='Shared/MainLayout.razor'> | <filebox fn='Shared/MainLayout.razor'> | ||
<MudThemeProvider IsDarkMode="true" Theme="customTheme" /> | <MudThemeProvider IsDarkMode="true" Theme="customTheme" /> | ||
Ligne 119 : | Ligne 925 : | ||
MudTheme customTheme = new MudTheme | MudTheme customTheme = new MudTheme | ||
{ | { | ||
PaletteDark = new PaletteDark | |||
{ | |||
Background = "#222" | |||
}, | |||
LayoutProperties = new LayoutProperties | LayoutProperties = new LayoutProperties | ||
{ | { | ||
Ligne 125 : | Ligne 935 : | ||
}; | }; | ||
} | } | ||
</filebox> | |||
<filebox fn='wwwroot/css/theme.css'> | |||
.mud-icon-default { | |||
color: var(--mud-palette-primary); | |||
} | |||
</filebox> | |||
== [[Blazor_ASP.NET_Core_7.0#CSS_isolation|CSS isolation]] == | |||
{{warn | | |||
* first tag of a component has to be an HTML tag ({{boxx|<nowiki><div></nowiki>}}), not a MudBlazor tag. | |||
* use {{boxx|::deep .my-class}} when class is set on a MudBlazor component | |||
* you may need to refresh the browser cache | |||
}} | |||
<filebox fn='MyPage.razor'> | |||
<div> | |||
<MudText Typo="Typo.h1" id="title1">@name</MudText> | |||
<h2 id="title2">@name</h2> | |||
<div> | |||
</filebox> | |||
<filebox fn='MyPage.razor.css'> | |||
::deep title1 { } | |||
title2 { } | |||
</filebox> | |||
= Build component in the code-behind = | |||
<filebox fn='MyPage.razor'> | |||
<MudTd DataLabel="Type"> | |||
@GetIconFromType(context.Type) | |||
</MudTd> | |||
</filebox> | |||
<filebox fn='MyPage.razor.cs'> | |||
private static RenderFragment GetIconFromType(string type) | |||
=> builder => | |||
{ | |||
builder.OpenComponent<MudIcon>(0); | |||
builder.AddAttribute(1, "Title", type); | |||
builder.AddAttribute(2, "Icon", @Icons.Material.Filled.Money); | |||
builder.AddAttribute(3, "Color", type switch | |||
{ | |||
"deposit" => Color.Success, | |||
"withdrawal" => Color.Warning, | |||
"charge" => Color.Error, | |||
_ => throw new NotSupportedException($"The type '{type}' is not supported.") | |||
}); | |||
builder.CloseComponent(); | |||
}; | |||
</filebox> | </filebox> | ||
= [https://mudblazor.com/getting-started/installation Installation] = | = [https://mudblazor.com/getting-started/installation Installation] = | ||
== New project == | == New project from template == | ||
<kode lang='bash'> | <kode lang='bash'> | ||
# install MudBlazor templates | # install MudBlazor templates | ||
Ligne 138 : | Ligne 998 : | ||
# new server project named MudBlazor | # new server project named MudBlazor | ||
dotnet new mudblazor --host server --output MudBlazor | dotnet new mudblazor --host server --output MudBlazor | ||
# --no-https is not available | |||
</kode> | </kode> | ||
=== Remove HTTPS === | |||
<filebox fn='Program.cs'> | |||
// remove | |||
// app.UseHttpsRedirection(); | |||
</filebox> | |||
<filebox fn='Properties/launchSettings.json'> | |||
{ | |||
"iisSettings": { | |||
"iisExpress": { | |||
"applicationUrl": "http://localhost:5000", | |||
// "sslPort": 44393 // remove | |||
} | |||
}, | |||
"profiles": { | |||
"MudBlazorThemeManager": { | |||
"applicationUrl": "http://localhost:5000", // remove https url | |||
</filebox> | |||
=== .NET 7 === | |||
By default the template set the project to .NET 6 | |||
<filebox fn='MYproject.csproj' lang='xml'> | |||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||
<PropertyGroup> | |||
<TargetFramework>net7.0</TargetFramework> | |||
</filebox> | |||
<filebox fn='.vscode/launch.json'> | |||
{ | |||
"configurations": [ | |||
{ | |||
"program": "${workspaceFolder}/bin/Debug/net7.0/MyProject.dll", | |||
</filebox> | |||
=== _Layout.cshtml === | |||
Remove {{boxx|_Layout.cshtml}} | |||
<filebox fn='_Host.cshtml' lang='razor' collapsed> | |||
@page "/" | |||
@using Microsoft.AspNetCore.Components.Web | |||
@namespace MyApp.Pages | |||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
<base href="~/" /> | |||
<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" /> | |||
@* CSS isolation *@ | |||
<link rel="stylesheet" href="MyApp.styles.css" /> | |||
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> | |||
</head> | |||
<body> | |||
<component type="typeof(App)" render-mode="ServerPrerendered" /> | |||
<div id="blazor-error-ui"> | |||
<environment include="Staging,Production"> | |||
An error has occurred. This application may no longer respond until reloaded. | |||
</environment> | |||
<environment include="Development"> | |||
An unhandled exception has occurred. See browser dev tools for details. | |||
</environment> | |||
<a href="" class="reload">Reload</a> | |||
<a class="dismiss">🗙</a> | |||
</div> | |||
<script src="_framework/blazor.server.js"></script> | |||
<script src="_content/MudBlazor/MudBlazor.min.js"></script> | |||
</body> | |||
</html> | |||
</filebox> | |||
=== Styled navigation menu === | |||
<filebox fn='Shared/MainLayout.razor' collapsed> | |||
<MudThemeProvider IsDarkMode="true" Theme="customTheme" /> | |||
@code { | |||
MudTheme customTheme = new MudTheme | |||
{ | |||
LayoutProperties = new LayoutProperties | |||
{ | |||
DrawerWidthLeft = "160px" // --mud-drawer-width-left 240px | |||
} | |||
}; | |||
} | |||
</filebox> | |||
<filebox fn='Shared/MainLayout.razor.css' collapsed> | |||
::deep .mud-drawer-content { | |||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); | |||
} | |||
</filebox> | |||
<filebox fn='Shared/NavMenu.razor.css' collapsed> | |||
::deep .mud-nav-item { | |||
font-size: 0.9rem; | |||
padding: 0 1rem 0.5rem; | |||
} | |||
::deep .mud-nav-item a { | |||
height: 3rem; | |||
border-radius: 4px; | |||
display: flex; | |||
align-items: center; | |||
} | |||
::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) { | |||
background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */ | |||
} | |||
::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) { | |||
background-color: rgba(255,255,255,0.1); | |||
color: inherit; | |||
} | |||
::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled), | |||
::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) { | |||
background-color: rgba(255,255,255,0.1); | |||
} | |||
</filebox> | |||
== Already existing project == | == Already existing project == | ||
Ligne 161 : | Ligne 1 147 : | ||
<filebox fn='Shared/MainLayout.razor'> | <filebox fn='Shared/MainLayout.razor'> | ||
<MudThemeProvider/> | <MudThemeProvider /> | ||
<MudDialogProvider/> | <MudPopoverProvider /> | ||
<MudSnackbarProvider/> | <MudDialogProvider /> | ||
<MudSnackbarProvider /> | |||
</filebox> | </filebox> |
Dernière version du 16 novembre 2024 à 22:57
Links
Layouts
Shared/MainLayout.razor |
@inherits LayoutComponentBase <PageTitle>My Application</PageTitle> <MudThemeProvider IsDarkMode="true" Theme="customTheme" /> <MudPopoverProvider /> <MudDialogProvider /> <MudSnackbarProvider /> <div> @*<MudAppBar> My Application </MudAppBar>*@ <MudLayout> <MudDrawer Open="true"> <MudDrawerHeader Class="cursor-help mb-1 mx-auto"> <MudTooltip Text="@($"Version {AssemblyVersion}")" Placement="Placement.Right" Arrow="true" Duration="1000"> <MudText Typo="Typo.h4" Color="Color.Primary">My Application</MudText> </MudTooltip> </MudDrawerHeader> <NavMenu /> </MudDrawer> <MudMainContent Class="py-2"> <MudContainer MaxWidth="MaxWidth.Large"> @Body </MudContainer> </MudMainContent> </MudLayout> </div> |
Shared/MainLayout.razor.css |
::deep .mud-drawer-content { background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); } |
Shared/NavMenu.razor |
<div> <MudNavMenu> <MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home" IconColor="Color.Primary"> Home </MudNavLink> <MudNavLink Href="/menu1" Icon="@Icons.Material.Filled.Diversity1" IconColor="Color.Secondary"> Menu 1 </MudNavLink> </MudNavMenu> </div> |
Shared/NavMenu.razor.css |
::deep .mud-nav-item { font-size: 0.9rem; padding: 0 1rem 0.5rem; } ::deep .mud-nav-item a { height: 3rem; border-radius: 4px; display: flex; align-items: center; } ::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */ } ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); color: inherit; } ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled), ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); } |
Components
Button
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Delete" Color="Color.Error">Delete</MudButton> <MudButton Href="https://github.com/MudBlazor/MudBlazor" Target="_blank" Variant="Variant.Filled" EndIcon="@Icons.Custom.Brands.GitHub" Color="Color.Primary"> Link </MudButton> <MudButton Disabled="@processing" OnClick="Process" Variant="Variant.Filled" Color="Color.Primary"> @if (processing) { <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true" /> <MudText Class="ms-2">Processing</MudText> } else { <MudText>Click me</MudText> } </MudButton> @code { private bool processing = false; async Task ProcessAsync() { processing = true; await Task.Delay(2000); processing = false; } } |
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="200" Elevation="10" Class="rounded-lg" /> |
Link
<MudLink Href="" Typo="Typo.h3" Underline="Underline.None" Color="Color.Default" Class="purple-text text-lighten-3">Text</MudLink> |
List
<MudPaper MaxHeight="200px" Class="mt-1 overflow-y-auto border-solid border mud-border-error"> <MudList T="string" Dense="true" Class="mud-error-text"> @foreach (var error in Errors) { <MudListItem Text="@error" Icon="@Icons.Material.Filled.ErrorOutline" IconColor="Color.Error" /> } </MudList> </MudPaper> @code { [Parameter] public IReadOnlyCollection<string> Errors { get; set; } = Array.Empty<string>(); } |
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> |
Expansion Panels
Pages/MyPage.razor |
<MudExpansionPanels MultiExpansion="true"> <MudExpansionPanel Text="Panel One"> Panel One Content </MudExpansionPanel> <MudExpansionPanel Text="Panel Two"> Panel Two Content </MudExpansionPanel> </MudExpansionPanels> |
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.ShowAsync(dialogOptions); if (result != true) { return; } |
<MudNavMenu> <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home" IconColor="Color.Primary"> Home </MudNavLink> <MudNavLink Href="menu1" Icon="@Icons.Material.Filled.Diversity1" IconColor="Color.Secondary"> Menu 1 </MudNavLink> </MudNavMenu> |
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; }); |
Text
<MudText Typo="Typo.h6" Align="Align.Center"> Title </MudText> |
Table
No validation in inline editing mode |
Sorting
<MudTable Items="@items" SortLabel="Sort By" AllowUnsorted="false"> <HeaderContent> <MudTh>Name</MudTh> <!-- no sorting on this column --> <MudTh> <MudTableSortLabel SortBy="new Func<Item, object>(x => x.Price)" InitialDirection="SortDirection.Descending"> <!-- sorting by default on this column --> Price </MudTableSortLabel> </MudTh> </HeaderContent> |
Server Side Filtering, Sorting and Pagination
<MudTable @ref="table" ServerData="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, CancellationToken cancellationToken) { 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(); |
Tabs
<MudTabs Elevation="2" Rounded="true" Centered="true" ApplyEffectsToContainer="true" PanelClass="pa-6"> <MudTabPanel Text="Tab text 1">...</MudTabPanel> <MudTabPanel Text="Tab text 2">...</MudTabPanel> </MudTabs> |
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. |
Text field
<MudTextField @ref="searchTextField" T="string" ValueChanged="@(s => OnSearch(s))" Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Clearable="true" Class="mt-0 mx-16"> |
Radio buttons
<MudField Label="Type" Class="mt-2" Variant="Variant.Text" InnerPadding="false"> <MudRadioGroup @bind-Value="item.Type"> <MudRadio Value="@("type1")" Color="Color.Primary">Type1</MudRadio> <MudRadio Value="@("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" Culture="@frCulture" /> @code { CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR"); } |
Time picker
<MudTimePicker Label="Time" @bind-Time="item.Time" Culture="@frCulture" /> @code { CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR"); } |
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
@* with an enum *@ <MudSelect @bind-Value="logLevel" Label="Log level"> @foreach (LogLevel logLevel in Enum.GetValues(typeof(LogLevel))) { <MudSelectItem Value="@logLevel">@logLevel.ToString()</MudSelectItem> } </MudSelect> @* with a list of items *@ <MudSelect T="Namespace.Item" @bind-Value="@selectedItem" Label="Item" AnchorOrigin="Origin.BottomCenter"> @foreach (var item in items) { <MudSelectItem Value="@item">@item.Description</MudSelectItem> } </MudSelect> @* with static items *@ <MudSelect @bind-Value="@selectedItem" Label="Item"> <MudSelectItem Value="@("Item1")">Item1</MudSelectItem> <MudSelectItem Value="@("Item2")">Item2</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"> <ActivatorContent> <MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.UploadFile"> Load </MudButton> </ActivatorContent> </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
Margin Padding
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 |
<MudPaper Class="pa-4 mr-16"> |
Display
Class | Property |
---|---|
d-block | display: block; |
d-inline | display: inline; |
d-none | display: none; |
Flexbox
Class | Property |
---|---|
d-flex | display: flex; |
justify-center | justify-content: center; |
justify-space-around | justify-content: space-around; |
Border
Code | Description |
---|---|
border | border-width: 1px; |
border-solid | border-style: solid; |
mud-border-primary |
Cursor / Mouse
<MudPaper Class="cursor-pointer">Hand with finger raised</MudPaper> <MudPaper Class="cursor-help">Question mark</MudPaper> |
Colors
<MudPaper Color="Color.Primary"> Color enum </MudPaper> <MudPaper Class="mud-info mud-secondary-text"> CSS and theme colors. background: theme info blue; color: theme secondary pink </MudPaper> <!-- mud-theme-primary vs mud-primary --> <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"> CSS and material colors background: blue lighten4; color: red darken4 </MudPaper> <MudPaper Style="@($"color:{Colors.Blue.Lighten4}; background:{Colors.Red.Darken4};")"> C# and material colors </MudPaper> <span style="color: var(--mud-palette-warning);">C# and CSS colors</span> @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
|
MyPage.razor |
<div> <MudText Typo="Typo.h1" id="title1">@name</MudText> <h2 id="title2">@name</h2> <div> |
MyPage.razor.css |
::deep title1 { } title2 { } |
Build component in the code-behind
MyPage.razor |
<MudTd DataLabel="Type"> @GetIconFromType(context.Type) </MudTd> |
MyPage.razor.cs |
private static RenderFragment GetIconFromType(string type) => builder => { builder.OpenComponent<MudIcon>(0); builder.AddAttribute(1, "Title", type); builder.AddAttribute(2, "Icon", @Icons.Material.Filled.Money); builder.AddAttribute(3, "Color", type switch { "deposit" => Color.Success, "withdrawal" => Color.Warning, "charge" => Color.Error, _ => throw new NotSupportedException($"The type '{type}' is not supported.") }); builder.CloseComponent(); }; |
Installation
New project from template
# 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 |
Remove HTTPS
Program.cs |
// remove // app.UseHttpsRedirection(); |
Properties/launchSettings.json |
{ "iisSettings": { "iisExpress": { "applicationUrl": "http://localhost:5000", // "sslPort": 44393 // remove } }, "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> |
.vscode/launch.json |
{ "configurations": [ { "program": "${workspaceFolder}/bin/Debug/net7.0/MyProject.dll", |
_Layout.cshtml
Remove _Layout.cshtml
_Host.cshtml |
@page "/" @using Microsoft.AspNetCore.Components.Web @namespace MyApp.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="~/" /> <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" /> @* CSS isolation *@ <link rel="stylesheet" href="MyApp.styles.css" /> <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> </head> <body> <component type="typeof(App)" render-mode="ServerPrerendered" /> <div id="blazor-error-ui"> <environment include="Staging,Production"> An error has occurred. This application may no longer respond until reloaded. </environment> <environment include="Development"> An unhandled exception has occurred. See browser dev tools for details. </environment> <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div> <script src="_framework/blazor.server.js"></script> <script src="_content/MudBlazor/MudBlazor.min.js"></script> </body> </html> |
Shared/MainLayout.razor |
<MudThemeProvider IsDarkMode="true" Theme="customTheme" /> @code { MudTheme customTheme = new MudTheme { LayoutProperties = new LayoutProperties { DrawerWidthLeft = "160px" // --mud-drawer-width-left 240px } }; } |
Shared/MainLayout.razor.css |
::deep .mud-drawer-content { background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); } |
Shared/NavMenu.razor.css |
::deep .mud-nav-item { font-size: 0.9rem; padding: 0 1rem 0.5rem; } ::deep .mud-nav-item a { height: 3rem; border-radius: 4px; display: flex; align-items: center; } ::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */ } ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); color: inherit; } ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled), ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); } |
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 /> <MudPopoverProvider /> <MudDialogProvider /> <MudSnackbarProvider /> |