Skip to content

Commit

Permalink
[HxSidebar] responsive collapsing (#943)
Browse files Browse the repository at this point in the history
* Responsive sidebar collapsing
* HxSidebarItem - force re-rendering when Collapsed changed
* Responsive condition to show/hide Sidebar brand
* Fix items text missing on sidebar collapse
* To add Sidebar colors demo, fixed SidebarFooter overflowing on Collapse, reverted default border radius of SidebarItem to regular (was large), + docs adjustment
* Fixes #719
* [HxSidebar] PR code cleanup

---------

Co-authored-by: Robert Haken <[email protected]>
  • Loading branch information
crdo and hakenr authored Nov 19, 2024
1 parent 1394787 commit 97d1bb8
Show file tree
Hide file tree
Showing 24 changed files with 268 additions and 181 deletions.
11 changes: 4 additions & 7 deletions Havit.Blazor.Components.Web.Bootstrap/Navigation/HxSidebar.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@namespace Havit.Blazor.Components.Web.Bootstrap

<div class="@CssClassHelper.Combine("hx-sidebar", GetResponsiveCssClass("flex-??-grow-0"), GetCollapsedCssClass(), CssClass)" id="@Id">
<div class="@CssClassHelper.Combine("hx-sidebar", ResponsiveBreakpoint.GetCssClass("flex-??-grow-0"), GetCollapsedCssClass(), CssClass)" id="@Id">
<div class="nav-menu d-flex flex-column flex-grow-1 position-relative">

@if (HeaderTemplate is not null)
Expand All @@ -9,7 +9,7 @@
<CascadingValue Value="@(this)">
@HeaderTemplate
</CascadingValue>
<HxButton CssClass="@CssClassHelper.Combine("hx-sidebar-navbar-toggler", GetResponsiveCssClass("d-??-none"))"
<HxButton CssClass="@CssClassHelper.Combine("hx-sidebar-navbar-toggler", ResponsiveBreakpoint.GetCssClass("d-??-none"))"
data-bs-toggle="collapse"
data-bs-target="@($"#{NavContentElementId}")"
aria-controls="@NavContentElementId"
Expand All @@ -19,17 +19,14 @@
</div>
}

<div class="@CssClassHelper.Combine("hx-sidebar-collapse collapse", GetResponsiveCssClass("d-??-flex"), (!Collapsed) ? "overflow-auto": null)" id="@NavContentElementId">
<div class="@CssClassHelper.Combine("hx-sidebar-collapse collapse", ResponsiveBreakpoint.GetCssClass("d-??-flex"), (!Collapsed) ? "overflow-auto": null)" id="@NavContentElementId">
<div class="hx-sidebar-body mb-auto w-100">
<HxNav Id="@_navId" Orientation="NavOrientation.Vertical">
<CascadingValue Value="@(this)" IsFixed="true">
@ItemsTemplate
</CascadingValue>
</HxNav>
<div class="@CssClassHelper.Combine(
"hx-sidebar-toggler",
Collapsed ? "collapsed" : null,
GetResponsiveCssClass("d-??-block"))"
<div class="@CssClassHelper.Combine("hx-sidebar-toggler", Collapsed ? "collapsed" : null, ResponsiveBreakpoint.GetCssClass("d-??-block"))"
role="button"
@onclick="HandleCollapseToggleClick"
@onclick:stopPropagation="true">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,4 @@ private async Task HandleCollapseToggleClick()
Collapsed = !Collapsed;
await InvokeCollapsedChangedAsync(Collapsed);
}

private string GetResponsiveCssClass(string cssClassPattern)
{
return ResponsiveBreakpoint switch
{
SidebarResponsiveBreakpoint.None => cssClassPattern.Replace("-??-", "-"), // !!! Simplified for the use case of this component.
SidebarResponsiveBreakpoint.Small => cssClassPattern.Replace("??", "sm"),
SidebarResponsiveBreakpoint.Medium => cssClassPattern.Replace("??", "md"),
SidebarResponsiveBreakpoint.Large => cssClassPattern.Replace("??", "lg"),
SidebarResponsiveBreakpoint.ExtraLarge => cssClassPattern.Replace("??", "xl"),
SidebarResponsiveBreakpoint.Xxl => cssClassPattern.Replace("??", "xxl"),
_ => throw new InvalidOperationException($"Unknown nameof(ResponsiveBreakpoint) value {ResponsiveBreakpoint}")
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@media (min-width: 768px) {
.hx-sidebar,
.hx-sidebar .nav-menu {
max-height: var(--hx-sidebar-max-height);;
max-height: var(--hx-sidebar-max-height);
}
}

Expand Down Expand Up @@ -104,21 +104,10 @@
transform: translateY(-.15rem) rotate(15deg) translateZ(0px);
}

.hx-sidebar-navbar-toggler:hover {
::deep .hx-sidebar-navbar-toggler:hover {
color: var(--bs-primary);
}

::deep a[aria-expanded="true"] .bi-chevron-right {
transform: rotate(90deg);
}

/*Desktop collapsed*/
.hx-sidebar.collapsed {
width: var(--hx-sidebar-collapsed-width);
}

.hx-sidebar.collapsed ::deep .hx-sidebar-brand-name,
.hx-sidebar.collapsed ::deep .hx-sidebar-item-navlink-content-inner,
.hx-sidebar.collapsed ::deep .expand-icon {
display: none;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@namespace Havit.Blazor.Components.Web.Bootstrap

<a class="hx-sidebar-brand" href="./">
@if (LogoTemplate != null)
{
Expand All @@ -8,6 +9,5 @@
{
<div class="hx-sidebar-brand-shortname">@BrandNameShort</div>
}

<span class="hx-sidebar-brand-name">@BrandName</span>
<span class="@CssClassHelper.Combine("hx-sidebar-brand-name", ParentSidebar.Collapsed ? ParentSidebar.ResponsiveBreakpoint.GetCssClass("d-??-none") : null)">@BrandName</span>
</a>
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
{
<HxIcon Icon="Icon" />
}
<span class="hx-sidebar-footer-item-navlink-content-inner">
<span class="@CssClassHelper.Combine("hx-sidebar-footer-item-navlink-content-inner", TextCssClass)">
@Text
</span>
</div>

</HxNavLink>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ public partial class HxSidebarFooter
[Parameter] public NavLinkMatch? Match { get; set; } = NavLinkMatch.Prefix;

/// <summary>
/// Allows you to disable the item with <c>false</c>.
/// The default value is <c>true</c>.
/// Any additional CSS class to add.
/// </summary>
[Parameter] public string CssClass { get; set; }

/// <summary>
/// Any additional CSS class to add to inner text.
/// </summary>
[Parameter] public string TextCssClass { get; set; }

/// <summary>
/// Sub-items (not intended to be used for any other purpose).
/// </summary>
Expand Down
138 changes: 58 additions & 80 deletions Havit.Blazor.Components.Web.Bootstrap/Navigation/HxSidebarItem.razor
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
@namespace Havit.Blazor.Components.Web.Bootstrap
@using Havit.Blazor.Components.Web.Bootstrap.Internal
<CascadingValue Value="this" IsFixed="true">
<div class="@CssClassHelper.Combine(HighlightOnActiveChild ? "hx-sidebar-item-highlight-on-active-child" : null)">
<div @key="@ParentSidebar.Collapsed.ToString()"
class="@CssClassHelper.Combine(HighlightOnActiveChild ? "hx-sidebar-item-highlight-on-active-child" : null)">
@if (!HasExpandableContent)
{
<HxTooltip @key="@(GetHashCode() + ParentSidebar.Collapsed.ToString())"
WrapperCssClass="@(ParentSidebar.Collapsed ? "d-block" : null)"
<HxTooltip WrapperCssClass="@(ParentSidebar.Collapsed ? "d-block" : null)"
Placement="TooltipPlacement.Right"
Trigger="TooltipTrigger.Hover"
Text="@(ParentSidebar.Collapsed ? TooltipText : null)">
Expand All @@ -19,100 +20,77 @@

@* To hide the nav-content in mobile view *@
<div data-bs-toggle="collapse" data-bs-target="#@ParentSidebar.NavContentElementId" class="stretched-link">
<div class="hx-sidebar-item-navlink-content">
@if (Icon is not null)
{
<HxIcon CssClass="hx-sidebar-item-icon" Icon="Icon" />
}

<span class="hx-sidebar-item-navlink-content-inner">
@if (ContentTemplate is not null)
{
@ContentTemplate(new() { ItemExpanded = null })
}
@Text
</span>
</div>
<HxSidebarItemNavLinkContentInternal Text="@Text"
Icon="Icon"
InnerCssClass="@((ParentSidebar.Collapsed && ParentSidebarItem is null) ? ParentSidebar.ResponsiveBreakpoint.GetCssClass("d-??-none") : null)"
ContentTemplate="ContentTemplate" />

</div>
</HxNavLink>
</HxTooltip>
}
else
{
RenderFragment itemNavLinkContent =
@<div class="hx-sidebar-item-navlink-content">
@if (Icon is not null)
{
<HxIcon CssClass="hx-sidebar-item-icon" Icon="Icon" />
}

<span class="hx-sidebar-item-navlink-content-inner">
@if (ContentTemplate is not null)
{
@ContentTemplate(new() { ItemExpanded = expanded })
}
@Text
</span>

<HxIcon Icon="@BootstrapIcon.ChevronRight" CssClass="expand-icon" />
</div>;

@if (ParentSidebar.Collapsed)
{
<HxDropdown Direction="DropdownDirection.End" CssClass="hx-sidebar-item">
<HxNavLink Href="@Href"
Match="Match"
OnClick="@(OnClick.HasDelegate ? InvokeOnClickAsync : null)"
OnClickStopPropagation="OnClickStopPropagation"
OnClickPreventDefault="OnClickPreventDefault"
CssClass="this.CssClass"
Enabled="Enabled"
role="button"
data-bs-offset="0,0"
onmouseenter="bootstrap.Dropdown.getOrCreateInstance(event.target).show()"
onmouseleave="bootstrap.Dropdown.getInstance(event.target)?.hide()"
aria-expanded="@(expanded ? "true" : "false")">

@itemNavLinkContent

<HxDropdownMenu CssClass="hx-sidebar-subitems">
@ChildContent
</HxDropdownMenu>

</HxNavLink>
</HxDropdown>
}
else
{
<HxDropdown Direction="DropdownDirection.End" CssClass="@CssClassHelper.Combine("hx-sidebar-item d-none", ParentSidebar.Collapsed ? ParentSidebar.ResponsiveBreakpoint.GetCssClass("d-??-block") : null)">
<HxNavLink Href="@Href"
CssClass="@CssClassHelper.Combine(CssClass, "hx-sidebar-item")"
Match="Match"
Enabled="Enabled"
OnClick="HandleExpandableItemClick"
OnClick="@(OnClick.HasDelegate ? InvokeOnClickAsync : null)"
OnClickStopPropagation="OnClickStopPropagation"
OnClickPreventDefault="OnClickPreventDefault"
CssClass="@CssClass"
Enabled="Enabled"
role="button"
data-bs-toggle="@(expanded && !String.IsNullOrEmpty(Href) ? null : "collapse")"
data-bs-target="@("#" + _id)"
onmouseenter="bootstrap.Dropdown.getOrCreateInstance(event.target).show()"
onmouseleave="bootstrap.Dropdown.getInstance(event.target)?.hide()"
data-bs-toggle="@(!String.IsNullOrEmpty(Href) ? null : "collapse")"
data-bs-target="@(!ParentSidebar.Collapsed ? "#" + _id : null)"
aria-expanded="@(expanded ? "true" : "false")">

@itemNavLinkContent
<HxSidebarItemNavLinkContentInternal Text="@Text"
Icon="Icon"
ContentTemplate="ContentTemplate"
InnerCssClass="@CssClassHelper.Combine(ParentSidebar.Collapsed ? "d-none" : null)"
Expandable="true"
Expanded="expanded" />

<HxDropdownMenu CssClass="hx-sidebar-subitems">
@ChildContent
</HxDropdownMenu>

</HxNavLink>
</HxDropdown>

<HxCollapse Id="@_id"
Parent="@(ParentSidebar.MultipleItemsExpansion ? null : $"#{ParentSidebar._navId}")"
@ref="collapseComponent"
CssClass="hx-sidebar-subitems"
InitiallyExpanded="expanded"
OnShown="HandleCollapseShown"
OnHidden="HandleCollapseHidden">
<nav class="nav hx-sidebar-collapsed-nav">
@ChildContent
</nav>
</HxCollapse>
}
<HxNavLink Href="@Href"
CssClass="@CssClassHelper.Combine(CssClass, "hx-sidebar-item", ParentSidebar.Collapsed ? ParentSidebar.ResponsiveBreakpoint.GetCssClass("d-??-none") : null)"
Match="Match"
Enabled="Enabled"
OnClick="HandleExpandableItemClick"
OnClickStopPropagation="OnClickStopPropagation"
OnClickPreventDefault="OnClickPreventDefault"
role="button"
data-bs-toggle="@(!String.IsNullOrEmpty(Href) ? null : "collapse")"
data-bs-target="@("#" + _id)"
aria-expanded="@(expanded ? "true" : "false")">

<HxSidebarItemNavLinkContentInternal Text="@Text"
Icon="Icon"
ContentTemplate="ContentTemplate"
Expandable="true"
Expanded="expanded" />

</HxNavLink>

<HxCollapse Id="@_id"
Parent="@(ParentSidebar.MultipleItemsExpansion ? null : $"#{ParentSidebar._navId}")"
@ref="collapseComponent"
CssClass="@CssClassHelper.Combine("hx-sidebar-subitems", ParentSidebar.Collapsed ? ParentSidebar.ResponsiveBreakpoint.GetCssClass("d-??-none") : null)"
InitiallyExpanded="expanded"
OnShown="HandleCollapseShown"
OnHidden="HandleCollapseHidden">
<nav class="nav hx-sidebar-collapsed-nav">
@ChildContent
</nav>
</HxCollapse>
}
</div>
</CascadingValue>
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@
border-radius: var(--hx-sidebar-item-border-radius);
}

::deep .hx-sidebar-item-navlink-content {
display: flex;
flex-direction: row;
align-items: center;
align-self: stretch;
gap: .75rem;
}

::deep .hx-sidebar-subitems {
--bs-dropdown-border-radius: calc(var(--hx-sidebar-item-border-radius) + var(--bs-dropdown-padding-x));
}
Expand All @@ -31,63 +23,40 @@
display: flex;
}

::deep .hx-sidebar-subitems.show .hx-sidebar-item-navlink-content-inner {
display: inline-flex;
}

.hx-sidebar-item-navlink-content-inner {
display: flex;
align-items: center;
flex-grow: 1;
}

::deep .hx-sidebar-collapsed-nav {
flex-direction: column;
margin-block: .25rem;
}

::deep a.nav-link:hover,
.hx-sidebar-item-highlight-on-active-child:has(.hx-sidebar-subitems .nav-link.active) ::deep > a.nav-link:hover {
background-color: rgba(var(--hx-sidebar-item-hover-background-color), var(--hx-sidebar-item-hover-background-opacity));
color: var(--hx-sidebar-item-hover-color);
}

::deep a.nav-link.active {
background-color: rgba(var(--hx-sidebar-item-active-background-color), var(--hx-sidebar-item-active-background-opacity));
color: var(--hx-sidebar-item-active-color);
font-weight: var(--hx-sidebar-item-active-font-weight);
}

/* Font-weight only for first level items */
.hx-sidebar-item-highlight-on-active-child:has(.hx-sidebar-subitems .nav-link.active) ::deep > a.nav-link {
background-color: rgba(var(--hx-sidebar-parent-item-active-background-color), var(--hx-sidebar-parent-item-active-background-opacity));
color: var(--hx-sidebar-parent-item-active-color);
font-weight: var(--hx-sidebar-parent-item-active-font-weight);
}

::deep i {
color: var(--hx-sidebar-item-icon-color);
transition: transform .35s ease;
transform-origin: .5em 50%;
}

::deep a.nav-link:hover .hx-sidebar-item-icon {
color: var(--hx-sidebar-item-hover-icon-color);
}

::deep a.nav-link.active .hx-sidebar-item-icon {
color: var(--hx-sidebar-item-active-icon-color);
/* Background and color for rest of the content */
.hx-sidebar-item-highlight-on-active-child:has(.hx-sidebar-subitems .nav-link.active) ::deep .dropend > a.nav-link,
.hx-sidebar-item-highlight-on-active-child:has(.hx-sidebar-subitems .nav-link.active) ::deep > a.nav-link {
background-color: rgba(var(--hx-sidebar-parent-item-active-background-color), var(--hx-sidebar-parent-item-active-background-opacity));
color: var(--hx-sidebar-parent-item-active-color);
}

.hx-sidebar-item-highlight-on-active-child:has(.hx-sidebar-subitems .nav-link.active) > ::deep.hx-sidebar-item .hx-sidebar-item-icon {
.hx-sidebar-item-highlight-on-active-child:has(.hx-sidebar-subitems .nav-link.active) > ::deep .hx-sidebar-item .hx-sidebar-item-icon {
color: var(--hx-sidebar-parent-item-active-icon-color);
}

::deep .bi-chevron-right {
font-size: .75rem;
margin-left: .5rem;
::deep a.nav-link:hover,
.hx-sidebar-item-highlight-on-active-child:has(.hx-sidebar-subitems .nav-link.active) ::deep > a.nav-link:hover {
background-color: rgba(var(--hx-sidebar-item-hover-background-color), var(--hx-sidebar-item-hover-background-opacity));
color: var(--hx-sidebar-item-hover-color);
}

::deep .dropdown-menu {
max-height: 100vh;
max-height: 75vh;
overflow: auto;
}
Loading

0 comments on commit 97d1bb8

Please sign in to comment.