File: Components\Controls\AspireMenuButton.razor.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Dashboard.Model;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.FluentUI.AspNetCore.Components;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;
 
namespace Aspire.Dashboard.Components;
 
public partial class AspireMenuButton : FluentComponentBase
{
    public AspireMenuButton(LibraryConfiguration configuration)
        : base(configuration)
    {
    }
 
    private static readonly Icon s_defaultIcon = new Icons.Regular.Size24.ChevronDown();
 
    private bool _renderMenu;
    private bool _visible;
    private Icon? _icon;
    private MenuButtonItem[] _items = [];
    private bool _disabled;
    private bool _hasActionableItems = true;
    private Func<IList<MenuButtonItem>>? _renderedItemsProvider;
 
    [Parameter]
    public string? Text { get; set; }
 
    [Parameter]
    public Icon? IconStart { get; set; }
 
    [Parameter]
    public string? IconStartClass { get; set; }
 
    [Parameter]
    public Color? IconStartColor { get; set; }
 
    [Parameter]
    public string? IconStartCustomColor { get; set; }
 
    [Parameter]
    public Icon? Icon { get; set; }
 
    [Parameter]
    public Color? IconColor { get; set; } = Color.Primary;
 
    [Parameter]
    public string? IconCustomColor { get; set; }
 
    [Parameter]
    public string? ButtonClass { get; set; }
 
    /// <summary>
    /// Gets or sets the callback that provides menu items when the menu is opened.
    /// </summary>
    [Parameter]
    public required Func<IList<MenuButtonItem>> ItemsProvider { get; set; }
 
    // Exposed only for tests to inspect the rendered menu items.
    internal IReadOnlyList<MenuButtonItem> Items => _items;
 
    [Parameter]
    public ButtonAppearance? ButtonAppearance { get; set; }
 
    [Parameter]
    public string? Title { get; set; }
 
    [Parameter]
    public string MenuButtonId { get; set; } = $"menu-button-{Guid.NewGuid():N}";
 
    [Parameter]
    public bool HideIcon { get; set; }
 
    /// <summary>
    /// Gets or sets a value indicating whether the button is unconditionally disabled.
    /// </summary>
    /// <remarks>
    /// This is independent of the automatic disabling that happens when the menu has no actionable items.
    /// </remarks>
    [Parameter]
    public bool Disabled { get; set; }
 
    /// <summary>
    /// Gets or sets a value indicating whether focus should return to this menu button after a menu item is clicked.
    /// </summary>
    /// <remarks>
    /// Focus restoration is enabled by default because the underlying menu anchor is the button that opened the menu.
    /// </remarks>
    [Parameter]
    public bool RestoreFocusOnItemClick { get; set; } = true;
 
    protected override void OnParametersSet()
    {
        _icon = Icon ?? s_defaultIcon;
 
        if (!ReferenceEquals(_renderedItemsProvider, ItemsProvider))
        {
            _renderedItemsProvider = ItemsProvider;
 
            // The provider hasn't run for this delegate yet, so the menu contents are unknown.
            // Assume it has content so only Disabled can disable the button before the first open.
            _hasActionableItems = true;
        }
 
        if (_visible)
        {
            RefreshItems();
 
            if (_disabled)
            {
                OnMenuOpenChanged(false);
            }
        }
        else
        {
            UpdateDisabled();
        }
    }
 
    private void ToggleMenu()
    {
        if (_visible)
        {
            OnMenuOpenChanged(false);
            return;
        }
 
        RefreshItems();
 
        _renderMenu = true;
        _visible = true;
    }
 
    private void RefreshItems()
    {
        _items = ItemsProvider().ToArray();
        _hasActionableItems = _items.Any(i => !i.IsDivider);
        UpdateDisabled();
    }
 
    private void UpdateDisabled()
    {
        _disabled = Disabled || !_hasActionableItems;
    }
 
    private void OnMenuOpenChanged(bool open)
    {
        _visible = open;
    }
 
    private void OnKeyDown(KeyboardEventArgs args)
    {
        if (args is not null && args.Key == "Escape")
        {
            OnMenuOpenChanged(false);
        }
    }
 
}