From 640612033c7a296d56a3e3d7497fa99492947011 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 23 Nov 2023 00:34:05 +0100 Subject: [PATCH 01/17] [DURACOM-195] New host-window service methods --- src/app/shared/host-window.service.ts | 57 +++++++++++++++---- .../testing/host-window-service.stub.ts | 4 ++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/app/shared/host-window.service.ts b/src/app/shared/host-window.service.ts index 6d13d921e07..1c71bf5075e 100644 --- a/src/app/shared/host-window.service.ts +++ b/src/app/shared/host-window.service.ts @@ -10,13 +10,15 @@ import { AppState } from '../app.reducer'; import { CSSVariableService } from './sass-helper/css-variable.service'; export enum WidthCategory { - XS, - SM, - MD, - LG, - XL + XS = 0, + SM = 1, + MD = 2, + LG = 3, + XL = 4, } +export const maxMobileWidth = WidthCategory.SM; + const hostWindowStateSelector = (state: AppState) => state.hostWindow; const widthSelector = createSelector(hostWindowStateSelector, (hostWindow: HostWindowState) => hostWindow.width); @@ -31,11 +33,11 @@ export class HostWindowService { /* See _exposed_variables.scss */ variableService.getAllVariables() .subscribe((variables) => { - this.breakPoints.XL_MIN = parseInt(variables['--bs-xl-min'], 10); - this.breakPoints.LG_MIN = parseInt(variables['--bs-lg-min'], 10); - this.breakPoints.MD_MIN = parseInt(variables['--bs-md-min'], 10); - this.breakPoints.SM_MIN = parseInt(variables['--bs-sm-min'], 10); - }); + this.breakPoints.XL_MIN = parseInt(variables['--bs-xl-min'], 10); + this.breakPoints.LG_MIN = parseInt(variables['--bs-lg-min'], 10); + this.breakPoints.MD_MIN = parseInt(variables['--bs-md-min'], 10); + this.breakPoints.SM_MIN = parseInt(variables['--bs-sm-min'], 10); + }); } private getWidthObs(): Observable { @@ -99,6 +101,41 @@ export class HostWindowService { ); } + is(exactWidthCat: WidthCategory): Observable { + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat === exactWidthCat), + distinctUntilChanged() + ); + } + + isIn(widthCatArray: [WidthCategory]): Observable { + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCatArray.includes(widthCat)), + distinctUntilChanged() + ); + } + + isUpTo(maxWidthCat: WidthCategory): Observable { + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat <= maxWidthCat), + distinctUntilChanged() + ); + } + + isMobile(): Observable { + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat <= maxMobileWidth), + distinctUntilChanged() + ); + } + + isDesktop(): Observable { + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat > maxMobileWidth), + distinctUntilChanged() + ); + } + isXsOrSm(): Observable { return observableCombineLatest( this.isXs(), diff --git a/src/app/shared/testing/host-window-service.stub.ts b/src/app/shared/testing/host-window-service.stub.ts index ecb8c26acbb..b64d6fbffcb 100644 --- a/src/app/shared/testing/host-window-service.stub.ts +++ b/src/app/shared/testing/host-window-service.stub.ts @@ -20,4 +20,8 @@ export class HostWindowServiceStub { isXsOrSm(): Observable { return this.isXs(); } + + isMobile(): Observable { + return this.isXs(); + } } From e84773afb25b20a9689a614f20361f7e8b086fc9 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 23 Nov 2023 10:36:53 +0100 Subject: [PATCH 02/17] [DURACOM-195] Base components --- .../header-navbar-wrapper.component.ts | 8 ++- src/app/header/header.component.html | 2 +- src/app/header/header.component.scss | 45 +++++++------ src/app/header/header.component.ts | 8 ++- src/app/navbar/navbar.component.html | 4 +- src/app/navbar/navbar.component.scss | 64 ++++++++++--------- src/app/navbar/navbar.component.ts | 11 ++-- src/styles/_custom_variables.scss | 9 ++- 8 files changed, 83 insertions(+), 68 deletions(-) diff --git a/src/app/header-nav-wrapper/header-navbar-wrapper.component.ts b/src/app/header-nav-wrapper/header-navbar-wrapper.component.ts index a610b2cb026..9786f0ed1d0 100644 --- a/src/app/header-nav-wrapper/header-navbar-wrapper.component.ts +++ b/src/app/header-nav-wrapper/header-navbar-wrapper.component.ts @@ -5,6 +5,7 @@ import { hasValue } from '../shared/empty.util'; import { Observable, Subscription } from 'rxjs'; import { MenuService } from '../shared/menu/menu.service'; import { MenuID } from '../shared/menu/menu-id.model'; +import { HostWindowService, WidthCategory } from '../shared/host-window.service'; /** * This component represents a wrapper for the horizontal navbar and the header @@ -18,15 +19,20 @@ export class HeaderNavbarWrapperComponent implements OnInit, OnDestroy { @HostBinding('class.open') isOpen = false; private sub: Subscription; public isNavBarCollapsed: Observable; + public isMobile$: Observable; + menuID = MenuID.PUBLIC; + maxMobileWidth = WidthCategory.SM; constructor( private store: Store, - private menuService: MenuService + private menuService: MenuService, + protected windowService: HostWindowService, ) { } ngOnInit(): void { + this.isMobile$ = this.windowService.isUpTo(this.maxMobileWidth); this.isNavBarCollapsed = this.menuService.isMenuCollapsed(this.menuID); this.sub = this.isNavBarCollapsed.subscribe((isCollapsed) => this.isOpen = !isCollapsed); } diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 32b42dc8a7c..8ff76242cda 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -11,7 +11,7 @@ -
+
+ + + + + + + + + + + + diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.scss b/src/app/admin/admin-sidebar/admin-sidebar.component.scss index 5ed0142c7f9..c2f3279a8ea 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.scss +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.scss @@ -1,123 +1,140 @@ :host { - --ds-icon-z-index: 10; - - left: 0; - top: 0; - height: 100vh; - flex: 1 1 auto; - nav { - background-color: var(--ds-admin-sidebar-bg); - height: 100%; - flex-direction: column; - > div { - width: 100%; - &.sidebar-top-level-items { - flex: 1; - overflow: auto; - @include dark-scrollbar; - } + + /* SIDEBAR SIZE AND POSITION */ + + /* Sidebar hierarchy: + § nav + § .sidebar-full-width-container (any OPTIONAL full width element with no horizontal margin or padding - it can be nested) + § .sidebar-section-wrapper + § .sidebar-fixed-element-wrapper + § .sidebar-collapsible-element-outer-wrapper + § .sidebar-collapsible-element-inner-wrapper + § .sidebar-item + */ + + // Sidebar position + position: fixed; + left: 0; + top: 0; + z-index: var(--ds-sidebar-z-index); + + // Sidebar size and content position + nav#admin-sidebar { + + max-width: var(--ds-admin-sidebar-fixed-element-width); // Sidebar collapsed width + + display: flex; + flex-direction: column; + flex-wrap: nowrap; + + div#sidebar-top-level-items-container { + flex: 1 1 auto; // Fill available vertical space + overflow-x: hidden; + overflow-y: auto; + @include dark-scrollbar; + } + + img#admin-sidebar-logo { + width: 20px; + } + + ::ng-deep { + + // This class must be applied to any nested wrapper containing a sidebar section + .sidebar-full-width-container { + width: 100%; + padding-left: 0 !important; + padding-right: 0 !important; + margin-left: 0 !important; + margin-right: 0 !important; + } + + // This class must be applied to the innermost block element containing a section or subsection link + // (it can be applied together with `sidebar-collapsible-element-inner-wrapper`) + .sidebar-item { + padding-top: 1rem; + padding-bottom: 1rem; + } + + // These classes handle the collapsing behavior + .sidebar-section-wrapper { + + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: stretch; + + // These elements have fixed width and determine the width of the collapsed sidebar + & > .sidebar-fixed-element-wrapper { + min-width: var(--ds-admin-sidebar-fixed-element-width); + flex: 1 1 auto; // Fill available space + + // Align the icons + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; } - &.inactive ::ng-deep .sidebar-collapsible { - margin-left: calc(-1 * var(--ds-sidebar-items-width)); + & > .sidebar-collapsible-element-outer-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; // make inner wrapper slide on the left when collapsing + max-width: calc(100% - var(--ds-admin-sidebar-fixed-element-width)); // fill available space + padding-left: var(--ds-dark-scrollbar-width); // leave room for scrollbar + overflow-x: hidden; // hide inner wrapper when sidebar is collapsed + + // These elements have fixed width and slide on the left when the sidebar is collapsed + // Their content should fill all available space + & > .sidebar-collapsible-element-inner-wrapper { + min-width: calc(var(--ds-admin-sidebar-collapsible-element-width) - var(--ds-dark-scrollbar-width)); + height: 100%; + padding-right: 16px !important; + } } + } - .navbar-nav { - .admin-menu-header { - background-color: var(--ds-admin-sidebar-header-bg); + } - .sidebar-section { - background-color: inherit; - } + // Set here any style that depends on the sidebar status + &.collapsed, &.transitioning, &.expanded { } - .logo-wrapper { - img { - height: 20px; - } - } - .section-header-text { - line-height: 1.5; - } + } - .navbar-brand { - margin-right: 0; - } + /* SIDEBAR STYLE */ - } - } + nav#admin-sidebar { + background-color: var(--ds-admin-sidebar-bg); - ::ng-deep { - .navbar-nav { - .sidebar-section { - display: flex; - align-content: stretch; - background-color: var(--ds-admin-sidebar-bg); - overflow-x: visible; - - .nav-item { - padding-top: var(--bs-spacer); - padding-bottom: var(--bs-spacer); - background-color: inherit; - - &:focus-visible { - // since links fill the whole sidebar, we _inset_ the outline - outline-offset: -4px; - - // replace padding with margins so it doesn't extend over the :focus-visible outline - // → can't remove the padding altogether; the icon needs to fill out - // the collapsed width of the sidebar for the slide animation to look decent. - .shortcut-icon { - padding-left: 0; - padding-right: 0; - margin-left: var(--ds-icon-padding); - margin-right: var(--ds-icon-padding); - } - .logo-wrapper { - margin-right: var(--bs-navbar-padding-x) !important; - } - .navbar-brand { - padding-top: 0; - padding-bottom: 0; - margin-top: var(--bs-navbar-brand-padding-y); - margin-bottom: var(--bs-navbar-brand-padding-y); - } - } - } - - .shortcut-icon { - background-color: inherit; - padding-left: var(--ds-icon-padding); - padding-right: var(--ds-icon-padding); - z-index: var(--ds-icon-z-index); - align-self: baseline; - } - - .sidebar-collapsible { - padding-left: 0; - padding-right: var(--bs-spacer); - width: var(--ds-sidebar-items-width); - position: relative; - .toggle { - width: 100%; - } - - ul { - padding-top: var(--bs-spacer); - - li a { - padding-left: var(--bs-spacer); - } - } - } - - &.active > .sidebar-collapsible > .nav-link { - color: var(--bs-navbar-dark-active-color); - } - } - } + ::ng-deep { + + color: white; + + // Set here the style of the *-menu-item nested components + .ds-menu-item { + } + + a { + color: var(--ds-admin-sidebar-link-color); + text-decoration: none; + + &:hover, &:focus { + color: var(--ds-admin-sidebar-link-hover-color); } + } + } + div#sidebar-header-container { + background-color: var(--ds-admin-sidebar-header-bg); + .sidebar-fixed-element-wrapper { + background-color: var(--ds-admin-sidebar-header-bg); + } } + div#sidebar-collapse-toggle-container { + .sidebar-collapsible-element-inner-wrapper { + } + } + } } diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts index 9522be29ce3..058b81df2f6 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts @@ -143,7 +143,7 @@ describe('AdminSidebarComponent', () => { describe('when the collapse link is clicked', () => { beforeEach(() => { spyOn(menuService, 'toggleMenu'); - const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle > button')); + const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle-container > a')); sidebarToggler.triggerEventHandler('click', { preventDefault: () => {/**/ } diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index 26ded965d4d..741f9b7630d 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, Injector, OnInit } from '@angular/core'; +import { Component, HostListener, Injector, Input, OnInit } from '@angular/core'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { debounceTime, distinctUntilChanged, first, map, withLatestFrom } from 'rxjs/operators'; import { AuthService } from '../../core/auth/auth.service'; @@ -28,9 +28,14 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { menuID = MenuID.ADMIN; /** - * Observable that emits the width of the collapsible menu sections + * Observable that emits the width of the sidebar when expanded */ - sidebarWidth: Observable; + @Input() expandedSidebarWidth$: Observable; + + /** + * Observable that emits the width of the sidebar when collapsed + */ + @Input() collapsedSidebarWidth$: Observable; /** * Is true when the sidebar is open, is false when the sidebar is animating or closed @@ -44,6 +49,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { */ sidebarClosed = !this.sidebarOpen; // Closed in UI, animation finished + /** + * Is true when the sidebar is opening or closing + * @type {boolean} + */ + sidebarTransitioning = !this.sidebarOpen; // Animation in progress + /** * Emits true when either the menu OR the menu's preview is expanded, else emits false */ @@ -69,7 +80,6 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { */ ngOnInit(): void { super.ngOnInit(); - this.sidebarWidth = this.variableService.getVariable('--ds-sidebar-items-width'); this.authService.isAuthenticated() .subscribe((loggedIn: boolean) => { if (loggedIn) { @@ -134,6 +144,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { * @param event The animation event */ startSlide(event: any): void { + this.sidebarTransitioning = true; if (event.toState === 'expanded') { this.sidebarClosed = false; } else if (event.toState === 'collapsed') { @@ -146,6 +157,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { * @param event The animation event */ finishSlide(event: any): void { + this.sidebarTransitioning = false; if (event.fromState === 'expanded') { this.sidebarClosed = true; } else if (event.fromState === 'collapsed') { diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html index 1f4666bbd08..303b1400f40 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html @@ -1,37 +1,47 @@ - diff --git a/src/app/navbar/navbar.component.html b/src/app/navbar/navbar.component.html index 5e15315a9bf..bda98154813 100644 --- a/src/app/navbar/navbar.component.html +++ b/src/app/navbar/navbar.component.html @@ -9,12 +9,12 @@ - +
diff --git a/src/themes/dspace/app/navbar/navbar.component.html b/src/themes/dspace/app/navbar/navbar.component.html index ac4424c6380..518d3bbe302 100644 --- a/src/themes/dspace/app/navbar/navbar.component.html +++ b/src/themes/dspace/app/navbar/navbar.component.html @@ -1,9 +1,9 @@ - + From bff93a08471240e107704cd7c525ac747b0c93b6 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 26 Feb 2024 18:23:51 +0100 Subject: [PATCH 13/17] [DURACOM-195] fix issue with logo on small screens --- src/themes/dspace/app/header/header.component.html | 4 ++-- src/themes/dspace/app/header/header.component.scss | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/themes/dspace/app/header/header.component.html b/src/themes/dspace/app/header/header.component.html index 1ea91bc4609..0a0e0552535 100644 --- a/src/themes/dspace/app/header/header.component.html +++ b/src/themes/dspace/app/header/header.component.html @@ -1,5 +1,5 @@
-
+
-
+
diff --git a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html index 637b483db09..c20ad463d19 100644 --- a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html +++ b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html @@ -1,6 +1,6 @@
diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.html b/src/app/admin/admin-sidebar/admin-sidebar.component.html index 0179b274047..fcb33eb1755 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.html +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.html @@ -28,13 +28,13 @@

{{ 'menu.header.admin' | translate }}

diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html index cb8b87cfa23..636938b7b6e 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html @@ -1,4 +1,4 @@ -
  • -
  • + diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.scss b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.scss index 5e76813abb4..845ba734df6 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.scss +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.scss @@ -1,5 +1,5 @@ :host { - ul.sidebar-sub-level-item-list { + .sidebar-sub-level-item-list { list-style: none; margin: 0; padding: 0; From fdbe7a6005e59783075395aa3ba26c2b56f7afe5 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 26 Feb 2024 20:41:44 +0100 Subject: [PATCH 16/17] [DURACOM-195] fix --- src/themes/dspace/app/header/header.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/dspace/app/header/header.component.html b/src/themes/dspace/app/header/header.component.html index 0a0e0552535..02226ac0a1c 100644 --- a/src/themes/dspace/app/header/header.component.html +++ b/src/themes/dspace/app/header/header.component.html @@ -5,7 +5,7 @@ [attr.role]="(isMobile$ | async) ? 'navigation' : 'presentation'" [attr.aria-label]="(isMobile$ | async) ? ('nav.main.description' | translate) : null" class="h-100 flex-fill d-flex flex-row flex-nowrap justify-content-start align-items-center gapx-3"> - +