UI/PixelDropdown.razor
@using System
@using System.Collections.Generic
@using Sandbox
@using Sandbox.UI
@inherits Panel
@namespace BlockParty
@attribute [StyleSheet( "PixelDropdown.razor.scss" )]
@* A minimal, fully self-styled dropdown. s&box stylesheets do NOT cascade into a child Razor
component's internals, so the engine DropDown can't be reskinned from a parent stylesheet — and
we want the blocky/pixel look, not the default. This component owns its own DOM + stylesheet.
Mouse-driven: click the header to open, click an option to pick it (and close), click anywhere
else (the full-screen backdrop) to dismiss without changing the value. The menu hangs below the
header by default; OpenUp stacks it above instead (for dropdowns near the bottom of the screen). *@
<root class="pixel-dropdown @(OpenUp ? "up" : "down") @(_open ? "open" : "")">
<div class="header" onclick=@Toggle>
<label class="value">@CurrentLabel</label>
@* Points toward where the menu appears while closed, and back toward the header while open. *@
<label class="arrow">@(_open != OpenUp ? "▲" : "▼")</label>
</div>
@if ( _open )
{
@* Full-screen catch: any click outside the option list closes the menu. Drawn behind the
menu (earlier sibling), stretched well past the reference viewport in every direction. *@
<div class="backdrop" onclick=@Close></div>
<div class="menu">
@for ( int i = 0; i < OptionCount; i++ )
{
var idx = i; // capture for the click handler
<div class="option @(i == Selected ? "sel" : "")" onclick=@(() => Choose( idx ))>
<label>@Options[idx]</label>
</div>
}
</div>
}
</root>
@code
{
/// <summary>The option labels, top-to-bottom.</summary>
[Parameter] public List<string> Options { get; set; } = new();
/// <summary>Index of the currently-selected option.</summary>
[Parameter] public int Selected { get; set; }
/// <summary>Raised with the new index when the player picks a different option.</summary>
[Parameter] public Action<int> OnSelected { get; set; }
/// <summary>Open the option list above the header instead of below it.</summary>
[Parameter] public bool OpenUp { get; set; }
private bool _open;
private int OptionCount => Options?.Count ?? 0;
private string CurrentLabel =>
Options is not null && Selected >= 0 && Selected < Options.Count ? Options[Selected] : "";
private void Toggle()
{
_open = !_open;
StateHasChanged();
}
private void Close()
{
_open = false;
StateHasChanged();
}
private void Choose( int index )
{
_open = false;
if ( index != Selected )
{
Audio.PlaySfx( SfxType.MenuBlip );
Selected = index;
OnSelected?.Invoke( index );
}
StateHasChanged();
}
protected override int BuildHash() => System.HashCode.Combine( Selected, _open, OptionCount, OpenUp );
}