Add KebabMenu component with JavaScript handler and styles for dropdown actions

This commit is contained in:
2026-02-04 21:47:08 +01:00
parent 65a13539e0
commit fd5abef3f6
5 changed files with 195 additions and 1 deletions

View File

@@ -0,0 +1,94 @@
@implements IAsyncDisposable
@inject IJSRuntime JS
<div class="kebab-menu" @ref="_menuRef">
<button class="kebab-button" type="button" @onclick="Toggle" aria-label="@AriaLabel" aria-haspopup="menu" aria-expanded="@_isOpen">⋮</button>
@if (_isOpen)
{
<div class="kebab-menu__items" role="menu">
@ChildContent(_context)
</div>
}
</div>
@code {
[Parameter] public string AriaLabel { get; set; } = "Menu";
[Parameter] public RenderFragment<KebabMenuContext> ChildContent { get; set; } = default!;
private bool _isOpen;
private ElementReference _menuRef;
private DotNetObjectReference<KebabMenu>? _dotNetRef;
private IJSObjectReference? _jsRef;
private readonly KebabMenuContext _context;
public KebabMenu()
{
_context = new KebabMenuContext(Close);
}
/// <summary>
/// Toggles the open state of the menu.
/// </summary>
private void Toggle()
{
_isOpen = !_isOpen;
}
/// <summary>
/// Closes the menu if it is open.
/// </summary>
[JSInvokable]
public void Close()
{
if (!_isOpen)
{
return;
}
_isOpen = false;
_ = InvokeAsync(StateHasChanged);
}
/// <summary>
/// Called after the component has been rendered. Registers the JavaScript event handler on first render.
/// </summary>
/// <param name="firstRender">True if this is the first render; otherwise, false.</param>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
{
return;
}
_dotNetRef = DotNetObjectReference.Create(this);
_jsRef = await JS.InvokeAsync<IJSObjectReference>("kebabMenu.register", _menuRef, _dotNetRef);
}
/// <summary>
/// Disposes the JavaScript object reference and event handler.
/// </summary>
public async ValueTask DisposeAsync()
{
if (_jsRef is not null)
{
await _jsRef.InvokeVoidAsync("dispose");
await _jsRef.DisposeAsync();
}
_dotNetRef?.Dispose();
}
/// <summary>
/// Initializes a new instance of the KebabMenuContext class.
/// </summary>
/// <param name="close">The action to close the menu.</param>
public sealed class KebabMenuContext
{
public KebabMenuContext(Action close)
{
Close = close;
}
public Action Close { get; }
}
}