164 lines
4.9 KiB
Plaintext
164 lines
4.9 KiB
Plaintext
@using System.Globalization
|
|
@using System.Linq
|
|
@using Prefab.Web.Client.Models.Shared
|
|
|
|
<Card Variant="CardVariant.Product"
|
|
Layout="@Layout"
|
|
CssClass="@CssClass"
|
|
ImagePlaceholderText="@ImagePlaceholderText"
|
|
AdditionalAttributes="RootAttributes"
|
|
ShowBadges="@ShowBadgesSection"
|
|
ShowMeta="@ShowMetaSection"
|
|
ShowRating="@ShouldRenderRating"
|
|
ShowDescription="@ShowDescriptionSection"
|
|
ShowFooter="false"
|
|
Prices="@PricesTemplate">
|
|
<Badges>
|
|
@if (Product.IsOnSale)
|
|
{
|
|
<div class="product-card__badge product-card__badge--style--sale">Sale</div>
|
|
}
|
|
@foreach (var badge in Product.Badges.Where(static badge => !string.IsNullOrWhiteSpace(badge)))
|
|
{
|
|
<div class="product-card__badge">@badge</div>
|
|
}
|
|
</Badges>
|
|
<BadgesContent>
|
|
@BadgesContent?.Invoke(Product)
|
|
</BadgesContent>
|
|
<Image>
|
|
<a href="@Product.Url">
|
|
<img src="@ImageSource"
|
|
alt="@ImageAltText"
|
|
loading="lazy" />
|
|
</a>
|
|
</Image>
|
|
<Meta>
|
|
@if (!string.IsNullOrWhiteSpace(Product.CategoryName))
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(Product.CategoryUrl))
|
|
{
|
|
<a href="@Product.CategoryUrl">@Product.CategoryName</a>
|
|
}
|
|
else
|
|
{
|
|
@Product.CategoryName
|
|
}
|
|
}
|
|
</Meta>
|
|
<Heading>
|
|
<a href="@Product.Url">
|
|
@Product.Title
|
|
</a>
|
|
</Heading>
|
|
<Rating>
|
|
@if (Product.ReviewCount > 0)
|
|
{
|
|
<div class="product-card__rating-title">@GetRatingTitle()</div>
|
|
}
|
|
<div class="product-card__rating-stars">
|
|
<div class="rating" aria-label="@RatingAriaLabel">
|
|
<div class="rating__body">
|
|
@foreach (var star in Enumerable.Range(1, TotalStars))
|
|
{
|
|
var isActive = star <= Math.Clamp(Product.Rating, 0, TotalStars);
|
|
<svg class="rating__star @(isActive ? "rating__star--active" : string.Empty)"
|
|
width="13px"
|
|
height="12px"
|
|
aria-hidden="true">
|
|
<g class="rating__fill">
|
|
<use xlink:href="images/sprite.svg#star-normal"></use>
|
|
</g>
|
|
<g class="rating__stroke">
|
|
<use xlink:href="images/sprite.svg#star-normal-stroke"></use>
|
|
</g>
|
|
</svg>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Rating>
|
|
<Description>
|
|
@DescriptionContent?.Invoke(Product)
|
|
</Description>
|
|
</Card>
|
|
|
|
@code {
|
|
private static readonly CultureInfo PriceCulture = CultureInfo.CurrentCulture;
|
|
private const string PlaceholderImageDataUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCA0MDAgNDAwJz48cmVjdCB3aWR0aD0nNDAwJyBoZWlnaHQ9JzQwMCcgZmlsbD0nI2YzZjNmMycvPjx0ZXh0IHg9JzUwJScgeT0nNTAlJyBkb21pbmFudC1iYXNlbGluZT0nbWlkZGxlJyB0ZXh0LWFuY2hvcj0nbWlkZGxlJyBmb250LWZhbWlseT0nT3BlbiBTYW5zLCBBcmlhbCwgc2Fucy1zZXJpZicgZm9udC1zaXplPScyOCcgZmlsbD0nIzliOWI5Yic+SW1hZ2UgY29taW5nIHNvb248L3RleHQ+PC9zdmc+";
|
|
|
|
[Parameter, EditorRequired]
|
|
public ProductCardModel Product { get; set; } = default!;
|
|
|
|
[Parameter]
|
|
public string Layout { get; set; } = "grid";
|
|
|
|
[Parameter]
|
|
public string CssClass { get; set; } = string.Empty;
|
|
|
|
[Parameter]
|
|
public string ImagePlaceholderText { get; set; } = "Image coming soon";
|
|
|
|
[Parameter]
|
|
public RenderFragment<ProductCardModel>? BadgesContent { get; set; }
|
|
|
|
[Parameter]
|
|
public RenderFragment<ProductCardModel>? DescriptionContent { get; set; }
|
|
|
|
[Parameter]
|
|
public bool ShowPrice { get; set; }
|
|
|
|
private bool HasBadges => Product.IsOnSale || Product.Badges.Any(static badge => !string.IsNullOrWhiteSpace(badge));
|
|
|
|
private bool ShouldRenderRating => Product.Rating > 0 || Product.ReviewCount > 0;
|
|
|
|
private bool ShowBadgesSection => HasBadges || BadgesContent is not null;
|
|
|
|
private bool ShowMetaSection => !string.IsNullOrWhiteSpace(Product.CategoryName);
|
|
|
|
private bool ShowDescriptionSection => DescriptionContent is not null;
|
|
|
|
private RenderFragment? PricesTemplate => !ShowPrice || !Product.HasPrice
|
|
? null
|
|
: @<text>
|
|
<div class="product-card__price">
|
|
<span class="product-card__price-new">@FormatMoney(Product.FromPrice!)</span>
|
|
@if (Product.OldPrice.HasValue)
|
|
{
|
|
<span class="product-card__price-old">@FormatDecimal(Product.OldPrice.Value)</span>
|
|
}
|
|
</div>
|
|
</text>;
|
|
|
|
private string RatingAriaLabel => Product.Rating > 0
|
|
? $"{Math.Clamp(Product.Rating, 0, TotalStars)} out of {TotalStars}"
|
|
: "No rating yet";
|
|
|
|
private IReadOnlyDictionary<string, object>? RootAttributes =>
|
|
string.IsNullOrWhiteSpace(Product.Sku)
|
|
? null
|
|
: new Dictionary<string, object>
|
|
{
|
|
["data-sku"] = Product.Sku
|
|
};
|
|
|
|
private string FormatMoney(MoneyModel money) => money.Amount.ToString("C", PriceCulture);
|
|
|
|
private string FormatDecimal(decimal value) => value.ToString("C", PriceCulture);
|
|
|
|
private string GetRatingTitle() => $"Reviews ({Math.Max(Product.ReviewCount, 0)})";
|
|
|
|
private string ImageSource => string.IsNullOrWhiteSpace(Product.PrimaryImageUrl)
|
|
? PlaceholderImageDataUrl
|
|
: Product.PrimaryImageUrl!;
|
|
|
|
private string ImageAltText => string.IsNullOrWhiteSpace(Product.PrimaryImageUrl)
|
|
? ImagePlaceholderText
|
|
: Product.Title;
|
|
|
|
private const int TotalStars = 5;
|
|
}
|
|
|
|
|
|
|