Rating
Documentație pentru componenta FodRating
1. Descriere Generală
FodRating
este componenta pentru selectarea unei evaluări sau rating în aplicații Blazor, oferind o interfață intuitivă cu stele sau alte iconițe personalizabile. Componenta suportă interacțiuni mouse și tastatură, stări diverse și personalizare completă.
Caracteristici principale: - Evaluare cu 1-n stele (configurabil) - Iconițe personalizabile pentru stări pline/goale - Suport pentru hover cu preview - Navigare cu tastatură (săgeți) - Moduri readonly și disabled - Culori și dimensiuni multiple - Two-way data binding - Animații fluide la interacțiune - Accesibilitate completă
2. Ghid de Utilizare API
Rating de bază
<FodRating @bind-SelectedValue="rating" />
@code {
private int rating = 3;
}
Rating cu număr diferit de stele
<!-- Rating 1-10 -->
<FodRating @bind-SelectedValue="rating" MaxValue="10" />
<!-- Rating 1-3 -->
<FodRating @bind-SelectedValue="rating" MaxValue="3" />
Rating cu feedback hover
<FodRating @bind-SelectedValue="rating"
@bind-HoveredValue="hoveredRating" />
<FodText Typo="Typo.body2" Class="mt-2">
@if (hoveredRating > 0)
{
<text>Veți selecta: @hoveredRating stele</text>
}
else if (rating > 0)
{
<text>Ați selectat: @rating stele</text>
}
else
{
<text>Selectați o evaluare</text>
}
</FodText>
@code {
private int rating = 0;
private int hoveredRating = 0;
}
Rating cu iconițe personalizate
<!-- Inimi în loc de stele -->
<FodRating @bind-SelectedValue="loveRating"
FullIcon="@FodIcons.Material.Filled.Favorite"
EmptyIcon="@FodIcons.Material.Outlined.FavoriteBorder"
Color="FodColor.Error" />
<!-- Emoji-uri -->
<FodRating @bind-SelectedValue="moodRating"
MaxValue="5"
FullIcon="@FodIcons.Material.Filled.SentimentVerySatisfied"
EmptyIcon="@FodIcons.Material.Outlined.SentimentVerySatisfied"
Color="FodColor.Success" />
<!-- Thumbs up -->
<FodRating @bind-SelectedValue="likeRating"
MaxValue="1"
FullIcon="@FodIcons.Material.Filled.ThumbUp"
EmptyIcon="@FodIcons.Material.Outlined.ThumbUp"
Color="FodColor.Primary" />
Dimensiuni diferite
<!-- Small -->
<FodRating @bind-SelectedValue="rating"
Size="FodSize.Small" />
<!-- Medium (implicit) -->
<FodRating @bind-SelectedValue="rating"
Size="FodSize.Medium" />
<!-- Large -->
<FodRating @bind-SelectedValue="rating"
Size="FodSize.Large" />
Rating readonly
<!-- Afișare rating existent -->
<div class="product-rating">
<FodRating SelectedValue="4"
ReadOnly="true" />
<FodText Typo="Typo.body2" Class="ms-2">
4.0 din 5 (123 recenzii)
</FodText>
</div>
Rating dezactivat
<FodRating @bind-SelectedValue="rating"
Disabled="@(!canRate)" />
@code {
private bool canRate = false;
private int rating = 0;
}
Rating în formular
<EditForm Model="@review" OnValidSubmit="SubmitReview">
<DataAnnotationsValidator />
<FodTextField @bind-Value="review.Title"
Label="Titlu recenzie"
Required="true" />
<div class="my-3">
<FodText Typo="Typo.body1" GutterBottom="true">
Evaluare generală
</FodText>
<FodRating @bind-SelectedValue="review.Rating" />
<ValidationMessage For="@(() => review.Rating)" />
</div>
<FodTextArea @bind-Value="review.Comment"
Label="Comentariu"
Rows="4" />
<FodButton Type="ButtonType.Submit"
Color="FodColor.Primary">
Trimite recenzia
</FodButton>
</EditForm>
@code {
private ProductReview review = new();
public class ProductReview
{
public string Title { get; set; }
[Required(ErrorMessage = "Vă rugăm selectați o evaluare")]
[Range(1, 5, ErrorMessage = "Evaluarea trebuie să fie între 1 și 5")]
public int Rating { get; set; }
public string Comment { get; set; }
}
}
Card produs cu rating
<FodCard>
<FodCardMedia Image="@product.ImageUrl"
Title="@product.Name" />
<FodCardContent>
<FodText Typo="Typo.h6">@product.Name</FodText>
<div class="d-flex align-items-center my-2">
<FodRating SelectedValue="@product.AverageRating"
ReadOnly="true"
Size="FodSize.Small" />
<FodText Typo="Typo.body2" Class="ms-2">
@product.AverageRating.ToString("F1")
(@product.ReviewCount recenzii)
</FodText>
</div>
<FodText Typo="Typo.h5" Color="FodColor.Primary">
@product.Price.ToString("C")
</FodText>
</FodCardContent>
<FodCardActions>
<FodButton Color="FodColor.Primary">
Adaugă în coș
</FodButton>
</FodCardActions>
</FodCard>
Sistem de feedback cu rating
<FodPaper Class="pa-4">
<FodText Typo="Typo.h5" GutterBottom="true">
Cum a fost experiența dvs?
</FodText>
<!-- Rating principal -->
<div class="text-center my-4">
<FodRating @bind-SelectedValue="overallRating"
@bind-HoveredValue="hoveredRating"
Size="FodSize.Large" />
<FodText Typo="Typo.body1" Class="mt-2">
@GetRatingText()
</FodText>
</div>
<!-- Ratinguri detaliate -->
@if (overallRating > 0)
{
<FodDivider Class="my-3" />
<div class="detailed-ratings">
<div class="rating-row">
<FodText Class="rating-label">Calitate serviciu</FodText>
<FodRating @bind-SelectedValue="serviceRating"
Size="FodSize.Small" />
</div>
<div class="rating-row">
<FodText Class="rating-label">Timp de răspuns</FodText>
<FodRating @bind-SelectedValue="responseRating"
Size="FodSize.Small" />
</div>
<div class="rating-row">
<FodText Class="rating-label">Profesionalism</FodText>
<FodRating @bind-SelectedValue="professionalRating"
Size="FodSize.Small" />
</div>
</div>
<FodTextArea @bind-Value="comments"
Label="Comentarii adiționale"
Rows="3"
Class="mt-3" />
<FodButton Color="FodColor.Primary"
Class="mt-3"
OnClick="SubmitFeedback">
Trimite feedback
</FodButton>
}
</FodPaper>
@code {
private int overallRating = 0;
private int hoveredRating = 0;
private int serviceRating = 0;
private int responseRating = 0;
private int professionalRating = 0;
private string comments = "";
private string GetRatingText()
{
var rating = hoveredRating > 0 ? hoveredRating : overallRating;
return rating switch
{
1 => "Foarte nemulțumit",
2 => "Nemulțumit",
3 => "Neutru",
4 => "Mulțumit",
5 => "Foarte mulțumit",
_ => "Evaluați experiența"
};
}
}
<style>
.rating-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.rating-label {
min-width: 150px;
}
</style>
Rating cu statistici
<FodCard>
<FodCardContent>
<FodText Typo="Typo.h6" GutterBottom="true">
Evaluări clienți
</FodText>
<!-- Rating mediu -->
<div class="d-flex align-items-center mb-3">
<FodText Typo="Typo.h2" Class="me-3">
@averageRating.ToString("F1")
</FodText>
<div>
<FodRating SelectedValue="@((int)Math.Round(averageRating))"
ReadOnly="true" />
<FodText Typo="Typo.body2" Color="FodColor.Secondary">
Bazat pe @totalReviews recenzii
</FodText>
</div>
</div>
<!-- Distribuție ratinguri -->
@for (int stars = 5; stars >= 1; stars--)
{
var percentage = GetRatingPercentage(stars);
<div class="rating-distribution">
<FodRating SelectedValue="stars"
ReadOnly="true"
Size="FodSize.Small" />
<FodLoadingLinear Value="percentage"
Class="mx-2 flex-grow-1"
Color="FodColor.Primary" />
<FodText Typo="Typo.caption" Style="min-width: 40px;">
@percentage%
</FodText>
</div>
}
</FodCardContent>
</FodCard>
@code {
private double averageRating = 4.3;
private int totalReviews = 156;
private Dictionary<int, int> ratingDistribution = new()
{
{ 5, 85 },
{ 4, 45 },
{ 3, 15 },
{ 2, 8 },
{ 1, 3 }
};
private int GetRatingPercentage(int stars)
{
if (totalReviews == 0) return 0;
return (int)Math.Round(ratingDistribution[stars] * 100.0 / totalReviews);
}
}
<style>
.rating-distribution {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
</style>
Rating inline în tabel
<FodDataTable Items="@products">
<HeaderContent>
<FodTHeadRow>
<FodTh>Produs</FodTh>
<FodTh>Preț</FodTh>
<FodTh>Evaluare</FodTh>
<FodTh>Acțiuni</FodTh>
</FodTHeadRow>
</HeaderContent>
<RowTemplate Context="product">
<FodTr>
<FodTd>@product.Name</FodTd>
<FodTd>@product.Price.ToString("C")</FodTd>
<FodTd>
<div class="d-flex align-items-center">
<FodRating SelectedValue="@product.Rating"
ReadOnly="true"
Size="FodSize.Small" />
<FodText Typo="Typo.caption" Class="ms-2">
(@product.ReviewCount)
</FodText>
</div>
</FodTd>
<FodTd>
<FodIconButton Icon="@FodIcons.Material.Filled.Edit"
Size="FodSize.Small"
OnClick="@(() => EditProduct(product))" />
</FodTd>
</FodTr>
</RowTemplate>
</FodDataTable>
3. Atribute disponibile
Proprietate | Tip | Descriere | Valoare Implicită |
---|---|---|---|
SelectedValue |
int |
Valoarea selectată | 0 |
SelectedValueChanged |
EventCallback<int> |
Eveniment la schimbare | - |
HoveredValue |
int |
Valoarea hover | 0 |
HoveredValueChanged |
EventCallback<int> |
Eveniment la hover | - |
MaxValue |
int |
Număr maxim de stele | 5 |
FullIcon |
string |
Iconița pentru stele pline | Star |
EmptyIcon |
string |
Iconița pentru stele goale | StarBorder |
Color |
FodColor |
Culoarea iconițelor | Default |
Size |
FodSize |
Dimensiunea iconițelor | Medium |
Disabled |
bool |
Dezactivează interacțiunile | false |
ReadOnly |
bool |
Doar citire, fără interacțiuni | false |
DisableRipple |
bool |
Dezactivează efectul ripple | false |
Class |
string |
Clase CSS adiționale | null |
Style |
string |
Stiluri inline | null |
UserAttributes |
Dictionary<string, object> |
Atribute HTML adiționale | null |
4. Evenimente
Eveniment | Tip | Descriere |
---|---|---|
SelectedValueChanged |
EventCallback<int> |
Se declanșează la selectarea unei valori |
HoveredValueChanged |
EventCallback<int> |
Se declanșează la hover pe o valoare |
5. Navigare cu tastatură
Tastă | Acțiune |
---|---|
Tab |
Focus pe componentă |
←/→ |
Navighează între stele |
↑/↓ |
Navighează între stele |
Shift + ← |
Prima stea |
Shift + → |
Ultima stea |
Space/Enter |
Selectează valoarea curentă |
6. Stilizare și personalizare
/* Rating cu dimensiune custom */
.custom-rating .fod-rating-item .fod-icon-root {
font-size: 2rem !important;
}
/* Rating cu spațiere între stele */
.spaced-rating .fod-rating-item {
margin: 0 0.25rem;
}
/* Culori diferite pentru hover */
.hover-rating .fod-rating-item:hover .fod-icon-root {
color: var(--fod-palette-warning-main);
}
/* Animație diferită */
.smooth-rating .fod-rating-item {
transition: all 0.3s ease;
}
.smooth-rating .fod-rating-item.active {
transform: scale(1.2) rotate(10deg);
}
/* Rating vertical */
.vertical-rating .fod-rating-root {
flex-direction: column;
}
/* Gradient pentru stele selectate */
.gradient-rating .fod-rating-item.fod-rating-item-active .fod-icon-root {
background: linear-gradient(45deg, #ffd700, #ffed4e);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
7. Integrare cu alte componente
În Card
<FodCard>
<FodCardContent>
<FodRating @bind-SelectedValue="rating" />
</FodCardContent>
</FodCard>
În Dialog
<FodDialog Show="@showRatingDialog">
<FodDialogTitle>Evaluați serviciul</FodDialogTitle>
<FodDialogContent>
<FodRating @bind-SelectedValue="serviceRating"
Size="FodSize.Large" />
</FodDialogContent>
<FodDialogActions>
<FodButton OnClick="SubmitRating">Trimite</FodButton>
</FodDialogActions>
</FodDialog>
În Chip pentru afișare compactă
<FodChip Color="FodColor.Primary">
<FodIcon Icon="@FodIcons.Material.Filled.Star"
Size="FodSize.Small" />
@rating.ToString("F1")
</FodChip>
8. Validare în formulare
@using System.ComponentModel.DataAnnotations
<EditForm Model="@model" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator />
<InputRating @bind-Value="model.Rating" />
<ValidationMessage For="@(() => model.Rating)" />
<FodButton Type="ButtonType.Submit">Submit</FodButton>
</EditForm>
@code {
public class RatingModel
{
[Required(ErrorMessage = "Rating este obligatoriu")]
[Range(1, 5, ErrorMessage = "Rating trebuie să fie între 1 și 5")]
public int Rating { get; set; }
}
private RatingModel model = new();
}
// Component wrapper pentru validare
@code {
public class InputRating : InputBase<int>
{
protected override bool TryParseValueFromString(string value,
out int result, out string validationErrorMessage)
{
if (int.TryParse(value, out result))
{
validationErrorMessage = null;
return true;
}
validationErrorMessage = "Valoare invalidă";
return false;
}
}
}
9. Performanță
- Folosește un singur event handler pentru toate stelele
- Minimizează re-render prin CascadingValue
- Animațiile folosesc CSS transforms
10. Accesibilitate
- Radio inputs ascunse pentru screen readers
- Suport complet pentru navigare cu tastatură
- ARIA labels pentru fiecare stea
- Focus vizibil pentru navigare
11. Bune practici
- Valoare inițială - Setați 0 pentru "fără rating"
- Feedback vizual - Folosiți HoveredValue pentru preview
- Readonly pentru afișare - Nu disabled pentru date existente
- Dimensiuni responsive - Size.Small pe mobile
- Validare - Verificați rating > 0 înainte de submit
- Text descriptiv - Adăugați text pentru claritate
12. Troubleshooting
Rating nu răspunde la click
- Verificați că nu este Disabled sau ReadOnly
- Verificați că SelectedValueChanged este conectat
Hover nu funcționează
- Verificați că HoveredValueChanged este setat
- Verificați CSS pentru pointer-events
Iconițe nu se afișează
- Verificați că iconițele există în FodIcons
- Verificați importul fonturilor de iconițe
13. Exemple avansate
Rating cu nivele și descrieri
<div class="rating-with-levels">
<FodRating @bind-SelectedValue="satisfactionLevel"
@bind-HoveredValue="hoveredLevel"
MaxValue="5"
Size="FodSize.Large" />
<FodText Typo="Typo.h6" Class="mt-2 text-center">
@GetLevelDescription()
</FodText>
@if (satisfactionLevel > 0)
{
<FodAlert Severity="@GetAlertSeverity()" Class="mt-3">
@GetDetailedFeedback()
</FodAlert>
}
</div>
@code {
private int satisfactionLevel = 0;
private int hoveredLevel = 0;
private string GetLevelDescription()
{
var level = hoveredLevel > 0 ? hoveredLevel : satisfactionLevel;
return level switch
{
1 => "😞 Foarte nemulțumit",
2 => "😟 Nemulțumit",
3 => "😐 Neutru",
4 => "😊 Mulțumit",
5 => "😄 Foarte mulțumit",
_ => "Selectați nivelul de satisfacție"
};
}
private Severity GetAlertSeverity() => satisfactionLevel switch
{
1 or 2 => Severity.Error,
3 => Severity.Warning,
4 or 5 => Severity.Success,
_ => Severity.Info
};
}
14. Concluzie
FodRating
oferă o soluție completă și flexibilă pentru implementarea sistemelor de evaluare în aplicații. Cu suport pentru personalizare extensivă, accesibilitate completă și integrare ușoară, componenta acoperă toate cazurile comune de utilizare pentru rating și feedback.