UI/Components/Menu/MenuMapGrid.razor

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.

Reflection
@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 );
}