From 303d4b72114771d902675348d07c43e68f9dac4d Mon Sep 17 00:00:00 2001 From: sagely1 <114952739+sagely1@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:16:06 -0700 Subject: [PATCH] feat(agora): migrate Header component and utils (AG-1549) (#2862) --- apps/agora/app/src/app/app.component.html | 2 +- apps/agora/app/src/app/app.component.ts | 4 +- libs/agora/ui/src/index.ts | 2 +- .../components/footer/footer.component.html | 2 +- .../footer/footer.component.spec.ts | 7 +- .../lib/components/footer/footer.component.ts | 12 +- .../components/header/header.component.html | 53 ++++ .../components/header/header.component.scss | 230 ++++++++++++++++++ .../header/header.component.spec.ts | 26 ++ .../lib/components/header/header.component.ts | 93 +++++++ .../ui/src/lib/models/navigation-link.ts | 3 + 11 files changed, 420 insertions(+), 14 deletions(-) create mode 100644 libs/agora/ui/src/lib/components/header/header.component.html create mode 100644 libs/agora/ui/src/lib/components/header/header.component.scss create mode 100644 libs/agora/ui/src/lib/components/header/header.component.spec.ts create mode 100644 libs/agora/ui/src/lib/components/header/header.component.ts diff --git a/apps/agora/app/src/app/app.component.html b/apps/agora/app/src/app/app.component.html index c4921b6488..d152ac90f4 100644 --- a/apps/agora/app/src/app/app.component.html +++ b/apps/agora/app/src/app/app.component.html @@ -1,3 +1,3 @@ - + diff --git a/apps/agora/app/src/app/app.component.ts b/apps/agora/app/src/app/app.component.ts index 9d4dec8e10..1ed2c549b7 100644 --- a/apps/agora/app/src/app/app.component.ts +++ b/apps/agora/app/src/app/app.component.ts @@ -1,12 +1,12 @@ import { Component, inject, OnInit } from '@angular/core'; import { Meta, Title } from '@angular/platform-browser'; import { ActivatedRoute, NavigationEnd, Router, RouterModule } from '@angular/router'; -import { FooterComponent } from '@sagebionetworks/agora/ui'; +import { FooterComponent, HeaderComponent } from '@sagebionetworks/agora/ui'; import { filter } from 'rxjs'; @Component({ standalone: true, - imports: [RouterModule, FooterComponent], + imports: [RouterModule, HeaderComponent, FooterComponent], selector: 'app-root', templateUrl: './app.component.html', styleUrl: './app.component.scss', diff --git a/libs/agora/ui/src/index.ts b/libs/agora/ui/src/index.ts index f5348da207..0734420938 100644 --- a/libs/agora/ui/src/index.ts +++ b/libs/agora/ui/src/index.ts @@ -1,4 +1,4 @@ -// export * from './lib/components/header/header.component'; +export * from './lib/components/header/header.component'; export * from './lib/components/footer/footer.component'; export * from './lib/components/loading-icon/loading-icon.component'; // export * from './lib/components/modal-link/modal-link.component'; diff --git a/libs/agora/ui/src/lib/components/footer/footer.component.html b/libs/agora/ui/src/lib/components/footer/footer.component.html index 95f8264b11..0ab9d7f75b 100644 --- a/libs/agora/ui/src/lib/components/footer/footer.component.html +++ b/libs/agora/ui/src/lib/components/footer/footer.component.html @@ -7,7 +7,7 @@ footer logo - @for (item of navItems; track item.url) { + @for (item of navItems; track item.label) {
  • @if (item.routerLink) { { let component: FooterComponent; @@ -9,7 +9,8 @@ describe('FooterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HttpClientModule, RouterTestingModule], + imports: [], + providers: [provideRouter([]), provideHttpClient()], }).compileComponents(); }); diff --git a/libs/agora/ui/src/lib/components/footer/footer.component.ts b/libs/agora/ui/src/lib/components/footer/footer.component.ts index 7bdaf8a3e5..9f1ad59149 100644 --- a/libs/agora/ui/src/lib/components/footer/footer.component.ts +++ b/libs/agora/ui/src/lib/components/footer/footer.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, inject, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { Dataversion, DataversionService } from '@sagebionetworks/agora/api-client-angular'; @@ -16,6 +16,10 @@ import { NavigationLink } from '../../models/navigation-link'; styleUrls: ['./footer.component.scss'], }) export class FooterComponent implements OnInit { + configService = inject(ConfigService); + dataVersionService = inject(DataversionService); + sanitizer = inject(PathSanitizer); + footerLogoPath!: SafeUrl; dataVersion$!: Observable; @@ -36,11 +40,7 @@ export class FooterComponent implements OnInit { }, ]; - constructor( - private readonly configService: ConfigService, - private dataVersionService: DataversionService, - private sanitizer: PathSanitizer, - ) { + constructor() { this.footerLogoPath = this.sanitizer.sanitize('/agora-assets/images/footer-logo.svg'); } diff --git a/libs/agora/ui/src/lib/components/header/header.component.html b/libs/agora/ui/src/lib/components/header/header.component.html new file mode 100644 index 0000000000..b5cd062f2c --- /dev/null +++ b/libs/agora/ui/src/lib/components/header/header.component.html @@ -0,0 +1,53 @@ +
    + +
    diff --git a/libs/agora/ui/src/lib/components/header/header.component.scss b/libs/agora/ui/src/lib/components/header/header.component.scss new file mode 100644 index 0000000000..46c1366899 --- /dev/null +++ b/libs/agora/ui/src/lib/components/header/header.component.scss @@ -0,0 +1,230 @@ +/* stylelint-disable plugin/no-unsupported-browser-features */ + +@import 'libs/agora/styles/src/lib/constants'; +@import 'libs/agora/styles/src/lib/mixins'; + +#header { + // Uncomment for sticky header + // position: fixed; + // top: 0; + // left: 0; + // right: 0; + // border-bottom: 1px solid var(--color-gray-200); + position: relative; + background-color: #fff; + z-index: 999; + + .header-inner { + @include container('lg'); + + padding: 0 var(--spacing-xl); + display: flex; + height: calc(var(--header-height) - 1px); + } +} + +.header-logo { + display: flex; + align-items: center; +} + +.header-nav { + display: flex; + flex-grow: 1; + justify-content: flex-end; + align-items: center; + + .header-nav-toggle { + @include reset-button; + + position: relative; + width: 20px; + height: 20px; + cursor: pointer; + + span { + display: block; + position: absolute; + left: 0; + right: 0; + background-color: var(--color-primary); + height: 4px; + transition: var(--transition-duration); + + &:nth-child(1) { + top: 0; + } + + &:nth-child(2) { + top: 50%; + margin-top: -2px; + } + + &:nth-child(3) { + bottom: 0; + } + } + + &:hover { + opacity: 0.8; + } + } + + ul { + list-style: none; + padding: 0; + margin: 0; + } + + a { + font-size: var(--font-size-lg); + color: var(--color-text); + text-decoration: none; + } + + .separator { + border-left: 2px solid var(--color-separator); + height: var(--spacing-lg); + box-sizing: border-box; + } + + .header-search { + height: fit-content; + } +} + +#header:not(.is-mobile) { + .header-nav-toggle { + display: none; + } + + .header-nav { + .header-nav-inner { + display: flex; + justify-content: flex-end; + align-items: center; + gap: var(--spacing-lg); + } + + ul { + display: flex; + height: 100%; + gap: var(--spacing-md); + } + + li { + padding: 0 20px; + } + + a { + position: relative; + display: flex; + font-weight: 700; + padding: 8px 0; + color: var(--color-text-secondary); + transition: var(--transition-duration); + align-items: center; + + &:hover { + color: var(--color-action-primary); + } + + &::after { + content: ' '; + position: absolute; + display: block; + bottom: 0; + left: 0; + right: 0; + height: 4px; + background-color: var(--color-action-primary); + border-radius: 2px; + opacity: 0; + visibility: hidden; + transition: var(--transition-duration); + } + + &.active { + color: var(--color-action-primary); + + &::after { + opacity: 1; + visibility: visible; + } + } + } + } +} + +#header.is-mobile { + .header-nav { + .header-nav-inner { + position: absolute; + top: 100%; + left: 0; + right: 0; + padding: 30px; + background-color: #fff; + border-top: 1px solid var(--color-gray-300); + border-bottom: 1px solid var(--color-gray-300); + opacity: 0; + visibility: hidden; + } + + ul { + li { + padding: 10px 0; + } + } + + a { + &.active { + position: relative; + color: var(--color-action-primary); + + &::after { + content: ' '; + position: absolute; + display: block; + bottom: -4px; + left: 0; + right: 0; + height: 4px; + background-color: var(--color-action-primary); + border-radius: 2px; + transition: var(--transition-duration); + } + } + } + + &.show { + .header-nav-toggle { + span { + &:nth-child(1) { + right: -5px; + transform: rotate(45deg) translate(5px, 6px); + } + + &:nth-child(2) { + opacity: 0; + } + + &:nth-child(3) { + right: -5px; + transform: rotate(-45deg) translate(5px, -6px); + } + } + } + + .header-nav-inner { + opacity: 1; + visibility: visible; + } + } + } + + .separator { + opacity: 0; + height: 15px; + } +} diff --git a/libs/agora/ui/src/lib/components/header/header.component.spec.ts b/libs/agora/ui/src/lib/components/header/header.component.spec.ts new file mode 100644 index 0000000000..182b1a5ab3 --- /dev/null +++ b/libs/agora/ui/src/lib/components/header/header.component.spec.ts @@ -0,0 +1,26 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HeaderComponent } from './header.component'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; + +describe('HeaderComponent', () => { + let component: HeaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [], + providers: [provideRouter([]), provideHttpClient()], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/agora/ui/src/lib/components/header/header.component.ts b/libs/agora/ui/src/lib/components/header/header.component.ts new file mode 100644 index 0000000000..7e415a1686 --- /dev/null +++ b/libs/agora/ui/src/lib/components/header/header.component.ts @@ -0,0 +1,93 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { Dataversion, DataversionService } from '@sagebionetworks/agora/api-client-angular'; +import { Observable } from 'rxjs'; +import { SafeUrl } from '@angular/platform-browser'; +import { PathSanitizer } from '@sagebionetworks/agora/util'; +import { ConfigService } from '@sagebionetworks/agora/config'; +import { NavigationLink } from '../../models/navigation-link'; +import { inject } from '@angular/core'; + +@Component({ + selector: 'agora-header', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'], +}) +export class HeaderComponent implements OnInit { + configService = inject(ConfigService); + dataVersionService = inject(DataversionService); + sanitizer = inject(PathSanitizer); + + headerLogoPath!: SafeUrl; + dataVersion$!: Observable; + + isMobile = false; + isShown = false; + + navItems: Array = []; + defaultNavItems: Array = [ + { + label: 'Home', + routerLink: [''], + activeOptions: { exact: true }, + }, + { + label: 'Gene Comparison', + routerLink: ['genes/comparison'], + }, + { + label: 'Nominated Targets', + routerLink: ['genes/nominated-targets'], + }, + { + label: 'Teams', + routerLink: ['teams'], + }, + { + label: 'News', + routerLink: ['news'], + }, + ]; + mobileNavItems: Array = [ + { + label: 'About', + routerLink: ['about'], + }, + { + label: 'Help', + url: 'https://help.adknowledgeportal.org/apd/Agora-Help.2663088129.html', + target: '_blank', + }, + { + label: 'Terms of Service', + url: 'https://s3.amazonaws.com/static.synapse.org/governance/SageBionetworksSynapseTermsandConditionsofUse.pdf?v=5', + target: '_blank', + }, + ]; + + constructor() { + this.headerLogoPath = this.sanitizer.sanitize('/agora-assets/images/header-logo.svg'); + } + + ngOnInit() { + this.onResize(); + } + + refreshNavItems() { + this.navItems = this.isMobile + ? [...this.defaultNavItems, ...this.mobileNavItems] + : [...this.defaultNavItems]; + } + + onResize() { + this.isMobile = window.innerWidth < 1320; + this.refreshNavItems(); + } + + toggleNav() { + this.isShown = !this.isShown; + } +} diff --git a/libs/agora/ui/src/lib/models/navigation-link.ts b/libs/agora/ui/src/lib/models/navigation-link.ts index 8379ec5be2..6096b49725 100644 --- a/libs/agora/ui/src/lib/models/navigation-link.ts +++ b/libs/agora/ui/src/lib/models/navigation-link.ts @@ -1,6 +1,9 @@ +import { IsActiveMatchOptions } from '@angular/router'; + export type NavigationLink = { label: string; url?: string; target?: '_blank' | '_self' | '_parent' | '_top'; routerLink?: string[]; + activeOptions?: { exact: boolean } | IsActiveMatchOptions; };