A Blazor/Razor UI component for an item card. Renders an optional image, category (with optional icon), title, and an optional footer with trait chips and flavor text. It defines a Trait record, component parameters, a ToneClass mapper, and a BuildHash override combining fields.
@namespace Sunless.Libraries.UI
@using System
@using System.Collections.Generic
@using System.Linq
@using Sandbox
@using Sandbox.UI
@inherits Panel
@attribute [StyleSheet]
<root class="item-card">
@if ( !string.IsNullOrEmpty( Image ) )
{
<div class="item-card-image" style="background-image: url(@Image)"></div>
}
<div class="item-card-body">
@if ( !string.IsNullOrEmpty( Category ) || !string.IsNullOrEmpty( CategoryIcon ) )
{
<div class="item-card-category">
@if ( !string.IsNullOrEmpty( CategoryIcon ) )
{
<Icon Name=@CategoryIcon Size=@(IconSize.Small) />
}
<div class="item-card-category-label">@Category</div>
</div>
}
<div class="item-card-title">@Title</div>
@if ( HasFooter )
{
<div class="item-card-footer">
@if ( Traits is not null && Traits.Count > 0 )
{
<div class="item-card-traits">
@foreach ( var t in Traits )
{
var tone = ToneClass( t.Tone );
<div class="item-card-trait @tone">
@if ( !string.IsNullOrEmpty( t.Icon ) )
{
<Icon [email protected] Size=@(IconSize.Small) />
}
<div class="item-card-trait-label">@t.Label</div>
</div>
}
</div>
}
@if ( !string.IsNullOrEmpty( Flavor ) )
{
<div class="item-card-flavor">@Flavor</div>
}
</div>
}
</div>
</root>
@code
{
public sealed record Trait( string Label, string Icon = null, MenuTone Tone = MenuTone.Default );
[Parameter] public string Title { get; set; }
[Parameter] public string Category { get; set; }
[Parameter] public string CategoryIcon { get; set; }
[Parameter] public string Image { get; set; }
[Parameter] public string Flavor { get; set; }
[Parameter] public IReadOnlyList<Trait> Traits { get; set; }
bool HasFooter => (Traits is not null && Traits.Count > 0) || !string.IsNullOrEmpty( Flavor );
static string ToneClass( MenuTone tone ) => tone switch
{
MenuTone.Primary => "tone-primary",
MenuTone.Confirm => "tone-confirm",
MenuTone.Danger => "tone-danger",
MenuTone.Ghost => "tone-ghost",
_ => "",
};
protected override int BuildHash() =>
HashCode.Combine(
Title, Category, CategoryIcon, Image, Flavor,
Traits is null ? 0 : Traits.Count,
Traits is null ? 0 : Traits.Aggregate( 0, ( h, t ) => HashCode.Combine( h, t.Label, t.Icon, t.Tone ) ) );
}