A Blazor/Razor UI component that renders a grid of map cards for a menu. It iterates Cards to draw thumbnails, badges, overlays for selected/locked/coming soon states, and invokes Pick when a card is clicked; Active is used to mark the selected card.
@namespace Sunless.Libraries.UI
@using System.Collections.Generic
@using Sandbox
@using Sandbox.UI
@inherits Panel
@attribute [StyleSheet]
<root class="menu-map-grid">
@if ( Cards is not null )
{
for ( int i = 0; i < Cards.Count; i++ )
{
var idx = i;
var card = Cards[idx];
var active = idx == ( Active?.Invoke() ?? -1 );
var unavailable = card.Locked || card.ComingSoon;
<div class="map-grid-card @(active ? "selected" : "") @(card.ComingSoon ? "coming-soon" : "") @(card.Locked ? "locked" : "")"
@onclick=@(() => { if ( !unavailable ) Pick?.Invoke( idx ); })>
<div class="map-grid-thumb">
@if ( !string.IsNullOrEmpty( card.Image ) )
{
<div class="map-grid-image" style="background-image: url(@card.Image)"></div>
}
else
{
<Icon Name="map" Size=@(IconSize.XLarge) ExtraClass="map-grid-placeholder" />
}
@if ( !string.IsNullOrEmpty( card.BadgeLabel ) )
{
<div class="map-grid-badge @ToneClass(card.BadgeTone)">@card.BadgeLabel</div>
}
@if ( active )
{
<div class="map-grid-overlay selected-overlay">
<Icon Name="check_circle" Size=@(IconSize.XLarge) ExtraClass="overlay-icon" />
<div class="overlay-text">Selected</div>
</div>
}
else if ( card.ComingSoon )
{
<div class="map-grid-overlay">
<Icon Name="lock" Size=@(IconSize.Large) ExtraClass="overlay-icon" />
<div class="overlay-text">Coming Soon</div>
</div>
}
else if ( card.Locked )
{
<div class="map-grid-overlay">
<Icon Name="lock" Size=@(IconSize.Large) ExtraClass="overlay-icon" />
<div class="overlay-text">Locked</div>
</div>
}
</div>
<div class="map-grid-info">
<div class="map-grid-title">@card.Title</div>
@if ( !string.IsNullOrEmpty( card.Description ) )
{
<div class="map-grid-desc">@card.Description</div>
}
else
{
<div class="map-grid-desc">Available</div>
}
</div>
</div>
}
}
</root>
@code
{
public IReadOnlyList<MenuMapGridWidget.Card> Cards { get; set; }
public Func<int> Active { get; set; }
public Action<int> Pick { get; set; }
static string ToneClass( MenuTone tone ) => tone switch
{
MenuTone.Primary => "tone-primary",
MenuTone.Confirm => "tone-confirm",
MenuTone.Danger => "tone-danger",
_ => "",
};
protected override int BuildHash() => HashCode.Combine( Cards?.Count ?? 0, Active?.Invoke() ?? -1 );
}