UI/Components/Modal/ModalPanel.razor

A UI Razor component for a modal dialog panel. It renders an overlay with optional header, body and footer regions, supports a close button and scrim click dismissal, and exposes properties and callbacks to control visibility and content.

@namespace Sunless.Libraries.UI
@using Sandbox
@using Sandbox.UI
@attribute [StyleSheet]
@inherits Panel

<root class="modal-overlay @(IsOpen ? "is-open" : "") @ExtraClass">
	@if ( IsOpen )
	{
		<div class="modal-scrim" @onclick=@HandleScrimClick></div>
		<div class="modal-dialog" style=@DialogStyle>
			@if ( Header is not null || !string.IsNullOrEmpty( Title ) || ShowClose )
			{
				<div class="modal-header">
					@if ( Header is not null )
					{
						@Header
					}
					else
					{
						<div class="modal-title">@Title</div>
					}
					@if ( ShowClose )
					{
						<div class="modal-close" @onclick=@HandleClose>X</div>
					}
				</div>
			}

			@if ( Body is not null )
			{
				<div class="modal-body">@Body</div>
			}

			@if ( Footer is not null )
			{
				<div class="modal-footer">@Footer</div>
			}
		</div>
	}
</root>

@code
{
	public bool IsOpen { get; set; }
	public string Title { get; set; }
	public bool ShowClose { get; set; } = true;
	public bool DismissOnScrim { get; set; } = true;
	public string ExtraClass { get; set; }
	public string DialogStyle { get; set; }
	public Action OnClose { get; set; }

	public RenderFragment Header { get; set; }
	public RenderFragment Body { get; set; }
	public RenderFragment Footer { get; set; }

	void HandleClose()
	{
		try { OnClose?.Invoke(); } catch ( Exception e ) { Log.Warning( $"[Modal] OnClose threw: {e.Message}" ); }
	}

	void HandleScrimClick()
	{
		if ( !DismissOnScrim ) return;
		HandleClose();
	}

	protected override int BuildHash() => HashCode.Combine( IsOpen, Title, ShowClose, ExtraClass, DialogStyle );
}