feat(adminui): T0-1 reusable ContextMenu component + demo page

Adds a reusable right-click / context menu Blazor component for the AdminUI:
- ContextMenu.razor: right-click surface (browser menu suppressed) + optional
  ellipsis fallback trigger, both anchoring the menu at the pointer; keyboard
  nav (arrows/Enter/Esc), transparent backdrop outside-click close (no JS).
- ContextMenu.razor.css: scoped styles on the shared theme tokens.
- ContextMenuItem.cs: Label/OnClick/Icon/Disabled/IsSeparator model + Separator().
- Dev demo page at /dev/context-menu-demo.

Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
This commit is contained in:
Joseph Doherty
2026-07-16 02:05:01 -04:00
parent f121f8ca16
commit 1d7afbb1eb
4 changed files with 324 additions and 0 deletions
@@ -0,0 +1,28 @@
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared;
/// <summary>
/// One entry in a <see cref="ContextMenu"/>. An item is either an actionable
/// row (<see cref="Label"/> + <see cref="OnClick"/>) or a visual divider
/// (<see cref="IsSeparator"/> = true, in which case the other members are
/// ignored). Use the <see cref="Separator"/> factory for dividers.
/// </summary>
public sealed class ContextMenuItem
{
/// <summary>Text shown for the row.</summary>
public string Label { get; init; } = "";
/// <summary>Invoked when the row is activated (click / Enter).</summary>
public Action? OnClick { get; init; }
/// <summary>Optional leading icon — an emoji or single glyph.</summary>
public string? Icon { get; init; }
/// <summary>Renders the row greyed-out and non-interactive.</summary>
public bool Disabled { get; init; }
/// <summary>When true this entry is a horizontal divider, not a row.</summary>
public bool IsSeparator { get; init; }
/// <summary>A horizontal divider between groups of items.</summary>
public static ContextMenuItem Separator() => new() { IsSeparator = true };
}