Code/ErrorBoundary.razor.cs
using System;
using System.Collections.Generic;
using Sandbox.Diagnostics;
using Sandbox.Razor;

namespace BetterUI;

/// <summary>
/// A panel that renders an error template when an error is encountered,
/// and provides an ID for the error boundary.
/// </summary>
/// <remarks>
/// Can be used to create custom error boundaries that can be used to
/// isolate errors in specific parts of the UI.
/// </remarks>
public partial class ErrorBoundary : Panel
{
	/// <summary>
	/// The ID of the error boundary.
	/// </summary>
	/// <remarks>
	/// This can be used to identify the error boundary in logs and other
	/// tools.
	/// </remarks>
	public string ErrorId { get; set; } = null!;

	/// <summary>
	/// A list of errors that have been encountered
	/// </summary>
	public List<Exception> Errors { get; } = new();

	/// <summary>
	/// The latest error that was encountered.
	/// </summary>
	/// <remarks>
	/// This is null if no errors have been encountered.
	/// </remarks>
	public Exception? LatestError { get; private set; }

	/// <summary>
	/// The template to render for each error.
	/// </summary>
	/// <remarks>
	/// The template is rendered with the error as the context.
	/// </remarks>
	public RenderFragment<Exception> OnError { get; set; } = null!;

	/// <inheritdoc />
	protected override void OnAfterTreeRender( bool firstTime )
	{
		Assert.NotNull( ErrorId, "ErrorBoundary must have an ErrorId" );
	}

	/// <summary>
	/// Adds an error to the boundary.
	/// </summary>
	/// <param name="ex">The error to add.</param>
	public void AddError( Exception ex )
	{
		LatestError = ex;
		Errors.Add( ex );
	}

	/// <summary>
	/// Clears all errors from the boundary.
	/// </summary>
	public void ClearErrors()
	{
		LatestError = null;
		Errors.Clear();
	}

	/// <inheritdoc />
	protected override int BuildHash() => HashCode.Combine( ErrorId, Errors.Count, LatestError );
}