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 @@
- @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;
};