Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
347 changes: 176 additions & 171 deletions src/BootstrapBlazor/Components/Menu/Menu.razor.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use "../../wwwroot/scss/variables" as *;
@use "../../wwwroot/scss/variables" as *;

.menu {
--bb-menu-nav-pading: #{$bb-menu-nav-pading};
Expand All @@ -12,163 +12,168 @@
--bs-nav-link-color: var(--bs-body-color);
}

.menu .nav {
padding: var(--bb-menu-nav-pading);
border-bottom: var(--bb-menu-nav-border-bottom);
}

.menu > .nav > li {
position: relative;
border-bottom: 2px solid transparent;
}

.menu > .nav > li:has(.active):after {
width: 100%;
left: 0;
}

.menu > .nav > li:after {
content: "";
position: absolute;
background-color: var(--bb-menu-bar-bg);
left: 50%;
height: 2px;
width: 0;
transition: width .3s linear, left .3s linear;
}

.menu .nav .nav-link, .cascade .dropdown-item {
padding: 0.5rem 1rem;
user-select: none;
white-space: nowrap;
position: relative;
}

.menu .nav .nav-link.active, .menu .nav .nav-link:not(.disabled):hover {
color: var(--bb-menu-active-color);
}

.menu .nav .dropdown-menu, .cascade .dropdown-menu {
overflow: unset;
max-height: unset;
margin-block-start: 10px;
}

.menu .nav .dropdown-menu .nav-link:not(.disabled):hover, .menu .nav .dropdown-menu .nav-link.active, .menu .submenu .nav-link.active, .menu .submenu .nav-link:not(.disabled):hover, .cascade .dropdown-item:not(.disabled):hover, .cascade .dropdown-item.active, .cascade .nav .nav-link:not(.disabled):hover, .cascade .nav .nav-link.active {
background-color: var(--bb-menu-item-hover-bg);
color: var(--bb-menu-item-hover-color);
}

.menu .nav .sub-menu, .cascade .has-leaf .sub-menu {
display: none;
background-color: var(--bb-menu-sub-bg);
white-space: nowrap;
border: solid 1px var(--bs-border-color);
border-radius: var(--bs-border-radius);
padding: 0.5rem 0;
position: absolute;
left: 100%;
top: -9px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}

.menu .nav .dropdown-menu, .menu .nav .sub-menu, .cascade .sub-menu {
min-width: var(--bb-menu-min-width);
}

.menu .nav .nav-link:hover > .sub-menu, .cascade .dropdown-item:hover > .sub-menu {
display: block;
}

.menu .nav-item:hover > .nav-link > .sub-menu {
left: calc(100% + 20px);
top: -15px;
}

.menu .has-leaf > .nav-link-right {
transition: transform .3s linear;
position: absolute;
right: 10px;
top: 10px;
}

.menu .nav .sub-menu .has-leaf > .nav-link-right {
right: 12px;
top: 10px;
}

.menu .has-leaf:hover > .nav-link-right {
transform: rotate(-90deg);
}

.menu .menu-text {
overflow: hidden;
vertical-align: top;
display: inline-block;
opacity: 1;
transition: opacity .3s linear;
}

.menu .nav .nav-link .menu-text {
margin-left: 4px;
}

.menu .nav .dropdown-toggle[aria-expanded="true"] + .dropdown-menu-arrow,
.cascade .show .dropdown-menu-arrow {
display: block;
}

.menu .nav .dropdown .dropdown-menu-arrow {
left: calc(50% - 6px);
}

.menu .nav-link {
display: flex;
align-items: center;
line-height: 21px;
transition: background-color .3s linear, color .3s linear;
}

.menu .nav-link > div {
white-space: nowrap;
}

.menu .widget > .badge:not(:first-child) {
margin-left: 4px;
}

.menu.is-vertical {
padding: 0 .5rem;
border: none;
}

.menu .submenu {
margin: 0;
padding: 0;
list-style: none;
}

.menu .submenu li {
margin-block-start: 2px;
}

.menu .submenu .nav-link {
border-radius: var(--bs-border-radius);
padding: .5rem;
}

.menu .submenu .nav-link .arrow {
transition: transform .3s linear;
}

.menu .submenu .nav-link[aria-expanded="true"] > .arrow {
transform: rotate(-90deg);
}

.menu .submenu .nav-link .menu-text,
.menu .submenu .nav-link .widget {
margin: 0 4px;
}
.menu .nav {
padding: var(--bb-menu-nav-pading);
border-bottom: var(--bb-menu-nav-border-bottom);
}

.menu > .nav > li {
position: relative;
border-bottom: 2px solid transparent;
}

.menu > .nav > li:has(.active):after {
width: 100%;
left: 0;
}

.menu > .nav > li:after {
content: "";
position: absolute;
background-color: var(--bb-menu-bar-bg);
left: 50%;
height: 2px;
width: 0;
transition: width .3s linear, left .3s linear;
}

.menu .nav .nav-link, .cascade .dropdown-item {
padding: 0.5rem 1rem;
user-select: none;
white-space: nowrap;
position: relative;
}

.menu .nav .nav-link.active, .menu .nav .nav-link:not(.disabled):hover {
color: var(--bb-menu-active-color);
}

.menu .nav .dropdown-menu, .cascade .dropdown-menu {
overflow: unset;
max-height: unset;
margin-block-start: 10px;
}

.menu .nav .dropdown-menu .nav-link:not(.disabled):hover, .menu .nav .dropdown-menu .nav-link.active, .menu .submenu .nav-link.active, .menu .submenu .nav-link:not(.disabled):hover, .cascade .dropdown-item:not(.disabled):hover, .cascade .dropdown-item.active, .cascade .nav .nav-link:not(.disabled):hover, .cascade .nav .nav-link.active {
background-color: var(--bb-menu-item-hover-bg);
color: var(--bb-menu-item-hover-color);
}

.menu .nav .sub-menu, .cascade .has-leaf .sub-menu {
display: none;
background-color: var(--bb-menu-sub-bg);
white-space: nowrap;
border: solid 1px var(--bs-border-color);
border-radius: var(--bs-border-radius);
padding: 0.5rem 0;
position: absolute;
left: 100%;
top: -9px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}

.menu .nav .dropdown-menu, .menu .nav .sub-menu, .cascade .sub-menu {
min-width: var(--bb-menu-min-width);
}

.menu .nav .nav-link:hover > .sub-menu, .cascade .dropdown-item:hover > .sub-menu {
display: block;
}

.menu .nav-item:hover > .nav-link > .sub-menu {
left: calc(100% + 20px);
top: -15px;
}

.menu .has-leaf > .nav-link-right {
transition: transform .3s linear;
position: absolute;
right: 10px;
top: 10px;
}

.menu .nav .sub-menu .has-leaf > .nav-link-right {
right: 12px;
top: 10px;
}

.menu .has-leaf:hover > .nav-link-right {
transform: rotate(-90deg);
}

.menu .menu-text {
overflow: hidden;
vertical-align: top;
display: inline-block;
opacity: 1;
transition: opacity .3s linear;
}

.menu .nav .nav-link .menu-text {
margin-left: 4px;
}

.menu .nav .dropdown-toggle[aria-expanded="true"] + .dropdown-menu-arrow,
.cascade .show .dropdown-menu-arrow {
display: block;
}

.menu .nav .dropdown .dropdown-menu-arrow {
left: calc(50% - 6px);
}

.menu .nav-link {
display: flex;
align-items: center;
line-height: 21px;
transition: background-color .3s linear, color .3s linear;
}

.menu .nav-link > .submenu-link {
flex: 1 1 auto;
min-width: 0;
width: 1%;
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The width: 1% property appears to be used as a flexbox hack to force the container to respect min-width: 0. While this is a known technique, it may not be necessary here since min-width: 0 with flex: 1 1 auto is typically sufficient for text truncation. Consider testing if removing width: 1% still achieves the desired overflow behavior.

Suggested change
width: 1%;

Copilot uses AI. Check for mistakes.
white-space: nowrap;
overflow: hidden;
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overflow prevention is missing text-overflow: ellipsis; to show an ellipsis (...) when text is truncated. Without this property, the text will be cut off abruptly. Consider adding text-overflow: ellipsis; to improve the user experience when menu text overflows.

Suggested change
overflow: hidden;
overflow: hidden;
text-overflow: ellipsis;

Copilot uses AI. Check for mistakes.
margin-inline-end: .5rem;
}

.menu .widget > .badge:not(:first-child) {
margin-left: 4px;
}

.menu.is-vertical {
padding: 0 .5rem;
border: none;
}

.menu .submenu {
margin: 0;
padding: 0;
list-style: none;
}

.menu .submenu li {
margin-block-start: 2px;
}

.menu .submenu .nav-link {
border-radius: var(--bs-border-radius);
padding: .5rem;
}

.menu .submenu .nav-link .arrow {
transition: transform .3s linear;
}

.menu .submenu .nav-link[aria-expanded="true"] > .arrow {
transform: rotate(-90deg);
}

.menu .submenu .nav-link .menu-text,
.menu .submenu .nav-link .widget {
margin: 0 4px;
}

.cascade .dropdown-menu {
right: 0;
Expand All @@ -189,18 +194,18 @@
right: 0;
}

.menu.is-bottom .nav {
flex-wrap: nowrap;
justify-content: space-around;
border-bottom: none;
border-top: 1px solid var(--bs-gray-400);
}
.menu.is-bottom .nav {
flex-wrap: nowrap;
justify-content: space-around;
border-bottom: none;
border-top: 1px solid var(--bs-gray-400);
}

.menu.is-bottom .nav .dropdown-toggle[aria-expanded="true"] + .dropdown-menu-arrow {
display: none;
}
.menu.is-bottom .nav .dropdown-toggle[aria-expanded="true"] + .dropdown-menu-arrow {
display: none;
}

.menu.is-bottom > .nav > li:after,
.menu.is-bottom .dropdown-menu-arrow:after {
content: none;
}
.menu.is-bottom > .nav > li:after,
.menu.is-bottom .dropdown-menu-arrow:after {
content: none;
}
4 changes: 2 additions & 2 deletions src/BootstrapBlazor/Components/Menu/MenuLink.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@namespace BootstrapBlazor.Components
@namespace BootstrapBlazor.Components
@inherits BootstrapComponentBase

@if (Item.IsDisabled)
Expand All @@ -11,7 +11,7 @@
else
{
<NavLink @attributes="@AdditionalAttributes" @onclick:preventDefault="@PreventDefault" class="@ClassString" href="@HrefString" target="@TargetString" Match="@Item.Match" style="@StyleClassString" aria-expanded="@AriaExpandedString">
<div class="flex-fill">
<div class="submenu-link">
<i class="@IconString"></i>
<span class="menu-text">@Item.Text</span>
</div>
Expand Down