From 79c13a5cff4812f6b4850e19a98dc8ca5908e768 Mon Sep 17 00:00:00 2001 From: master <> Date: Sat, 7 Mar 2026 16:36:12 +0200 Subject: [PATCH] Redesign sidebar navigation with warmer UX and smoother animations - Add amber dot indicators to group headers with hover scale effect - Replace hard divider lines with gradient fade separators - Active nav item gets rounded pill with amber glow box-shadow - Section chevrons start subtle (25% opacity), reveal on hover - Expand/collapse adds opacity fade alongside CSS grid height animation - Chevron rotation uses spring-like cubic-bezier bounce easing - Child items get gradient guide line and active dot indicator - Press feedback via scale(0.98) on :active for all nav items - Collapsed rail active state uses small amber left bar - Footer divider uses gradient fade, version text fades on collapse - Sidebar background uses subtle vertical gradient - Right edge border gets amber-tinted gradient glow at center Co-Authored-By: Claude Opus 4.6 --- .../app-sidebar/app-sidebar.component.ts | 257 ++++++++++++------ .../app-sidebar/sidebar-nav-item.component.ts | 102 +++++-- 2 files changed, 258 insertions(+), 101 deletions(-) diff --git a/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts b/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts index 5d9cbd955..7c523934b 100644 --- a/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts +++ b/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts @@ -105,6 +105,7 @@ interface NavSectionGroup { [attr.aria-expanded]="!sidebarPrefs.collapsedGroups().has(group.id)" [attr.aria-controls]="'nav-grp-' + group.id" > + {{ group.label }}
} @if (!collapsed && section.displayChildren.length > 0) { - +
-
+
@for (child of section.displayChildren; track child.id) { app-sidebar-nav-item { + flex: 1; + min-width: 0; + } + + .sb-section__chevron { + flex-shrink: 0; display: flex; align-items: center; justify-content: center; - width: 22px; - height: 22px; + width: 20px; + height: 20px; border: none; - border-radius: 4px; + border-radius: 5px; background: transparent; color: var(--color-sidebar-text-muted); cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s, color 0.15s; - z-index: 2; + opacity: 0.25; + transition: opacity 0.2s, background 0.2s, color 0.2s, transform 0.15s; + margin-right: 0.25rem; &:hover { - background: rgba(255, 255, 255, 0.06); - color: var(--color-sidebar-text-heading); + opacity: 1 !important; + background: rgba(245, 166, 35, 0.12); + color: var(--color-sidebar-active-text); + transform: scale(1.1); } &:focus-visible { - opacity: 1; - outline: 1px solid var(--color-sidebar-active-border); + opacity: 1 !important; + outline: 1.5px solid var(--color-sidebar-active-border); + outline-offset: -1.5px; } } - /* Show fold button on hover or when section is folded */ - .sb-section__head:hover .sb-section__fold, - .sb-section--folded .sb-section__fold { - opacity: 1; + .sb-section__head:hover .sb-section__chevron { + opacity: 0.7; } - .sb-section__fold-icon { - transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); + .sb-section--folded .sb-section__chevron { + opacity: 0.45; } - .sb-section--folded .sb-section__fold-icon { + .sb-section__chevron-icon { + transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + } + + .sb-section--folded .sb-section__chevron-icon { transform: rotate(-90deg); } @@ -435,7 +502,7 @@ interface NavSectionGroup { .sb-section__body { display: grid; grid-template-rows: 1fr; - transition: grid-template-rows 0.22s cubic-bezier(0.4, 0, 0.2, 1); + transition: grid-template-rows 0.28s cubic-bezier(0.4, 0, 0.2, 1); } .sb-section--folded .sb-section__body { @@ -444,16 +511,37 @@ interface NavSectionGroup { .sb-section__body-inner { overflow: hidden; - margin-left: 1.25rem; - border-left: 1px solid rgba(245, 166, 35, 0.12); + margin-left: 1.375rem; + padding-left: 0.375rem; + position: relative; + transition: opacity 0.24s ease; + + &::before { + content: ''; + position: absolute; + left: 0; + top: 0.125rem; + bottom: 0.125rem; + width: 1px; + background: linear-gradient( + 180deg, + rgba(245, 166, 35, 0.2) 0%, + rgba(245, 166, 35, 0.08) 100% + ); + border-radius: 1px; + } + } + + .sb-section--folded .sb-section__body-inner { + opacity: 0; } /* ================================================================ - Footer with collapse toggle + Footer ================================================================ */ .sidebar__footer { flex-shrink: 0; - padding: 0.375rem 0.75rem 0.5rem; + padding: 0.5rem 0.625rem 0.625rem; } .sidebar__collapse-btn { @@ -461,28 +549,27 @@ interface NavSectionGroup { align-items: center; justify-content: center; width: 100%; - height: 28px; - border: 1px solid var(--color-sidebar-divider); - border-radius: 6px; + height: 30px; + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 8px; background: transparent; color: var(--color-sidebar-text-muted); cursor: pointer; - margin-bottom: 0.375rem; - transition: color 0.15s, background 0.15s, border-color 0.15s; + margin-bottom: 0.5rem; + transition: color 0.2s, background 0.2s, border-color 0.2s; &:hover { - color: var(--color-sidebar-text-heading); - background: rgba(255, 255, 255, 0.04); - border-color: rgba(255, 255, 255, 0.12); + color: var(--color-sidebar-active-text); + background: rgba(245, 166, 35, 0.06); + border-color: rgba(245, 166, 35, 0.15); } &:focus-visible { - outline: 1px solid var(--color-sidebar-active-border); - outline-offset: -1px; + outline: 1.5px solid var(--color-sidebar-active-border); + outline-offset: -1.5px; } } - /* Hide collapse button on mobile (mobile uses the close X) */ @media (max-width: 991px) { .sidebar__collapse-btn { display: none; @@ -491,8 +578,14 @@ interface NavSectionGroup { .sidebar__footer-divider { height: 1px; - background: var(--color-sidebar-divider); - margin-bottom: 0.375rem; + background: linear-gradient( + 90deg, + transparent 0%, + var(--color-sidebar-divider) 30%, + var(--color-sidebar-divider) 70%, + transparent 100% + ); + margin-bottom: 0.5rem; } .sidebar__version { @@ -504,6 +597,7 @@ interface NavSectionGroup { color: var(--color-sidebar-version); text-align: center; white-space: nowrap; + transition: font-size 0.2s, opacity 0.2s; } .sidebar--collapsed .sidebar__footer { @@ -512,6 +606,7 @@ interface NavSectionGroup { .sidebar--collapsed .sidebar__version { font-size: 0; + opacity: 0; overflow: hidden; } `], diff --git a/src/Web/StellaOps.Web/src/app/layout/app-sidebar/sidebar-nav-item.component.ts b/src/Web/StellaOps.Web/src/app/layout/app-sidebar/sidebar-nav-item.component.ts index 60d4016f9..db5416a37 100644 --- a/src/Web/StellaOps.Web/src/app/layout/app-sidebar/sidebar-nav-item.component.ts +++ b/src/Web/StellaOps.Web/src/app/layout/app-sidebar/sidebar-nav-item.component.ts @@ -282,26 +282,35 @@ export interface NavItem { align-items: center; gap: 0.625rem; padding: 0.4375rem 0.75rem; - margin: 0 0.25rem 1px; + margin: 1px 0.125rem; color: var(--color-sidebar-text); text-decoration: none; font-size: 0.8125rem; font-weight: 450; - transition: all 0.12s; - border-radius: 6px; + transition: + background 0.18s cubic-bezier(0.4, 0, 0.2, 1), + color 0.18s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.18s cubic-bezier(0.4, 0, 0.2, 1), + border-color 0.18s cubic-bezier(0.4, 0, 0.2, 1), + transform 0.12s cubic-bezier(0.4, 0, 0.2, 1); + border-radius: 8px; cursor: pointer; position: relative; min-width: 0; border-left: 2px solid transparent; &:hover { - background: var(--color-sidebar-hover); + background: rgba(245, 166, 35, 0.08); color: var(--color-sidebar-text-heading); } + &:active { + transform: scale(0.98); + } + &:focus-visible { - outline: 2px solid var(--color-sidebar-active-border); - outline-offset: -2px; + outline: 1.5px solid var(--color-sidebar-active-border); + outline-offset: -1.5px; } } @@ -310,39 +319,75 @@ export interface NavItem { color: var(--color-sidebar-active-text); font-weight: 600; border-left-color: var(--color-sidebar-active-border); + box-shadow: + 0 0 0 1px rgba(245, 166, 35, 0.08), + 0 1px 6px rgba(245, 166, 35, 0.06); .nav-item__icon { color: var(--color-sidebar-active-text); + opacity: 1; } &:hover { background: var(--color-sidebar-active-bg); + box-shadow: + 0 0 0 1px rgba(245, 166, 35, 0.12), + 0 2px 10px rgba(245, 166, 35, 0.08); } } .nav-item--child { - font-size: 0.8125rem; - border-radius: 0; - margin: 0 0.25rem 1px; - padding: 0.375rem 0.75rem; + font-size: 0.7875rem; + font-weight: 420; + border-radius: 6px; + margin: 1px 0.125rem; + padding: 0.3125rem 0.625rem; border-left: 2px solid transparent; .nav-item__icon { - width: 16px; - height: 16px; + width: 15px; + height: 15px; + opacity: 0.45; svg { - width: 14px; - height: 14px; + width: 13px; + height: 13px; } } } + .nav-item--child:hover { + background: rgba(245, 166, 35, 0.06); + + .nav-item__icon { + opacity: 0.7; + } + } + .nav-item--child.nav-item--active { - background: transparent; + background: rgba(245, 166, 35, 0.1); color: var(--color-sidebar-active-text); font-weight: 600; border-left-color: transparent; + box-shadow: none; + + .nav-item__icon { + opacity: 1; + color: var(--color-sidebar-active-text); + } + + &::before { + content: ''; + position: absolute; + left: -0.625rem; + top: 50%; + transform: translateY(-50%); + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--color-sidebar-active-border); + box-shadow: 0 0 6px rgba(245, 166, 35, 0.3); + } } .nav-item__icon { @@ -352,11 +397,12 @@ export interface NavItem { justify-content: center; width: 18px; height: 18px; - opacity: 0.65; + opacity: 0.55; + transition: opacity 0.18s, color 0.18s; } .nav-item:hover .nav-item__icon { - opacity: 0.9; + opacity: 0.85; } .nav-item--active .nav-item__icon { @@ -384,23 +430,39 @@ export interface NavItem { display: flex; align-items: center; justify-content: center; + box-shadow: 0 1px 4px rgba(245, 166, 35, 0.2); } /* Collapsed: icon-only mode */ .nav-item--icon-only { justify-content: center; padding: 0.5rem; - margin: 0 0.125rem 1px; + margin: 1px 0.125rem; border-left-color: transparent; + border-radius: 8px; .nav-item__icon { - opacity: 0.8; + opacity: 0.7; + } + + &:hover .nav-item__icon { + opacity: 1; } } .nav-item--icon-only.nav-item--active { border-left-color: transparent; - border-radius: 6px; + + &::after { + content: ''; + position: absolute; + left: -1px; + top: 25%; + bottom: 25%; + width: 2px; + border-radius: 2px; + background: var(--color-sidebar-active-border); + } } `], changeDetection: ChangeDetectionStrategy.OnPush,