mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-25 18:25:01 +00:00
chore: publish from main
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
---
|
||||
name: fluentui-blazor
|
||||
description: >
|
||||
Guide for using the Microsoft Fluent UI Blazor component library
|
||||
(Microsoft.FluentUI.AspNetCore.Components NuGet package) in Blazor applications.
|
||||
Use this when the user is building a Blazor app with Fluent UI components,
|
||||
setting up the library, using FluentUI components like FluentButton, FluentDataGrid,
|
||||
FluentDialog, FluentToast, FluentNavMenu, FluentTextField, FluentSelect,
|
||||
FluentAutocomplete, FluentDesignTheme, or any component prefixed with "Fluent".
|
||||
Also use when troubleshooting missing providers, JS interop issues, or theming.
|
||||
---
|
||||
|
||||
# Fluent UI Blazor — Consumer Usage Guide
|
||||
|
||||
This skill teaches how to correctly use the **Microsoft.FluentUI.AspNetCore.Components** (version 4) NuGet package in Blazor applications.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### 1. No manual `<script>` or `<link>` tags needed
|
||||
|
||||
The library auto-loads all CSS and JS via Blazor's static web assets and JS initializers. **Never tell users to add `<script>` or `<link>` tags for the core library.**
|
||||
|
||||
### 2. Providers are mandatory for service-based components
|
||||
|
||||
These provider components **MUST** be added to the root layout (e.g. `MainLayout.razor`) for their corresponding services to work. Without them, service calls **fail silently** (no error, no UI).
|
||||
|
||||
```razor
|
||||
<FluentToastProvider />
|
||||
<FluentDialogProvider />
|
||||
<FluentMessageBarProvider />
|
||||
<FluentTooltipProvider />
|
||||
<FluentKeyCodeProvider />
|
||||
```
|
||||
|
||||
### 3. Service registration in Program.cs
|
||||
|
||||
```csharp
|
||||
builder.Services.AddFluentUIComponents();
|
||||
|
||||
// Or with configuration:
|
||||
builder.Services.AddFluentUIComponents(options =>
|
||||
{
|
||||
options.UseTooltipServiceProvider = true; // default: true
|
||||
options.ServiceLifetime = ServiceLifetime.Scoped; // default
|
||||
});
|
||||
```
|
||||
|
||||
**ServiceLifetime rules:**
|
||||
- `ServiceLifetime.Scoped` — for Blazor Server / Interactive (default)
|
||||
- `ServiceLifetime.Singleton` — for Blazor WebAssembly standalone
|
||||
- `ServiceLifetime.Transient` — **throws `NotSupportedException`**
|
||||
|
||||
### 4. Icons require a separate NuGet package
|
||||
|
||||
```
|
||||
dotnet add package Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||
```
|
||||
|
||||
Usage with a `@using` alias:
|
||||
|
||||
```razor
|
||||
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||
|
||||
<FluentIcon Value="@(Icons.Regular.Size24.Save)" />
|
||||
<FluentIcon Value="@(Icons.Filled.Size20.Delete)" Color="@Color.Error" />
|
||||
```
|
||||
|
||||
Pattern: `Icons.[Variant].[Size].[Name]`
|
||||
- Variants: `Regular`, `Filled`
|
||||
- Sizes: `Size12`, `Size16`, `Size20`, `Size24`, `Size28`, `Size32`, `Size48`
|
||||
|
||||
Custom image: `Icon.FromImageUrl("/path/to/image.png")`
|
||||
|
||||
**Never use string-based icon names** — icons are strongly-typed classes.
|
||||
|
||||
### 5. List component binding model
|
||||
|
||||
`FluentSelect<TOption>`, `FluentCombobox<TOption>`, `FluentListbox<TOption>`, and `FluentAutocomplete<TOption>` do NOT work like `<InputSelect>`. They use:
|
||||
|
||||
- `Items` — the data source (`IEnumerable<TOption>`)
|
||||
- `OptionText` — `Func<TOption, string?>` to extract display text
|
||||
- `OptionValue` — `Func<TOption, string?>` to extract the value string
|
||||
- `SelectedOption` / `SelectedOptionChanged` — for single selection binding
|
||||
- `SelectedOptions` / `SelectedOptionsChanged` — for multi-selection binding
|
||||
|
||||
```razor
|
||||
<FluentSelect Items="@countries"
|
||||
OptionText="@(c => c.Name)"
|
||||
OptionValue="@(c => c.Code)"
|
||||
@bind-SelectedOption="@selectedCountry"
|
||||
Label="Country" />
|
||||
```
|
||||
|
||||
**NOT** like this (wrong pattern):
|
||||
```razor
|
||||
@* WRONG — do not use InputSelect pattern *@
|
||||
<FluentSelect @bind-Value="@selectedValue">
|
||||
<option value="1">One</option>
|
||||
</FluentSelect>
|
||||
```
|
||||
|
||||
### 6. FluentAutocomplete specifics
|
||||
|
||||
- Use `ValueText` (NOT `Value` — it's obsolete) for the search input text
|
||||
- `OnOptionsSearch` is the required callback to filter options
|
||||
- Default is `Multiple="true"`
|
||||
|
||||
```razor
|
||||
<FluentAutocomplete TOption="Person"
|
||||
OnOptionsSearch="@OnSearch"
|
||||
OptionText="@(p => p.FullName)"
|
||||
@bind-SelectedOptions="@selectedPeople"
|
||||
Label="Search people" />
|
||||
|
||||
@code {
|
||||
private void OnSearch(OptionsSearchEventArgs<Person> args)
|
||||
{
|
||||
args.Items = allPeople.Where(p =>
|
||||
p.FullName.Contains(args.Text, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Dialog service pattern
|
||||
|
||||
**Do NOT toggle visibility of `<FluentDialog>` tags.** The service pattern is:
|
||||
|
||||
1. Create a content component implementing `IDialogContentComponent<TData>`:
|
||||
|
||||
```csharp
|
||||
public partial class EditPersonDialog : IDialogContentComponent<Person>
|
||||
{
|
||||
[Parameter] public Person Content { get; set; } = default!;
|
||||
|
||||
[CascadingParameter] public FluentDialog Dialog { get; set; } = default!;
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
await Dialog.CloseAsync(Content);
|
||||
}
|
||||
|
||||
private async Task CancelAsync()
|
||||
{
|
||||
await Dialog.CancelAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Show the dialog via `IDialogService`:
|
||||
|
||||
```csharp
|
||||
[Inject] private IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
private async Task ShowEditDialog()
|
||||
{
|
||||
var dialog = await DialogService.ShowDialogAsync<EditPersonDialog, Person>(
|
||||
person,
|
||||
new DialogParameters
|
||||
{
|
||||
Title = "Edit Person",
|
||||
PrimaryAction = "Save",
|
||||
SecondaryAction = "Cancel",
|
||||
Width = "500px",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
var updatedPerson = result.Data as Person;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For convenience dialogs:
|
||||
```csharp
|
||||
await DialogService.ShowConfirmationAsync("Are you sure?", "Yes", "No");
|
||||
await DialogService.ShowSuccessAsync("Done!");
|
||||
await DialogService.ShowErrorAsync("Something went wrong.");
|
||||
```
|
||||
|
||||
### 8. Toast notifications
|
||||
|
||||
```csharp
|
||||
[Inject] private IToastService ToastService { get; set; } = default!;
|
||||
|
||||
ToastService.ShowSuccess("Item saved successfully");
|
||||
ToastService.ShowError("Failed to save");
|
||||
ToastService.ShowWarning("Check your input");
|
||||
ToastService.ShowInfo("New update available");
|
||||
```
|
||||
|
||||
`FluentToastProvider` parameters: `Position` (default `TopRight`), `Timeout` (default 7000ms), `MaxToastCount` (default 4).
|
||||
|
||||
### 9. Design tokens and themes work only after render
|
||||
|
||||
Design tokens rely on JS interop. **Never set them in `OnInitialized`** — use `OnAfterRenderAsync`.
|
||||
|
||||
```razor
|
||||
<FluentDesignTheme Mode="DesignThemeModes.System"
|
||||
OfficeColor="OfficeColor.Teams"
|
||||
StorageName="mytheme" />
|
||||
```
|
||||
|
||||
### 10. FluentEditForm vs EditForm
|
||||
|
||||
`FluentEditForm` is only needed inside `FluentWizard` steps (per-step validation). For regular forms, use standard `EditForm` with Fluent form components:
|
||||
|
||||
```razor
|
||||
<EditForm Model="@model" OnValidSubmit="HandleSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<FluentTextField @bind-Value="@model.Name" Label="Name" Required />
|
||||
<FluentSelect Items="@options"
|
||||
OptionText="@(o => o.Label)"
|
||||
@bind-SelectedOption="@model.Category"
|
||||
Label="Category" />
|
||||
<FluentValidationSummary />
|
||||
<FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Save</FluentButton>
|
||||
</EditForm>
|
||||
```
|
||||
|
||||
Use `FluentValidationMessage` and `FluentValidationSummary` instead of standard Blazor validation components for Fluent styling.
|
||||
|
||||
## Reference files
|
||||
|
||||
For detailed guidance on specific topics, see:
|
||||
|
||||
- [Setup and configuration](references/SETUP.md)
|
||||
- [Layout and navigation](references/LAYOUT-AND-NAVIGATION.md)
|
||||
- [Data grid](references/DATAGRID.md)
|
||||
- [Theming](references/THEMING.md)
|
||||
@@ -0,0 +1,162 @@
|
||||
# FluentDataGrid
|
||||
|
||||
`FluentDataGrid<TGridItem>` is a strongly-typed generic component for displaying tabular data.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```razor
|
||||
<FluentDataGrid Items="@people" TGridItem="Person">
|
||||
<PropertyColumn Property="@(p => p.Name)" Sortable="true" />
|
||||
<PropertyColumn Property="@(p => p.Email)" />
|
||||
<PropertyColumn Property="@(p => p.BirthDate)" Format="yyyy-MM-dd" />
|
||||
<TemplateColumn Title="Actions">
|
||||
<FluentButton OnClick="@(() => Edit(context))">Edit</FluentButton>
|
||||
</TemplateColumn>
|
||||
</FluentDataGrid>
|
||||
```
|
||||
|
||||
**Critical**: Columns are child components, NOT properties. Use `PropertyColumn`, `TemplateColumn`, and `SelectColumn` within the grid.
|
||||
|
||||
## Column Types
|
||||
|
||||
### PropertyColumn
|
||||
|
||||
Binds to a property expression. Auto-derives title from property name or `[Display]` attribute.
|
||||
|
||||
```razor
|
||||
<PropertyColumn Property="@(p => p.Name)" Sortable="true" />
|
||||
<PropertyColumn Property="@(p => p.Price)" Format="C2" Title="Unit Price" />
|
||||
<PropertyColumn Property="@(p => p.Category)" Comparer="@StringComparer.OrdinalIgnoreCase" />
|
||||
```
|
||||
|
||||
Parameters: `Property` (required), `Format`, `Title`, `Sortable`, `SortBy`, `Comparer`, `IsDefaultSortColumn`, `InitialSortDirection`, `Class`, `Tooltip`.
|
||||
|
||||
### TemplateColumn
|
||||
|
||||
Full custom rendering via render fragment. `context` is the `TGridItem`.
|
||||
|
||||
```razor
|
||||
<TemplateColumn Title="Status" SortBy="@statusSort">
|
||||
<FluentBadge Appearance="Appearance.Accent"
|
||||
BackgroundColor="@(context.IsActive ? "green" : "red")">
|
||||
@(context.IsActive ? "Active" : "Inactive")
|
||||
</FluentBadge>
|
||||
</TemplateColumn>
|
||||
```
|
||||
|
||||
### SelectColumn
|
||||
|
||||
Checkbox selection column.
|
||||
|
||||
```razor
|
||||
<SelectColumn TGridItem="Person"
|
||||
SelectMode="DataGridSelectMode.Multiple"
|
||||
@bind-SelectedItems="@selectedPeople" />
|
||||
```
|
||||
|
||||
Modes: `DataGridSelectMode.Single`, `DataGridSelectMode.Multiple`.
|
||||
|
||||
## Data Sources
|
||||
|
||||
Two mutually exclusive approaches:
|
||||
|
||||
### In-memory (IQueryable)
|
||||
|
||||
```razor
|
||||
<FluentDataGrid Items="@people.AsQueryable()" TGridItem="Person">
|
||||
...
|
||||
</FluentDataGrid>
|
||||
```
|
||||
|
||||
### Server-side / Custom (ItemsProvider)
|
||||
|
||||
```razor
|
||||
<FluentDataGrid ItemsProvider="@peopleProvider" TGridItem="Person">
|
||||
...
|
||||
</FluentDataGrid>
|
||||
|
||||
@code {
|
||||
private GridItemsProvider<Person> peopleProvider = async request =>
|
||||
{
|
||||
var result = await PeopleService.GetPeopleAsync(
|
||||
request.StartIndex,
|
||||
request.Count ?? 50,
|
||||
request.GetSortByProperties().FirstOrDefault());
|
||||
|
||||
return GridItemsProviderResult.From(result.Items, result.TotalCount);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### EF Core Adapter
|
||||
|
||||
```csharp
|
||||
// Program.cs
|
||||
builder.Services.AddDataGridEntityFrameworkAdapter();
|
||||
```
|
||||
|
||||
```razor
|
||||
<FluentDataGrid Items="@dbContext.People" TGridItem="Person">
|
||||
...
|
||||
</FluentDataGrid>
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
```razor
|
||||
<FluentDataGrid Items="@people" Pagination="@pagination" TGridItem="Person">
|
||||
...
|
||||
</FluentDataGrid>
|
||||
|
||||
<FluentPaginator State="@pagination" />
|
||||
|
||||
@code {
|
||||
private PaginationState pagination = new() { ItemsPerPage = 10 };
|
||||
}
|
||||
```
|
||||
|
||||
## Virtualization
|
||||
|
||||
For large datasets, enable virtualization:
|
||||
|
||||
```razor
|
||||
<FluentDataGrid Items="@people" Virtualize="true" ItemSize="46" TGridItem="Person">
|
||||
...
|
||||
</FluentDataGrid>
|
||||
```
|
||||
|
||||
`ItemSize` is the estimated row height in pixels (default varies). Important for scroll position calculations.
|
||||
|
||||
## Key Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `Items` | `IQueryable<TGridItem>?` | In-memory data source |
|
||||
| `ItemsProvider` | `GridItemsProvider<TGridItem>?` | Async data provider |
|
||||
| `Pagination` | `PaginationState?` | Pagination state |
|
||||
| `Virtualize` | `bool` | Enable virtualization |
|
||||
| `ItemSize` | `float` | Estimated row height (px) |
|
||||
| `ItemKey` | `Func<TGridItem, object>?` | Stable key for `@key` |
|
||||
| `ResizableColumns` | `bool` | Enable column resize |
|
||||
| `HeaderCellAsButtonWithMenu` | `bool` | Sortable header UI |
|
||||
| `GridTemplateColumns` | `string?` | CSS grid-template-columns |
|
||||
| `Loading` | `bool` | Show loading indicator |
|
||||
| `ShowHover` | `bool` | Highlight rows on hover |
|
||||
| `OnRowClick` | `EventCallback<FluentDataGridRow<TGridItem>>` | Row click handler |
|
||||
| `OnRowDoubleClick` | `EventCallback<FluentDataGridRow<TGridItem>>` | Row double-click handler |
|
||||
| `OnRowFocus` | `EventCallback<FluentDataGridRow<TGridItem>>` | Row focus handler |
|
||||
|
||||
## Sorting
|
||||
|
||||
```razor
|
||||
<PropertyColumn Property="@(p => p.Name)" Sortable="true" IsDefaultSortColumn="true"
|
||||
InitialSortDirection="SortDirection.Ascending" />
|
||||
```
|
||||
|
||||
Or with a custom sort:
|
||||
|
||||
```razor
|
||||
<TemplateColumn Title="Full Name" SortBy="@(GridSort<Person>.ByAscending(p => p.LastName).ThenAscending(p => p.FirstName))">
|
||||
@context.LastName, @context.FirstName
|
||||
</TemplateColumn>
|
||||
```
|
||||
@@ -0,0 +1,173 @@
|
||||
# Layout and Navigation
|
||||
|
||||
## Layout Components
|
||||
|
||||
### FluentLayout
|
||||
|
||||
Root layout container. Use as the outermost structural component.
|
||||
|
||||
```razor
|
||||
<FluentLayout Orientation="Orientation.Vertical">
|
||||
<FluentHeader>...</FluentHeader>
|
||||
<FluentBodyContent>...</FluentBodyContent>
|
||||
<FluentFooter>...</FluentFooter>
|
||||
</FluentLayout>
|
||||
```
|
||||
|
||||
### FluentHeader / FluentFooter
|
||||
|
||||
Sticky header and footer sections within `FluentLayout`.
|
||||
|
||||
```razor
|
||||
<FluentHeader Height="50">
|
||||
<FluentStack Orientation="Orientation.Horizontal" HorizontalAlignment="HorizontalAlignment.SpaceBetween">
|
||||
<span>App Title</span>
|
||||
<FluentButton>Settings</FluentButton>
|
||||
</FluentStack>
|
||||
</FluentHeader>
|
||||
```
|
||||
|
||||
### FluentBodyContent
|
||||
|
||||
Main scrollable content area within `FluentLayout`.
|
||||
|
||||
### FluentStack
|
||||
|
||||
Flexbox container for horizontal or vertical layouts.
|
||||
|
||||
```razor
|
||||
<FluentStack Orientation="Orientation.Horizontal"
|
||||
HorizontalGap="10"
|
||||
VerticalGap="10"
|
||||
HorizontalAlignment="HorizontalAlignment.Center"
|
||||
VerticalAlignment="VerticalAlignment.Center"
|
||||
Wrap="true"
|
||||
Width="100%">
|
||||
<FluentButton>One</FluentButton>
|
||||
<FluentButton>Two</FluentButton>
|
||||
</FluentStack>
|
||||
```
|
||||
|
||||
Parameters: `Orientation`, `HorizontalGap`, `VerticalGap`, `HorizontalAlignment`, `VerticalAlignment`, `Wrap`, `Width`.
|
||||
|
||||
### FluentGrid / FluentGridItem
|
||||
|
||||
12-column responsive grid system.
|
||||
|
||||
```razor
|
||||
<FluentGrid Spacing="3" Justify="JustifyContent.Center" AdaptiveRendering="true">
|
||||
<FluentGridItem xs="12" sm="6" md="4" lg="3">
|
||||
Card 1
|
||||
</FluentGridItem>
|
||||
<FluentGridItem xs="12" sm="6" md="4" lg="3">
|
||||
Card 2
|
||||
</FluentGridItem>
|
||||
</FluentGrid>
|
||||
```
|
||||
|
||||
Size parameters (`xs`, `sm`, `md`, `lg`, `xl`, `xxl`) represent column spans out of 12. Use `AdaptiveRendering="true"` to hide items that don't fit.
|
||||
|
||||
### FluentMainLayout (convenience)
|
||||
|
||||
Pre-composed layout with header, nav menu, and body area.
|
||||
|
||||
```razor
|
||||
<FluentMainLayout Header="@header"
|
||||
SubHeader="@subheader"
|
||||
NavMenuContent="@navMenu"
|
||||
Body="@body"
|
||||
HeaderHeight="50"
|
||||
NavMenuWidth="250"
|
||||
NavMenuTitle="Navigation" />
|
||||
```
|
||||
|
||||
## Navigation Components
|
||||
|
||||
### FluentNavMenu
|
||||
|
||||
Collapsible navigation menu with keyboard support.
|
||||
|
||||
```razor
|
||||
<FluentNavMenu Width="250"
|
||||
Collapsible="true"
|
||||
@bind-Expanded="@menuExpanded"
|
||||
Title="Main navigation"
|
||||
CollapsedChildNavigation="true"
|
||||
Margin="4px 0">
|
||||
<FluentNavLink Href="/" Icon="@(Icons.Regular.Size20.Home)" Match="NavLinkMatch.All">
|
||||
Home
|
||||
</FluentNavLink>
|
||||
<FluentNavLink Href="/counter" Icon="@(Icons.Regular.Size20.NumberSymbol)">
|
||||
Counter
|
||||
</FluentNavLink>
|
||||
<FluentNavGroup Title="Admin" Icon="@(Icons.Regular.Size20.Shield)" @bind-Expanded="@adminExpanded">
|
||||
<FluentNavLink Href="/admin/users">Users</FluentNavLink>
|
||||
<FluentNavLink Href="/admin/roles">Roles</FluentNavLink>
|
||||
</FluentNavGroup>
|
||||
</FluentNavMenu>
|
||||
```
|
||||
|
||||
Key parameters:
|
||||
- `Width` — width in pixels (40px when collapsed)
|
||||
- `Collapsible` — enables expand/collapse toggle
|
||||
- `Expanded` / `ExpandedChanged` — bindable collapse state
|
||||
- `CollapsedChildNavigation` — shows flyout menus for groups when collapsed
|
||||
- `CustomToggle` — for mobile hamburger button patterns
|
||||
- `Title` — aria-label for accessibility
|
||||
|
||||
### FluentNavGroup
|
||||
|
||||
Expandable group within a nav menu.
|
||||
|
||||
```razor
|
||||
<FluentNavGroup Title="Settings"
|
||||
Icon="@(Icons.Regular.Size20.Settings)"
|
||||
@bind-Expanded="@settingsExpanded"
|
||||
Gap="2">
|
||||
<FluentNavLink Href="/settings/general">General</FluentNavLink>
|
||||
<FluentNavLink Href="/settings/profile">Profile</FluentNavLink>
|
||||
</FluentNavGroup>
|
||||
```
|
||||
|
||||
Parameters: `Title`, `Expanded`/`ExpandedChanged`, `Icon`, `IconColor`, `HideExpander`, `Gap`, `MaxHeight`, `TitleTemplate`.
|
||||
|
||||
### FluentNavLink
|
||||
|
||||
Navigation link with active state tracking.
|
||||
|
||||
```razor
|
||||
<FluentNavLink Href="/page"
|
||||
Icon="@(Icons.Regular.Size20.Document)"
|
||||
Match="NavLinkMatch.Prefix"
|
||||
Target="_blank"
|
||||
Disabled="false">
|
||||
Page Title
|
||||
</FluentNavLink>
|
||||
```
|
||||
|
||||
Parameters: `Href`, `Target`, `Match` (`NavLinkMatch.Prefix` default, or `All`), `ActiveClass`, `Icon`, `IconColor`, `Disabled`, `Tooltip`.
|
||||
|
||||
All nav components inherit from `FluentNavBase` which provides: `Icon`, `IconColor`, `CustomColor`, `Disabled`, `Tooltip`.
|
||||
|
||||
### FluentBreadcrumb / FluentBreadcrumbItem
|
||||
|
||||
```razor
|
||||
<FluentBreadcrumb>
|
||||
<FluentBreadcrumbItem Href="/">Home</FluentBreadcrumbItem>
|
||||
<FluentBreadcrumbItem Href="/products">Products</FluentBreadcrumbItem>
|
||||
<FluentBreadcrumbItem>Current Page</FluentBreadcrumbItem>
|
||||
</FluentBreadcrumb>
|
||||
```
|
||||
|
||||
### FluentTab / FluentTabs
|
||||
|
||||
```razor
|
||||
<FluentTabs @bind-ActiveTabId="@activeTab">
|
||||
<FluentTab Id="tab1" Label="Details">
|
||||
Details content
|
||||
</FluentTab>
|
||||
<FluentTab Id="tab2" Label="History">
|
||||
History content
|
||||
</FluentTab>
|
||||
</FluentTabs>
|
||||
```
|
||||
@@ -0,0 +1,129 @@
|
||||
# Setup and Configuration
|
||||
|
||||
## NuGet Packages
|
||||
|
||||
| Package | Purpose |
|
||||
|---|---|
|
||||
| `Microsoft.FluentUI.AspNetCore.Components` | Core component library (required) |
|
||||
| `Microsoft.FluentUI.AspNetCore.Components.Icons` | Icon package (optional, recommended) |
|
||||
| `Microsoft.FluentUI.AspNetCore.Components.Emojis` | Emoji package (optional) |
|
||||
| `Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapter` | EF Core adapter for DataGrid (optional) |
|
||||
| `Microsoft.FluentUI.AspNetCore.Components.DataGrid.ODataAdapter` | OData adapter for DataGrid (optional) |
|
||||
|
||||
## Program.cs Registration
|
||||
|
||||
```csharp
|
||||
builder.Services.AddFluentUIComponents();
|
||||
```
|
||||
|
||||
### Configuration Options (LibraryConfiguration)
|
||||
|
||||
| Property | Type | Default | Notes |
|
||||
|---|---|---|---|
|
||||
| `UseTooltipServiceProvider` | `bool` | `true` | Registers `ITooltipService`. If true, you MUST add `<FluentTooltipProvider>` to layout |
|
||||
| `RequiredLabel` | `MarkupString` | Red `*` | Custom markup for required field indicators |
|
||||
| `HideTooltipOnCursorLeave` | `bool` | `false` | Close tooltip when cursor leaves both anchor and tooltip |
|
||||
| `ServiceLifetime` | `ServiceLifetime` | `Scoped` | Only `Scoped` or `Singleton`. `Transient` throws! |
|
||||
| `ValidateClassNames` | `bool` | `true` | Validates CSS class names against `^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$` |
|
||||
| `CollocatedJavaScriptQueryString` | `Func<string, string>?` | `v={version}` | Cache-busting for JS files |
|
||||
|
||||
### ServiceLifetime by hosting model
|
||||
|
||||
| Hosting model | ServiceLifetime |
|
||||
|---|---|
|
||||
| Blazor Server | `Scoped` (default) |
|
||||
| Blazor WebAssembly Standalone | `Singleton` |
|
||||
| Blazor Web App (Interactive) | `Scoped` (default) |
|
||||
| Blazor Hybrid (MAUI) | `Singleton` |
|
||||
|
||||
## MainLayout.razor Template
|
||||
|
||||
```razor
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<FluentLayout>
|
||||
<FluentHeader Height="50">
|
||||
My App
|
||||
</FluentHeader>
|
||||
|
||||
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="0" Style="height: 100%;">
|
||||
<FluentNavMenu Width="250" Collapsible="true" Title="Navigation">
|
||||
<FluentNavLink Href="/" Icon="@(Icons.Regular.Size20.Home)" Match="NavLinkMatch.All">Home</FluentNavLink>
|
||||
<FluentNavLink Href="/counter" Icon="@(Icons.Regular.Size20.NumberSymbol)">Counter</FluentNavLink>
|
||||
<FluentNavGroup Title="Settings" Icon="@(Icons.Regular.Size20.Settings)">
|
||||
<FluentNavLink Href="/settings/general">General</FluentNavLink>
|
||||
<FluentNavLink Href="/settings/profile">Profile</FluentNavLink>
|
||||
</FluentNavGroup>
|
||||
</FluentNavMenu>
|
||||
|
||||
<FluentBodyContent>
|
||||
<FluentStack Orientation="Orientation.Vertical" Style="padding: 1rem;">
|
||||
@Body
|
||||
</FluentStack>
|
||||
</FluentBodyContent>
|
||||
</FluentStack>
|
||||
</FluentLayout>
|
||||
|
||||
@* Required providers — place after FluentLayout *@
|
||||
<FluentToastProvider />
|
||||
<FluentDialogProvider />
|
||||
<FluentMessageBarProvider />
|
||||
<FluentTooltipProvider />
|
||||
<FluentKeyCodeProvider />
|
||||
|
||||
@* Theme — place at root *@
|
||||
<FluentDesignTheme Mode="DesignThemeModes.System"
|
||||
OfficeColor="OfficeColor.Teams"
|
||||
StorageName="mytheme" />
|
||||
```
|
||||
|
||||
Or use the convenience component:
|
||||
|
||||
```razor
|
||||
<FluentMainLayout Header="@header"
|
||||
NavMenuContent="@navMenu"
|
||||
Body="@body"
|
||||
HeaderHeight="50"
|
||||
NavMenuWidth="250"
|
||||
NavMenuTitle="Navigation" />
|
||||
|
||||
@code {
|
||||
private RenderFragment header = @<span>My App</span>;
|
||||
private RenderFragment navMenu = @<div>
|
||||
<FluentNavLink Href="/">Home</FluentNavLink>
|
||||
</div>;
|
||||
private RenderFragment body = @<div>@Body</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## _Imports.razor
|
||||
|
||||
Add this to your `_Imports.razor`:
|
||||
|
||||
```razor
|
||||
@using Microsoft.FluentUI.AspNetCore.Components
|
||||
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||
```
|
||||
|
||||
## Static Web Assets
|
||||
|
||||
No manual `<link>` or `<script>` tags are needed. The library uses:
|
||||
- **CSS**: `reboot.css` (normalization) + component-scoped CSS — auto-loaded via static web assets
|
||||
- **JS**: `lib.module.js` — auto-loaded via Blazor's JS initializer system
|
||||
- Component-specific JS (e.g. DataGrid, Autocomplete) — lazy-loaded on demand
|
||||
|
||||
All served from `_content/Microsoft.FluentUI.AspNetCore.Components/`.
|
||||
|
||||
## Services Registered
|
||||
|
||||
Services automatically registered by `AddFluentUIComponents()`:
|
||||
|
||||
| Service | Implementation | Purpose |
|
||||
|---|---|---|
|
||||
| `GlobalState` | `GlobalState` | Shared application state |
|
||||
| `IToastService` | `ToastService` | Toast notifications (needs `FluentToastProvider`) |
|
||||
| `IDialogService` | `DialogService` | Dialogs and panels (needs `FluentDialogProvider`) |
|
||||
| `IMessageService` | `MessageService` | Message bars (needs `FluentMessageBarProvider`) |
|
||||
| `IKeyCodeService` | `KeyCodeService` | Keyboard shortcuts (needs `FluentKeyCodeProvider`) |
|
||||
| `IMenuService` | `MenuService` | Context menus |
|
||||
| `ITooltipService` | `TooltipService` | Tooltips (needs `FluentTooltipProvider`, opt-in via `UseTooltipServiceProvider`) |
|
||||
@@ -0,0 +1,103 @@
|
||||
# Theming
|
||||
|
||||
## FluentDesignTheme (recommended)
|
||||
|
||||
The primary theming component. Place it at the root of your app.
|
||||
|
||||
```razor
|
||||
<FluentDesignTheme Mode="DesignThemeModes.System"
|
||||
OfficeColor="OfficeColor.Teams"
|
||||
StorageName="mytheme" />
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Mode` | `DesignThemeModes` | `System` | `Light`, `Dark`, or `System` (follows OS) |
|
||||
| `CustomColor` | `string?` | null | Hex accent color (e.g. `"#0078D4"`) |
|
||||
| `OfficeColor` | `OfficeColor?` | null | Preset accent: `Teams`, `Word`, `Excel`, `PowerPoint`, `Outlook`, `OneNote` |
|
||||
| `NeutralBaseColor` | `string?` | null | Neutral palette base hex color |
|
||||
| `StorageName` | `string?` | null | Persist theme to localStorage under this key |
|
||||
| `Direction` | `LocalizationDirection?` | null | `Ltr` or `Rtl` |
|
||||
| `OnLuminanceChanged` | `EventCallback<LuminanceChangedEventArgs>` | | Fired when dark/light mode changes |
|
||||
| `OnLoaded` | `EventCallback<LoadedEventArgs>` | | Fired when theme is loaded from storage |
|
||||
|
||||
### Two-way binding
|
||||
|
||||
```razor
|
||||
<FluentDesignTheme @bind-Mode="@themeMode"
|
||||
@bind-OfficeColor="@officeColor"
|
||||
@bind-CustomColor="@customColor"
|
||||
StorageName="mytheme" />
|
||||
|
||||
<FluentSelect Items="@(Enum.GetValues<DesignThemeModes>())"
|
||||
@bind-SelectedOption="@themeMode"
|
||||
OptionText="@(m => m.ToString())" />
|
||||
|
||||
@code {
|
||||
private DesignThemeModes themeMode = DesignThemeModes.System;
|
||||
private OfficeColor? officeColor = OfficeColor.Teams;
|
||||
private string? customColor;
|
||||
}
|
||||
```
|
||||
|
||||
### Important: JS interop dependency
|
||||
|
||||
`FluentDesignTheme` uses JavaScript interop internally. It will NOT work during server-side pre-rendering. If you need to react to theme changes:
|
||||
|
||||
```csharp
|
||||
// Use OnAfterRenderAsync, NOT OnInitialized
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// Safe to interact with design tokens here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## FluentDesignSystemProvider (advanced)
|
||||
|
||||
For scoping design tokens to a subtree of the component tree. Provides 50+ CSS custom properties.
|
||||
|
||||
```razor
|
||||
<FluentDesignSystemProvider AccentBaseColor="#0078D4"
|
||||
NeutralBaseColor="#808080"
|
||||
BaseLayerLuminance="0.95">
|
||||
<FluentButton Appearance="Appearance.Accent">Themed Button</FluentButton>
|
||||
</FluentDesignSystemProvider>
|
||||
```
|
||||
|
||||
## Design Token Classes (DI-based, advanced)
|
||||
|
||||
For programmatic token control via dependency injection. Each token is a generated service.
|
||||
|
||||
```csharp
|
||||
@inject AccentBaseColor AccentBaseColor
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// Set token for a specific element
|
||||
await AccentBaseColor.SetValueFor(myElement, "#FF0000".ToSwatch());
|
||||
|
||||
// Read token value
|
||||
var currentColor = await AccentBaseColor.GetValueFor(myElement);
|
||||
|
||||
// Remove override
|
||||
await AccentBaseColor.DeleteValueFor(myElement);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Available DesignThemeModes
|
||||
|
||||
- `DesignThemeModes.Light` — light theme
|
||||
- `DesignThemeModes.Dark` — dark theme
|
||||
- `DesignThemeModes.System` — follows OS preference
|
||||
|
||||
## Available OfficeColor presets
|
||||
|
||||
`Teams`, `Word`, `Excel`, `PowerPoint`, `Outlook`, `OneNote`, `Loop`, `Planner`, `SharePoint`, `Stream`, `Sway`, `Viva`, `VivaEngage`, `VivaInsights`, `VivaLearning`, `VivaTopics`.
|
||||
Reference in New Issue
Block a user