1. Descriere Generală
Sistemul de navigare FOD constă din trei componente principale care lucrează împreună pentru a crea meniuri de navigare flexibile și ierarhice:
- FodNavMenu - Container principal pentru elementele de navigare
- FodNavGroup - Grup de navigare care poate fi expandat/restrâns
- FodNavLink - Link individual de navigare
Caracteristici principale:
- Navigare ierarhică pe mai multe niveluri
- Grupuri expandabile cu animații fluide
- Integrare completă cu sistemul de rutare Blazor
- Suport pentru iconițe și culori personalizate
- Design responsive pentru drawers mini
- Stilizare activă automată
- Suport pentru comenzi și evenimente
- Accesibilitate completă
2. Ghid de Utilizare API
Meniu de navigare simplu
<FodNavMenu>
<FodNavLink Href="/"
Icon="@FodIcons.Material.Filled.Home">
Acasă
</FodNavLink>
<FodNavLink Href="/products"
Icon="@FodIcons.Material.Filled.ShoppingCart">
Produse
</FodNavLink>
<FodNavLink Href="/about"
Icon="@FodIcons.Material.Filled.Info">
Despre noi
</FodNavLink>
<FodNavLink Href="/contact"
Icon="@FodIcons.Material.Filled.ContactMail">
Contact
</FodNavLink>
</FodNavMenu>
Meniu cu grupuri expandabile
<FodNavMenu Color="FodColor.Primary" Bordered="true">
<FodNavLink Href="/" Icon="@FodIcons.Material.Filled.Dashboard">
Dashboard
</FodNavLink>
<FodNavGroup Title="Administrare"
Icon="@FodIcons.Material.Filled.Settings"
Expanded="true">
<FodNavLink Href="/admin/users">Utilizatori</FodNavLink>
<FodNavLink Href="/admin/roles">Roluri</FodNavLink>
<FodNavLink Href="/admin/permissions">Permisiuni</FodNavLink>
</FodNavGroup>
<FodNavGroup Title="Rapoarte"
Icon="@FodIcons.Material.Filled.Assessment">
<FodNavLink Href="/reports/sales">Vânzări</FodNavLink>
<FodNavLink Href="/reports/inventory">Inventar</FodNavLink>
<FodNavLink Href="/reports/customers">Clienți</FodNavLink>
</FodNavGroup>
<FodNavLink Href="/settings" Icon="@FodIcons.Material.Filled.Tune">
Setări
</FodNavLink>
</FodNavMenu>
Meniu cu sub-grupuri (nested)
<FodNavMenu>
<FodNavGroup Title="Produse"
Icon="@FodIcons.Material.Filled.Inventory"
@bind-Expanded="productsExpanded">
<FodNavLink Href="/products/all">Toate produsele</FodNavLink>
<FodNavGroup Title="Categorii" Expanded="false">
<FodNavLink Href="/products/electronics">Electronice</FodNavLink>
<FodNavLink Href="/products/clothing">Îmbrăcăminte</FodNavLink>
<FodNavLink Href="/products/books">Cărți</FodNavLink>
<FodNavGroup Title="Sporturi">
<FodNavLink Href="/products/sports/outdoor">Outdoor</FodNavLink>
<FodNavLink Href="/products/sports/indoor">Indoor</FodNavLink>
<FodNavLink Href="/products/sports/water">Sporturi nautice</FodNavLink>
</FodNavGroup>
</FodNavGroup>
<FodNavLink Href="/products/promotions"
Icon="@FodIcons.Material.Filled.LocalOffer">
Promoții
</FodNavLink>
</FodNavGroup>
</FodNavMenu>
@code {
private bool productsExpanded = true;
}
Meniu în drawer lateral
<FodDrawer Variant="DrawerVariant.Persistent"
Open="true">
<FodNavMenu Color="FodColor.Primary"
Rounded="true"
Margin="FodMargin.Dense">
<div class="pa-4 text-center">
<FodIcon Icon="@FodIcons.Custom.Brands.MudBlazor"
Size="Size.Large" />
<FodText Typo="Typo.h6">Aplicația Mea</FodText>
</div>
<FodDivider Class="mb-4" />
<FodNavLink Href="/dashboard"
Icon="@FodIcons.Material.Filled.Dashboard">
Dashboard
</FodNavLink>
<FodNavGroup Title="Management"
Icon="@FodIcons.Material.Filled.ManageAccounts">
<FodNavLink Href="/users">Utilizatori</FodNavLink>
<FodNavLink Href="/teams">Echipe</FodNavLink>
<FodNavLink Href="/projects">Proiecte</FodNavLink>
</FodNavGroup>
<FodNavGroup Title="Financiar"
Icon="@FodIcons.Material.Filled.AttachMoney">
<FodNavLink Href="/invoices">Facturi</FodNavLink>
<FodNavLink Href="/payments">Plăți</FodNavLink>
<FodNavLink Href="/expenses">Cheltuieli</FodNavLink>
</FodNavGroup>
<FodDivider Class="my-4" />
<FodNavLink Href="/profile"
Icon="@FodIcons.Material.Filled.Person">
Profil
</FodNavLink>
<FodNavLink Href="/logout"
Icon="@FodIcons.Material.Filled.Logout">
Deconectare
</FodNavLink>
</FodNavMenu>
</FodDrawer>
Meniu cu acțiuni custom
<FodNavMenu>
<FodNavLink Icon="@FodIcons.Material.Filled.Add"
OnClick="@CreateNewItem">
Adaugă element nou
</FodNavLink>
<FodNavLink Icon="@FodIcons.Material.Filled.Refresh"
OnClick="@RefreshData"
Disabled="@isRefreshing">
@if (isRefreshing)
{
<FodLoadingCircular Size="Size.Small" Class="me-2" />
}
Reîmprospătează
</FodNavLink>
<FodNavGroup Title="Acțiuni"
Icon="@FodIcons.Material.Filled.PlayArrow">
<FodNavLink OnClick="@(() => ExecuteAction("export"))">
Export date
</FodNavLink>
<FodNavLink OnClick="@(() => ExecuteAction("import"))">
Import date
</FodNavLink>
<FodNavLink OnClick="@(() => ExecuteAction("backup"))">
Backup
</FodNavLink>
</FodNavGroup>
</FodNavMenu>
@code {
private bool isRefreshing = false;
private void CreateNewItem()
{
// Logică pentru creare element nou
}
private async Task RefreshData()
{
isRefreshing = true;
await DataService.RefreshAsync();
isRefreshing = false;
}
private void ExecuteAction(string action)
{
// Execută acțiunea specifică
}
}
Meniu cu stilizare personalizată
<FodNavMenu Color="FodColor.Secondary"
Bordered="true"
Rounded="true"
Dense="true">
<FodNavLink Href="/home"
Icon="@FodIcons.Material.Filled.Home"
IconColor="FodColor.Primary">
<span style="font-weight: bold;">Acasă</span>
</FodNavLink>
<FodNavGroup Title="Secțiuni"
Icon="@FodIcons.Material.Filled.Folder"
IconColor="FodColor.Warning"
HideExpandIcon="false">
<FodNavLink Href="/section1"
Class="custom-nav-link">
<FodBadge Content="3" Color="FodColor.Error" Class="me-2">
Secțiunea 1
</FodBadge>
</FodNavLink>
<FodNavLink Href="/section2">
<div class="d-flex justify-content-between align-items-center">
<span>Secțiunea 2</span>
<FodChip Size="Size.Small" Color="FodColor.Success">
Nou
</FodChip>
</div>
</FodNavLink>
</FodNavGroup>
</FodNavMenu>
Meniu cu control programatic
<FodNavMenu>
@foreach (var section in menuSections)
{
@if (section.Children?.Any() == true)
{
<FodNavGroup Title="@section.Title"
Icon="@section.Icon"
@bind-Expanded="section.IsExpanded">
@foreach (var child in section.Children)
{
<FodNavLink Href="@child.Url"
Icon="@child.Icon"
Disabled="@(!child.IsAccessible)">
@child.Title
</FodNavLink>
}
</FodNavGroup>
}
else
{
<FodNavLink Href="@section.Url"
Icon="@section.Icon"
Disabled="@(!section.IsAccessible)">
@section.Title
</FodNavLink>
}
}
</FodNavMenu>
@code {
private List<MenuSection> menuSections = new();
protected override async Task OnInitializedAsync()
{
menuSections = await MenuService.GetUserMenuAsync();
}
public class MenuSection
{
public string Title { get; set; }
public string Icon { get; set; }
public string Url { get; set; }
public bool IsAccessible { get; set; }
public bool IsExpanded { get; set; }
public List<MenuSection> Children { get; set; }
}
}
Meniu responsive pentru drawer mini
<FodDrawer @bind-Open="drawerOpen"
Variant="@DrawerVariant.Mini">
<FodNavMenu>
<FodNavLink Href="/dashboard"
Icon="@FodIcons.Material.Filled.Dashboard">
<FodTooltip Text="Dashboard" Placement="Placement.Right">
<ChildContent>
Dashboard
</ChildContent>
</FodTooltip>
</FodNavLink>
<FodNavGroup Title="Management"
Icon="@FodIcons.Material.Filled.Settings">
<!-- Conținutul grupului se afișează doar când drawer-ul e expandat -->
<FodNavLink Href="/users">Utilizatori</FodNavLink>
<FodNavLink Href="/settings">Setări</FodNavLink>
</FodNavGroup>
</FodNavMenu>
</FodDrawer>
3. Atribute disponibile
Proprietate |
Tip |
Descriere |
Valoare Implicită |
Color |
FodColor |
Culoarea pentru NavLink activ |
Default |
Bordered |
bool |
Adaugă bordură la NavLink activ |
false |
Rounded |
bool |
Aplică border-radius |
false |
Margin |
FodMargin |
Spațiere verticală între elemente |
Normal |
Dense |
bool |
Padding vertical compact |
false |
ChildContent |
RenderFragment |
Conținutul meniului |
- |
FodNavGroup
Proprietate |
Tip |
Descriere |
Valoare Implicită |
Title |
string |
Textul afișat pentru grup |
- |
Icon |
string |
Iconița grupului |
null |
IconColor |
FodColor |
Culoarea iconiței |
Default |
Expanded |
bool |
Starea expandată |
false |
ExpandedChanged |
EventCallback<bool> |
Eveniment pentru two-way binding |
- |
HideExpandIcon |
bool |
Ascunde iconița expand/collapse |
false |
MaxHeight |
int? |
Înălțime maximă pentru collapse |
null |
ExpandIcon |
string |
Iconița pentru expand |
ArrowDropDown |
ChildContent |
RenderFragment |
Conținutul grupului |
- |
Disabled |
bool |
Dezactivează grupul |
false |
FodNavLink
Proprietate |
Tip |
Descriere |
Valoare Implicită |
Href |
string |
URL-ul de navigare |
null |
Icon |
string |
Iconița link-ului |
null |
IconColor |
FodColor |
Culoarea iconiței |
Default |
Match |
NavLinkMatch |
Modul de potrivire URL |
Prefix |
Target |
string |
Target pentru link (_blank, etc) |
null |
ActiveClass |
string |
Clasa CSS când e activ |
"active" |
OnClick |
EventCallback<MouseEventArgs> |
Handler pentru click |
- |
Command |
ICommand |
Comandă pentru MVVM |
null |
CommandParameter |
object |
Parametru pentru comandă |
null |
Disabled |
bool |
Dezactivează link-ul |
false |
DisableRipple |
bool |
Dezactivează efectul ripple |
false |
ChildContent |
RenderFragment |
Conținutul link-ului |
- |
4. Evenimente
Component |
Eveniment |
Descriere |
FodNavGroup |
ExpandedChanged |
Se declanșează la schimbarea stării expand/collapse |
FodNavLink |
OnClick |
Se declanșează la click pe link |
5. Stilizare și personalizare
/* Meniu cu stil custom */
.custom-nav-menu .fod-nav-menu {
background-color: #f5f5f5;
padding: 1rem;
border-radius: 8px;
}
/* Link-uri cu hover personalizat */
.custom-nav-menu .fod-nav-link:hover {
background-color: rgba(0, 0, 0, 0.08);
transform: translateX(4px);
transition: all 0.2s ease;
}
/* Grup cu stil special */
.custom-nav-menu .fod-nav-group-header {
font-weight: bold;
text-transform: uppercase;
font-size: 0.875rem;
letter-spacing: 0.5px;
}
/* Active state personalizat */
.custom-nav-menu .fod-nav-link.active {
background: linear-gradient(90deg, var(--fod-palette-primary-main) 0%, var(--fod-palette-primary-light) 100%);
color: white;
font-weight: 600;
}
/* Iconițe colorate */
.colorful-nav .fod-nav-link-icon {
color: var(--fod-palette-primary-main);
}
.colorful-nav .fod-nav-link:hover .fod-nav-link-icon {
color: var(--fod-palette-secondary-main);
}
/* Animație pentru expand */
.smooth-nav .fod-collapse-container {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Badge-uri în meniu */
.nav-with-badges .fod-nav-link {
position: relative;
}
.nav-with-badges .notification-badge {
position: absolute;
top: 8px;
right: 8px;
background-color: var(--fod-palette-error-main);
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
}
6. Integrare cu alte componente
În Layout principal
<FodLayout>
<FodAppBar Position="Position.Fixed">
<FodIconButton Icon="@FodIcons.Material.Filled.Menu"
OnClick="ToggleDrawer" />
<FodText Typo="Typo.h6">Aplicația Mea</FodText>
</FodAppBar>
<FodDrawer @bind-Open="drawerOpen"
Variant="DrawerVariant.Temporary">
<FodNavMenu>
<!-- Conținut meniu -->
</FodNavMenu>
</FodDrawer>
<FodMainContent>
@Body
</FodMainContent>
</FodLayout>
Cu sistem de autentificare
<FodNavMenu>
<AuthorizeView>
<Authorized>
<FodNavLink Href="/dashboard" Icon="@FodIcons.Material.Filled.Dashboard">
Dashboard
</FodNavLink>
<AuthorizeView Roles="Admin">
<FodNavGroup Title="Administrare" Icon="@FodIcons.Material.Filled.AdminPanelSettings">
<FodNavLink Href="/admin/users">Utilizatori</FodNavLink>
<FodNavLink Href="/admin/settings">Setări</FodNavLink>
</FodNavGroup>
</AuthorizeView>
<FodNavLink Href="/profile" Icon="@FodIcons.Material.Filled.Person">
@context.User.Identity.Name
</FodNavLink>
<FodNavLink OnClick="Logout" Icon="@FodIcons.Material.Filled.Logout">
Deconectare
</FodNavLink>
</Authorized>
<NotAuthorized>
<FodNavLink Href="/login" Icon="@FodIcons.Material.Filled.Login">
Autentificare
</FodNavLink>
<FodNavLink Href="/register" Icon="@FodIcons.Material.Filled.PersonAdd">
Înregistrare
</FodNavLink>
</NotAuthorized>
</AuthorizeView>
</FodNavMenu>
7. Patterns comune
Meniu cu căutare
<FodNavMenu>
<div class="pa-3">
<FodTextField @bind-Value="searchTerm"
Placeholder="Caută în meniu..."
Adornment="Adornment.Start"
AdornmentIcon="@FodIcons.Material.Filled.Search"
Immediate="true"
DebounceInterval="300" />
</div>
<FodDivider />
@foreach (var item in FilteredMenuItems)
{
@if (item.IsGroup)
{
<FodNavGroup Title="@item.Title" Icon="@item.Icon">
@foreach (var child in item.Children)
{
<FodNavLink Href="@child.Href">@child.Title</FodNavLink>
}
</FodNavGroup>
}
else
{
<FodNavLink Href="@item.Href" Icon="@item.Icon">
@item.Title
</FodNavLink>
}
}
</FodNavMenu>
@code {
private string searchTerm = "";
private IEnumerable<MenuItem> FilteredMenuItems =>
string.IsNullOrWhiteSpace(searchTerm)
? allMenuItems
: allMenuItems.Where(item =>
item.Title.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) ||
item.Children?.Any(c => c.Title.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) == true);
}
Meniu cu state persistent
@inject ILocalStorageService LocalStorage
<FodNavMenu>
@foreach (var group in navGroups)
{
<FodNavGroup Title="@group.Title"
Icon="@group.Icon"
@bind-Expanded="group.Expanded"
ExpandedChanged="@(() => SaveMenuState())">
@foreach (var link in group.Links)
{
<FodNavLink Href="@link.Href">@link.Title</FodNavLink>
}
</FodNavGroup>
}
</FodNavMenu>
@code {
private List<NavGroupState> navGroups = new();
protected override async Task OnInitializedAsync()
{
// Încarcă starea salvată
var savedState = await LocalStorage.GetItemAsync<List<NavGroupState>>("navMenuState");
if (savedState != null)
{
navGroups = savedState;
}
else
{
// Inițializare implicită
InitializeDefaultMenu();
}
}
private async Task SaveMenuState()
{
await LocalStorage.SetItemAsync("navMenuState", navGroups);
}
}
- NavLink-urile inactive nu procesează evenimente
- Grupurile collapse folosesc animații CSS eficiente
- Evitați re-render prin păstrarea referințelor stabile
- Pentru meniuri foarte mari, implementați virtualizare
9. Accesibilitate
- Suport complet pentru navigare cu tastatură
- ARIA labels pentru screen readers
- Focus management corect
- Anunțuri pentru schimbări de stare
- Contrast adecvat pentru text și iconițe
10. Bune practici
- Organizare logică - Grupați elementele înrudite
- Iconițe consistente - Folosiți același stil de iconițe
- Feedback vizual - Indicați clar elementul activ
- Loading states - Pentru meniuri dinamice
- Responsive design - Testați cu drawer mini
- Limite rezonabile - Nu depășiți 3 niveluri de adâncime
11. Troubleshooting
Link-ul activ nu se evidențiază
- Verificați că Href corespunde cu URL-ul curent
- Verificați NavLinkMatch (Prefix vs All)
- Verificați că Color este setat pe NavMenu
Grupurile nu se expandează
- Verificați că Expanded binding funcționează
- Verificați consolă pentru erori JavaScript
- Verificați că FodCollapse este disponibil
Iconițele nu se afișează
- Verificați că fontul de iconițe este încărcat
- Verificați sintaxa iconiței
- Verificați IconColor pentru contrast
12. Limitări cunoscute
- Nu suportă drag & drop pentru reordonare
- Nu are suport built-in pentru breadcrumbs
- Animațiile nu pot fi personalizate ușor
- Nu suportă lazy loading pentru grupuri mari
13. Exemple avansate
Meniu cu multi-tenant
<FodNavMenu>
<!-- Selector tenant -->
<div class="pa-3">
<FodSelect @bind-Value="selectedTenant"
Label="Organizație"
FullWidth="true"
ValueChanged="OnTenantChanged">
@foreach (var tenant in userTenants)
{
<FodSelectItem Value="@tenant.Id">
@tenant.Name
</FodSelectItem>
}
</FodSelect>
</div>
<FodDivider Class="mb-3" />
<!-- Meniu specific tenant-ului -->
@if (tenantMenu != null)
{
@foreach (var section in tenantMenu.Sections)
{
<FodNavGroup Title="@section.Title"
Icon="@section.Icon"
Disabled="@(!section.IsEnabled)">
@foreach (var item in section.Items)
{
<FodNavLink Href="@($"/tenant/{selectedTenant}/{item.Route}")"
Icon="@item.Icon"
Disabled="@(!item.HasAccess)">
@item.Title
@if (!item.HasAccess)
{
<FodTooltip Text="Necesită upgrade plan">
<FodIcon Icon="@FodIcons.Material.Filled.Lock"
Size="Size.Small"
Class="ms-2" />
</FodTooltip>
}
</FodNavLink>
}
</FodNavGroup>
}
}
</FodNavMenu>
@code {
private Guid selectedTenant;
private List<Tenant> userTenants = new();
private TenantMenu tenantMenu;
private async Task OnTenantChanged(Guid tenantId)
{
selectedTenant = tenantId;
tenantMenu = await MenuService.GetTenantMenuAsync(tenantId);
}
}
14. Concluzie
Componentele FodNavMenu oferă un sistem complet și flexibil pentru crearea meniurilor de navigare în aplicații Blazor. Cu suport pentru ierarhii complexe, stilizare extensivă și integrare perfectă cu sistemul de rutare, acestea sunt esențiale pentru orice aplicație care necesită navigare structurată și intuitivă.