diff --git a/src/MudBlazor.Docs/Pages/Components/Menu/Examples/ContextMenuExample.razor b/src/MudBlazor.Docs/Pages/Components/Menu/Examples/ContextMenuExample.razor new file mode 100644 index 000000000000..0ea4ee939d50 --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Components/Menu/Examples/ContextMenuExample.razor @@ -0,0 +1,33 @@ +@namespace MudBlazor.Docs.Examples + + + + + Istra Croatia + Peninsula in Europe + + +
+ +
+ + Try left clicking or right clicking the image to open the menu. + +
+ + + Cut + Copy + Paste + Delete + + +@code { +#nullable enable + private MudMenu _contextMenu = null!; + + private async Task OpenContextMenu(MouseEventArgs args) + { + await _contextMenu.OpenMenuAsync(args); + } +} diff --git a/src/MudBlazor.Docs/Pages/Components/Menu/MenuPage.razor b/src/MudBlazor.Docs/Pages/Components/Menu/MenuPage.razor index 98016c21d469..0d336319247f 100644 --- a/src/MudBlazor.Docs/Pages/Components/Menu/MenuPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Menu/MenuPage.razor @@ -105,19 +105,31 @@ + + + + It is possible to open a menu using custom logic. This is useful when implementing context menus. + When Label, Icon and ActivatorContent are unset, no + button is rendered and the menu can only be opened programmatically. + + + + + + - - - - - The component uses MudPopover to place its menu, which can be controlled with AnchorOrigin and TransformOrigin.
- To learn and see all possible examples of how you can position the popover check out its documentation page. -
-
- - - -
+ + + + + + The component uses MudPopover to place its menu, which can be controlled with AnchorOrigin and TransformOrigin.
+ To learn and see all possible examples of how you can position the popover check out its documentation page. +
+
+ + +
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/ContextMenuTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/ContextMenuTest.razor new file mode 100644 index 000000000000..bc4f2edda945 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/ContextMenuTest.razor @@ -0,0 +1,46 @@ + +
+ + Click me + +
+
+ +@if (ActivatorContent is null) +{ + + Cut + Copy + Paste + Delete + +} +else +{ + + @ActivatorContent + + Cut + Copy + Paste + Delete + + +} + +@code { + public static string __description__ = "Left or right clicking the card should open a context menu. The padding above and below the card must be the same size."; + + private MudMenu _contextMenu = null!; + + [Parameter] + public string? Label { get; set; } + + [Parameter] + public RenderFragment? ActivatorContent { get; set; } + + private async Task OpenContextMenu(MouseEventArgs args) + { + await _contextMenu.OpenMenuAsync(args); + } +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/MenuHrefTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/MenuHrefTest.razor index 72095ea5e77f..39c2f6afbc66 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/MenuHrefTest.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/MenuHrefTest.razor @@ -1,5 +1,5 @@ - + 1 2 3 diff --git a/src/MudBlazor.UnitTests/Components/MenuTests.cs b/src/MudBlazor.UnitTests/Components/MenuTests.cs index 34ece4ce492b..cf8cf8781833 100644 --- a/src/MudBlazor.UnitTests/Components/MenuTests.cs +++ b/src/MudBlazor.UnitTests/Components/MenuTests.cs @@ -407,9 +407,7 @@ public void ItemsWithHrefShouldRenderAsAnchor() [Test] [TestCase("x", null, null)] - [TestCase(null, "Close menu", "Close menu")] [TestCase("x", "Close menu", "Close menu")] - [TestCase(null, null, null, Description = "Ensures aria-label is not present instead of empty string")] public void MenuWithLabelAndAriaLabel_Should_HaveExpectedAriaLabel(string label, string ariaLabel, string expectedAriaLabel) { var comp = Context.RenderComponent(parameters => parameters @@ -426,6 +424,7 @@ public void IconMenuWithAriaLabel_Should_HaveExpectedAriaLabel(string ariaLabel, { var comp = Context.RenderComponent(parameters => parameters .Add(p => p.Icon, Icons.Material.Filled.Accessibility) + .Add(p => p.Label, "Accessibility") .Add(p => p.AriaLabel, ariaLabel)); comp.Find("button").GetAttribute("aria-label").Should().Be(expectedAriaLabel); @@ -566,5 +565,44 @@ public async Task OpenMenuAsync_Should_Set_FixedPosition() await Context.Renderer.Dispatcher.InvokeAsync(mudMenuContext.CloseMenuAsync); } + + [Test] + public void ContextMenu_Should_NotHaveButton_And_NotBeVisible() + { + // Arrange + var comp = Context.RenderComponent(); + var menuComponent = comp.FindComponent(); + + // Assert + comp.FindAll("button.mud-button-root").Count.Should().Be(0); + menuComponent.Find("div.mud-menu").ClassList.Should().Contain("mud-menu-button-hidden"); + } + + [Test] + public void ContextMenu_WithLabel_Sould_HaveButton_And_BeVisible() + { + // Arrange + var comp = Context.RenderComponent(parameters + => parameters.Add(p => p.Label, "Context Menu")); + var menuComponent = comp.FindComponent(); + + // Assert + menuComponent.FindAll("button").Count.Should().Be(1); + menuComponent.Find("div.mud-menu").ClassList.Should().NotContain("mud-menu-button-hidden"); + } + + [Test] + public void ContextMenu_WithActivatorContent_Sould_HaveActivatorContent_And_BeVisible() + { + // Arrange + var comp = Context.RenderComponent(parameters + => parameters.Add(p => p.ActivatorContent, "
Custom Activator Content
")); + var menuComponent = comp.FindComponent(); + + // Assert + menuComponent.FindAll("button").Count.Should().Be(0); + menuComponent.Find("div.mud-menu").ClassList.Should().NotContain("mud-menu-button-hidden"); + menuComponent.Find("div#custom-activator").TextContent.Should().Be("Custom Activator Content"); + } } } diff --git a/src/MudBlazor/Components/Menu/MudMenu.razor b/src/MudBlazor/Components/Menu/MudMenu.razor index 95870ccba993..3473293b5fe1 100644 --- a/src/MudBlazor/Components/Menu/MudMenu.razor +++ b/src/MudBlazor/Components/Menu/MudMenu.razor @@ -10,8 +10,11 @@ @onpointerleave="@PointerLeaveAsync" @oncontextmenu="@((ActivationEvent == MouseEvent.RightClick ? ToggleMenuAsync : null)!)" @oncontextmenu:preventDefault="@(ActivationEvent == MouseEvent.RightClick)"> - - @if (ActivatorContent != null) + @if (ActivatorHidden) + { + @* No need to render anything *@ + } + else if (ActivatorContent != null) {
diff --git a/src/MudBlazor/Components/Menu/MudMenu.razor.cs b/src/MudBlazor/Components/Menu/MudMenu.razor.cs index 2a61c23f3cd7..86877e1af9d9 100644 --- a/src/MudBlazor/Components/Menu/MudMenu.razor.cs +++ b/src/MudBlazor/Components/Menu/MudMenu.razor.cs @@ -34,6 +34,7 @@ public partial class MudMenu : MudComponentBase, IActivatable protected string Classname => new CssBuilder("mud-menu") + .AddClass("mud-menu-button-hidden", ActivatorHidden) .AddClass(Class) .Build(); @@ -297,6 +298,8 @@ public partial class MudMenu : MudComponentBase, IActivatable [CascadingParameter] private MudMenu? ParentMenu { get; set; } + private bool ActivatorHidden => ActivatorContent is null && string.IsNullOrWhiteSpace(Label) && string.IsNullOrWhiteSpace(Icon); + /// /// Closes this menu. /// diff --git a/src/MudBlazor/Styles/components/_menu.scss b/src/MudBlazor/Styles/components/_menu.scss index 4d2666fb0e18..8a1243631a13 100644 --- a/src/MudBlazor/Styles/components/_menu.scss +++ b/src/MudBlazor/Styles/components/_menu.scss @@ -20,3 +20,8 @@ user-select: none; } } + +.mud-menu-button-hidden { + /* this ensures that the menu div doesn't use any space in the layout */ + position: absolute; +}