Popover
Documentație pentru componentele FodPopover și FodPopoverProvider
1. Descriere Generală
Sistemul Popover din FOD oferă o infrastructură completă pentru afișarea conținutului flotant poziționat relativ la un element ancoră. Sistemul este format din două componente principale:
- FodPopover - Componenta principală pentru definirea popover-ului
- FodPopoverProvider - Provider global care gestionează toate popover-urile din aplicație
Caracteristici principale:
- Poziționare inteligentă cu overflow behavior
- Suport pentru multiple origini de ancorare
- Animații fluide cu durată și întârziere configurabile
- Paper styling cu elevație personalizabilă
- Suport pentru lățime relativă la părinte
- Management centralizat prin service
- Integrare cu JSInterop pentru poziționare precisă
- Suport RTL (Right-to-Left)
2. Configurare inițială
Adăugarea FodPopoverProvider
<!-- În MainLayout.razor sau App.razor -->
<FodPopoverProvider />
@Body
Înregistrarea serviciului
// În Program.cs
builder.Services.AddFodComponents();
// sau specific pentru popover
builder.Services.AddScoped<IFodPopoverService, FodPopoverService>();
3. Ghid de Utilizare API
Popover simplu
<div style="position: relative;">
<FodButton @onclick="@(() => showPopover = !showPopover)">
Deschide Popover
</FodButton>
<FodPopover Open="@showPopover">
<FodPaper Class="pa-4">
<FodText>Acesta este conținutul popover-ului</FodText>
</FodPaper>
</FodPopover>
</div>
@code {
private bool showPopover = false;
}
Popover cu poziționare personalizată
<div style="position: relative; display: inline-block;">
<FodButton @onclick="TogglePopover">
Popover cu poziționare
</FodButton>
<FodPopover Open="@isOpen"
AnchorOrigin="Origin.BottomCenter"
TransformOrigin="Origin.TopCenter">
<FodList Class="py-0" Style="min-width: 200px;">
<FodListItem Button="true" OnClick="@(() => SelectOption(1))">
<FodListItemText Primary="Opțiunea 1" />
</FodListItem>
<FodListItem Button="true" OnClick="@(() => SelectOption(2))">
<FodListItemText Primary="Opțiunea 2" />
</FodListItem>
<FodListItem Button="true" OnClick="@(() => SelectOption(3))">
<FodListItemText Primary="Opțiunea 3" />
</FodListItem>
</FodList>
</FodPopover>
</div>
@code {
private bool isOpen = false;
private void TogglePopover() => isOpen = !isOpen;
private void SelectOption(int option)
{
Console.WriteLine($"Selected: {option}");
isOpen = false;
}
}
Popover cu overflow behavior
<div style="position: relative;">
<FodIconButton Icon="@FodIcons.Material.Filled.MoreVert"
@onclick="@(() => showMenu = !showMenu)" />
<FodPopover Open="@showMenu"
AnchorOrigin="Origin.TopRight"
TransformOrigin="Origin.TopLeft"
OverflowBehavior="OverflowBehavior.FlipAlways">
<FodPaper Elevation="8" Class="pa-2" Style="min-width: 150px;">
<FodList Dense="true">
<FodListItem Button="true" OnClick="Edit">
<FodListItemIcon>
<FodIcon Icon="@FodIcons.Material.Filled.Edit" />
</FodListItemIcon>
<FodListItemText Primary="Editează" />
</FodListItem>
<FodListItem Button="true" OnClick="Delete">
<FodListItemIcon>
<FodIcon Icon="@FodIcons.Material.Filled.Delete" />
</FodListItemIcon>
<FodListItemText Primary="Șterge" />
</FodListItem>
</FodList>
</FodPaper>
</FodPopover>
</div>
Popover cu animații personalizate
<FodPopover Open="@showAnimated"
Duration="400"
Delay="100"
AnchorOrigin="Origin.BottomLeft"
TransformOrigin="Origin.TopLeft">
<FodCard Style="width: 300px;">
<FodCardContent>
<FodText Typo="Typo.h6" GutterBottom="true">
Notificare animată
</FodText>
<FodText Typo="Typo.body2">
Acest popover apare cu o animație personalizată.
</FodText>
</FodCardContent>
<FodCardActions>
<FodButton Color="FodColor.Primary" OnClick="@(() => showAnimated = false)">
Închide
</FodButton>
</FodCardActions>
</FodCard>
</FodPopover>
Popover cu lățime relativă
<div style="position: relative; width: 400px;">
<FodTextField Label="Caută"
@bind-Value="searchText"
@onfocus="@(() => showResults = true)"
@onblur="@(() => showResults = false)" />
<FodPopover Open="@showResults"
RelativeWidth="true"
AnchorOrigin="Origin.BottomLeft"
TransformOrigin="Origin.TopLeft"
MaxHeight="300">
<FodPaper>
<FodList>
@foreach (var result in SearchResults)
{
<FodListItem Button="true">
<FodListItemText Primary="@result" />
</FodListItem>
}
</FodList>
</FodPaper>
</FodPopover>
</div>
@code {
private string searchText = "";
private bool showResults = false;
private List<string> SearchResults = new()
{
"Rezultat 1",
"Rezultat 2",
"Rezultat 3"
};
}
Popover fix (pentru modal-uri)
<FodPopover Open="@showFixed"
Fixed="true"
Paper="true"
Elevation="24"
AnchorOrigin="Origin.CenterCenter"
TransformOrigin="Origin.CenterCenter">
<div style="width: 400px; max-width: 90vw;">
<FodDialogTitle>
Dialog în Popover
</FodDialogTitle>
<FodDialogContent>
<FodText>
Acest popover folosește poziționare fixă și este centrat pe ecran.
</FodText>
</FodDialogContent>
<FodDialogActions>
<FodButton OnClick="@(() => showFixed = false)">Anulează</FodButton>
<FodButton Color="FodColor.Primary" Variant="FodVariant.Filled">
Confirmă
</FodButton>
</FodDialogActions>
</div>
</FodPopover>
Meniu contextual cu popover
<div @oncontextmenu="ShowContextMenu"
@oncontextmenu:preventDefault="true"
style="position: relative; padding: 40px; border: 1px dashed #ccc;">
<FodText>Click dreapta pentru meniu contextual</FodText>
<FodPopover Open="@showContext"
AnchorOrigin="Origin.TopLeft"
TransformOrigin="Origin.TopLeft"
Style="@($"position: fixed; left: {mouseX}px; top: {mouseY}px;")">
<FodPaper Elevation="8">
<FodList Dense="true" Style="min-width: 180px;">
<FodListItem Button="true" OnClick="Copy">
<FodListItemIcon>
<FodIcon Icon="@FodIcons.Material.Filled.ContentCopy" />
</FodListItemIcon>
<FodListItemText Primary="Copiază" />
</FodListItem>
<FodListItem Button="true" OnClick="Paste">
<FodListItemIcon>
<FodIcon Icon="@FodIcons.Material.Filled.ContentPaste" />
</FodListItemIcon>
<FodListItemText Primary="Lipește" />
</FodListItem>
<FodDivider />
<FodListItem Button="true" OnClick="Delete">
<FodListItemIcon>
<FodIcon Icon="@FodIcons.Material.Filled.Delete" Color="FodColor.Error" />
</FodListItemIcon>
<FodListItemText Primary="Șterge" PrimaryTypographyProps="@(new { Color = FodColor.Error })" />
</FodListItem>
</FodList>
</FodPaper>
</FodPopover>
</div>
@code {
private bool showContext = false;
private double mouseX = 0;
private double mouseY = 0;
private void ShowContextMenu(MouseEventArgs e)
{
mouseX = e.ClientX;
mouseY = e.ClientY;
showContext = true;
}
}
<div style="position: relative;">
<FodIconButton Icon="@FodIcons.Material.Filled.PersonAdd"
Color="FodColor.Primary"
@onclick="@(() => showAddUser = !showAddUser)" />
<FodPopover Open="@showAddUser"
AnchorOrigin="Origin.BottomRight"
TransformOrigin="Origin.TopRight"
Paper="true"
Elevation="8">
<FodForm Style="width: 300px;" Class="pa-4">
<FodText Typo="Typo.h6" GutterBottom="true">
Adaugă utilizator
</FodText>
<FodTextField Label="Nume"
@bind-Value="newUser.Name"
FullWidth="true"
Margin="Margin.Dense" />
<FodTextField Label="Email"
@bind-Value="newUser.Email"
Type="InputType.Email"
FullWidth="true"
Margin="Margin.Dense"
Class="mt-3" />
<FodSelect T="string"
Label="Rol"
@bind-Value="newUser.Role"
FullWidth="true"
Margin="Margin.Dense"
Class="mt-3">
<FodSelectItem Value="@("admin")">Administrator</FodSelectItem>
<FodSelectItem Value="@("user")">Utilizator</FodSelectItem>
<FodSelectItem Value="@("guest")">Vizitator</FodSelectItem>
</FodSelect>
<div class="d-flex justify-end mt-4 gap-2">
<FodButton OnClick="@(() => showAddUser = false)">
Anulează
</FodButton>
<FodButton Color="FodColor.Primary"
Variant="FodVariant.Filled"
OnClick="SaveUser">
Salvează
</FodButton>
</div>
</FodForm>
</FodPopover>
</div>
@code {
private bool showAddUser = false;
private UserModel newUser = new();
private void SaveUser()
{
// Salvare utilizator
showAddUser = false;
newUser = new();
}
}
4. Atribute disponibile
FodPopover
Proprietate |
Tip |
Descriere |
Valoare Implicită |
Open |
bool |
Controlează vizibilitatea popover-ului |
false |
Paper |
bool |
Aplică stiluri FodPaper |
true |
Elevation |
int |
Nivelul umbrei (0-24) |
8 |
Square |
bool |
Elimină border-radius |
false |
Fixed |
bool |
Folosește position fixed în loc de absolute |
false |
Duration |
double |
Durata animației în ms |
251 |
Delay |
double |
Întârziere înainte de animație în ms |
0 |
AnchorOrigin |
Origin |
Punctul de ancorare pe elementul părinte |
TopLeft |
TransformOrigin |
Origin |
Punctul de transformare pe popover |
TopLeft |
OverflowBehavior |
OverflowBehavior |
Comportament la overflow |
FlipOnOpen |
RelativeWidth |
bool |
Popover-ul va avea lățimea părintelui |
false |
MaxHeight |
int? |
Înălțimea maximă în pixeli |
null |
ChildContent |
RenderFragment |
Conținutul popover-ului |
- |
Class |
string |
Clase CSS adiționale |
null |
Style |
string |
Stiluri inline |
null |
UserAttributes |
Dictionary<string, object> |
Atribute HTML adiționale |
null |
FodPopoverProvider
Proprietate |
Tip |
Descriere |
Valoare Implicită |
IsEnabled |
bool |
Activează/dezactivează provider-ul |
true |
5. Enumerări
Origin
Valoare |
Descriere |
TopLeft |
Colț stânga sus |
TopCenter |
Centru sus |
TopRight |
Colț dreapta sus |
CenterLeft |
Centru stânga |
CenterCenter |
Centru |
CenterRight |
Centru dreapta |
BottomLeft |
Colț stânga jos |
BottomCenter |
Centru jos |
BottomRight |
Colț dreapta jos |
OverflowBehavior
Valoare |
Descriere |
None |
Fără logică de overflow |
FlipOnOpen |
Schimbă poziția la deschidere dacă nu încape |
FlipAlways |
Verifică și schimbă poziția constant |
FlipNever |
Nu schimba niciodată poziția |
6. Serviciul IFodPopoverService
public interface IFodPopoverService
{
FodPopoverHandler Register(RenderFragment fragment);
Task<bool> Unregister(FodPopoverHandler handler);
IEnumerable<FodPopoverHandler> Handlers { get; }
Task InitializeIfNeeded();
event EventHandler FragmentsChanged;
}
7. Stilizare și personalizare
/* Popover cu umbră colorată */
.custom-popover .fod-paper {
box-shadow: 0 8px 32px rgba(63, 81, 181, 0.15) !important;
}
/* Popover cu animație de scale */
.scale-popover {
transform-origin: top center;
animation: scaleIn 0.2s ease-out;
}
@keyframes scaleIn {
from {
transform: scale(0.8);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
/* Popover transparent */
.transparent-popover {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* Popover cu bordură */
.bordered-popover .fod-paper {
border: 2px solid var(--fod-palette-primary-main);
}
/* Popover cu săgeată custom */
.arrow-popover::before {
content: '';
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid white;
}
8. Integrare cu alte componente
În Select/Dropdown
<div style="position: relative;">
<FodTextField Label="Selectează"
Value="@selectedValue"
ReadOnly="true"
@onclick="@(() => showSelect = !showSelect)"
EndAdornment="@(
<FodIcon Icon="@FodIcons.Material.Filled.ArrowDropDown" />
)" />
<FodPopover Open="@showSelect"
RelativeWidth="true"
AnchorOrigin="Origin.BottomLeft"
TransformOrigin="Origin.TopLeft">
<FodList>
@foreach (var option in options)
{
<FodListItem Button="true" OnClick="@(() => Select(option))">
<FodListItemText Primary="@option" />
</FodListItem>
}
</FodList>
</FodPopover>
</div>
@* Tooltip implementat cu Popover *@
<div style="position: relative; display: inline-block;"
@onmouseenter="@(() => showTooltip = true)"
@onmouseleave="@(() => showTooltip = false)">
<FodIconButton Icon="@FodIcons.Material.Filled.Info" />
<FodPopover Open="@showTooltip"
AnchorOrigin="Origin.BottomCenter"
TransformOrigin="Origin.TopCenter"
Paper="false"
Delay="500">
<div class="fod-tooltip">
Aceasta este o informație utilă
</div>
</FodPopover>
</div>
În Autocomplete
<div style="position: relative;">
<FodTextField Label="Caută țară"
@bind-Value="searchCountry"
@oninput="OnSearchInput"
@onfocus="@(() => showSuggestions = true)" />
<FodPopover Open="@(showSuggestions && filteredCountries.Any())"
RelativeWidth="true"
AnchorOrigin="Origin.BottomLeft"
TransformOrigin="Origin.TopLeft"
MaxHeight="200">
<FodList>
@foreach (var country in filteredCountries)
{
<FodListItem Button="true" OnClick="@(() => SelectCountry(country))">
<FodListItemText>
<HighlightText Text="@country" Highlight="@searchCountry" />
</FodListItemText>
</FodListItem>
}
</FodList>
</FodPopover>
</div>
9. Patterns comune
Popover controlat extern
@* Componenta părinte *@
<PopoverController @ref="popoverController" />
<FodButton OnClick="@(() => popoverController.Show())">
Deschide popover controlat
</FodButton>
@* Componenta PopoverController *@
@code {
private bool isOpen = false;
public void Show() => isOpen = true;
public void Hide() => isOpen = false;
public void Toggle() => isOpen = !isOpen;
}
<div style="position: relative;">
<FodPopover Open="@isOpen">
<!-- Conținut -->
</FodPopover>
</div>
Popover cu închidere la click exterior
@implements IDisposable
@inject IJSRuntime JS
<div style="position: relative;" @ref="containerRef">
<FodButton @onclick="TogglePopover">
Deschide Popover
</FodButton>
<FodPopover Open="@isOpen">
<FodPaper Class="pa-4">
<FodText>Click în afară pentru a închide</FodText>
</FodPaper>
</FodPopover>
</div>
@code {
private ElementReference containerRef;
private bool isOpen = false;
private DotNetObjectReference<ComponentName> objRef;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
objRef = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("registerClickOutside", containerRef, objRef);
}
}
[JSInvokable]
public void ClosePopover()
{
isOpen = false;
StateHasChanged();
}
private void TogglePopover() => isOpen = !isOpen;
public void Dispose()
{
objRef?.Dispose();
}
}
- Lazy Rendering - Popover-ul randează conținutul doar când este deschis
- Portal Pattern - Conținutul este mutat în FodPopoverProvider pentru evitarea problemelor de z-index
- Event Delegation - Evenimente gestionate eficient la nivel de provider
- JSInterop Optimization - Apeluri JS minimizate și grupate
11. Accesibilitate
- Popover-urile trebuie să aibă focus management adecvat
- Folosiți
role="dialog"
pentru popover-uri modale
- Asigurați navigare cu tastatura
- Adăugați
aria-describedby
pentru screen readers
<FodPopover Open="@isOpen"
UserAttributes="@(new Dictionary<string, object>
{
{ "role", "dialog" },
{ "aria-labelledby", "popover-title" }
})">
<div>
<FodText id="popover-title" Typo="Typo.h6">
Titlu Popover
</FodText>
<!-- Conținut -->
</div>
</FodPopover>
12. Bune practici
- Container Position - Elementul părinte trebuie să aibă
position: relative
- Z-Index Management - Folosiți Fixed pentru overlay-uri globale
- Memory Leaks - Implementați Dispose pattern pentru event handlers
- Overflow - Setați OverflowBehavior pentru UI consistent
- Animation - Folosiți durată scurtă (200-300ms) pentru UX optim
- Focus Management - Gestionați focus pentru accesibilitate
13. Troubleshooting
Popover-ul nu apare
- Verificați că FodPopoverProvider este adăugat
- Verificați că părintele are
position: relative
- Verificați valoarea proprietății
Open
Poziționare incorectă
- Verificați AnchorOrigin și TransformOrigin
- Verificați OverflowBehavior
- Asigurați-vă că părintele nu are
overflow: hidden
Probleme de z-index
- Folosiți
Fixed="true"
pentru popover-uri globale
- Verificați z-index-ul elementelor părinte
14. Exemple avansate
Multi-level popover (submeniu)
<div style="position: relative;">
<FodButton @onclick="@(() => showMenu = !showMenu)">
Meniu Multi-nivel
</FodButton>
<FodPopover Open="@showMenu"
AnchorOrigin="Origin.BottomLeft"
TransformOrigin="Origin.TopLeft">
<FodList Style="min-width: 200px;">
<FodListItem Button="true">
<FodListItemText Primary="Opțiune simplă" />
</FodListItem>
<div style="position: relative;">
<FodListItem Button="true"
@onmouseenter="@(() => showSubmenu = true)"
@onmouseleave="@(() => showSubmenu = false)">
<FodListItemText Primary="Cu submeniu" />
<FodListItemSecondaryAction>
<FodIcon Icon="@FodIcons.Material.Filled.ChevronRight" />
</FodListItemSecondaryAction>
</FodListItem>
<FodPopover Open="@showSubmenu"
AnchorOrigin="Origin.TopRight"
TransformOrigin="Origin.TopLeft">
<FodList Style="min-width: 150px;">
<FodListItem Button="true">
<FodListItemText Primary="Sub-opțiune 1" />
</FodListItem>
<FodListItem Button="true">
<FodListItemText Primary="Sub-opțiune 2" />
</FodListItem>
</FodList>
</FodPopover>
</div>
</FodList>
</FodPopover>
</div>
@code {
private bool showMenu = false;
private bool showSubmenu = false;
}
Popover cu tranziții complexe
<style>
.fade-scale-enter {
opacity: 0;
transform: scale(0.8);
}
.fade-scale-enter-active {
opacity: 1;
transform: scale(1);
transition: opacity 300ms, transform 300ms;
}
.fade-scale-exit {
opacity: 1;
transform: scale(1);
}
.fade-scale-exit-active {
opacity: 0;
transform: scale(0.8);
transition: opacity 300ms, transform 300ms;
}
</style>
<FodPopover Open="@showAnimated"
Class="@GetAnimationClass()"
Duration="300">
<!-- Conținut animat -->
</FodPopover>
@code {
private bool showAnimated = false;
private bool isAnimating = false;
private string GetAnimationClass()
{
if (isAnimating)
{
return showAnimated ? "fade-scale-enter-active" : "fade-scale-exit-active";
}
return showAnimated ? "fade-scale-enter" : "fade-scale-exit";
}
}
15. Concluzie
Sistemul Popover din FOD oferă o soluție completă și flexibilă pentru afișarea conținutului flotant în aplicații Blazor. Cu suport pentru poziționare inteligentă, animații fluide și management centralizat, componentele FodPopover și FodPopoverProvider facilitează crearea de interfețe complexe precum meniuri, tooltip-uri, select-uri și dialoguri flotante. Arhitectura bazată pe service asigură performanță optimă și gestionare eficientă a resurselor.