From 53ec6cf3275072f2313b4eb1c81a7df7ef45fa2e Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Mon, 15 Jun 2026 17:36:18 +0200 Subject: [PATCH 1/3] fix: replace header buttons with app-pill-button for improved UI consistency --- .../pill-button/pill-button.component.html | 38 ++++++++ .../pill-button/pill-button.component.scss | 87 +++++++++++++++++++ .../pill-button/pill-button.component.ts | 21 +++++ .../app/pages/header/header.component.html | 22 ++--- .../app/pages/header/header.component.scss | 40 +-------- .../app/pages/header/header.component.spec.ts | 8 +- .../src/app/pages/header/header.component.ts | 3 +- .../release-catalogus.component.html | 14 +-- .../release-catalogus.component.scss | 20 +---- .../release-catalogus.component.ts | 29 ++----- .../release-graph.component.html | 28 +++--- .../release-graph.component.scss | 74 +++------------- .../release-graph/release-graph.component.ts | 4 +- 13 files changed, 199 insertions(+), 189 deletions(-) create mode 100644 src/main/frontend/src/app/components/pill-button/pill-button.component.html create mode 100644 src/main/frontend/src/app/components/pill-button/pill-button.component.scss create mode 100644 src/main/frontend/src/app/components/pill-button/pill-button.component.ts diff --git a/src/main/frontend/src/app/components/pill-button/pill-button.component.html b/src/main/frontend/src/app/components/pill-button/pill-button.component.html new file mode 100644 index 00000000..2809bfd6 --- /dev/null +++ b/src/main/frontend/src/app/components/pill-button/pill-button.component.html @@ -0,0 +1,38 @@ + diff --git a/src/main/frontend/src/app/components/pill-button/pill-button.component.scss b/src/main/frontend/src/app/components/pill-button/pill-button.component.scss new file mode 100644 index 00000000..1a7e395a --- /dev/null +++ b/src/main/frontend/src/app/components/pill-button/pill-button.component.scss @@ -0,0 +1,87 @@ +:host { + display: inline-flex; + align-items: center; +} + +.control-pill { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + height: 36px; + border-radius: 18px; + background-color: #ffffff; + border: 1px solid #dee2e6; + cursor: pointer; + transition: all 0.2s ease-in-out; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); + padding: 6px 14px 6px 10px; + + .pill-icon { + width: 20px; + height: 20px; + fill: #6c757d; + transition: all 0.2s ease-in-out; + flex-shrink: 0; + } + + .spinner { + flex-shrink: 0; + animation: spin 1s linear infinite; + } + + .toggle-label { + font-size: 14px; + font-weight: 600; + color: #495057; + white-space: nowrap; + transition: color 0.2s ease-in-out; + } + + &:hover:not(:disabled) { + background-color: #f8f9fa; + border-color: #ced4da; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12); + } + + &:active:not(:disabled) { + transform: scale(0.98); + } + + &:disabled { + cursor: not-allowed; + opacity: 0.6; + } + + &.active { + background-color: #1e3a8a; + border-color: #1e3a8a; + box-shadow: 0 3px 6px rgba(30, 58, 138, 0.3); + + .pill-icon { + fill: #ffffff; + + path { + stroke: #ffffff; + } + } + + .toggle-label { + color: #ffffff; + } + + &:hover { + background-color: #1d3680; + border-color: #1d3680; + } + } +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/src/main/frontend/src/app/components/pill-button/pill-button.component.ts b/src/main/frontend/src/app/components/pill-button/pill-button.component.ts new file mode 100644 index 00000000..cd10947c --- /dev/null +++ b/src/main/frontend/src/app/components/pill-button/pill-button.component.ts @@ -0,0 +1,21 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +export type PillButtonIcon = 'moon' | 'help' | 'github'; + +@Component({ + selector: 'app-pill-button', + standalone: true, + imports: [], + templateUrl: './pill-button.component.html', + styleUrl: './pill-button.component.scss', +}) +export class PillButtonComponent { + @Input() icon: PillButtonIcon = 'help'; + @Input() label = ''; + @Input() active = false; + @Input() disabled = false; + @Input() loading = false; + @Input() tooltip = ''; + + @Output() clicked = new EventEmitter(); +} diff --git a/src/main/frontend/src/app/pages/header/header.component.html b/src/main/frontend/src/app/pages/header/header.component.html index dce17c52..46f3e44d 100644 --- a/src/main/frontend/src/app/pages/header/header.component.html +++ b/src/main/frontend/src/app/pages/header/header.component.html @@ -58,20 +58,14 @@

CVE Overview

} } @else { - + } diff --git a/src/main/frontend/src/app/pages/header/header.component.scss b/src/main/frontend/src/app/pages/header/header.component.scss index 8adf0801..20513cbb 100644 --- a/src/main/frontend/src/app/pages/header/header.component.scss +++ b/src/main/frontend/src/app/pages/header/header.component.scss @@ -76,35 +76,10 @@ margin-left: auto; display: flex; align-items: center; - padding-right: 2.5rem; + padding-right: 2rem; - .github-login-btn { - display: flex; - align-items: center; - justify-content: center; - background-color: white; - border: none; - border-radius: 50%; - cursor: pointer; - padding: 8px; - transition: background-color 0.2s; - - svg { - display: block; - } - - &:hover:not(:disabled) { - background-color: #f6f8fa; - } - - &:disabled { - cursor: not-allowed; - opacity: 0.6; - } - - .spinner { - animation: spin 1s linear infinite; - } + .github-pill-button { + margin-top: 0.25rem; } .user-profile { @@ -284,12 +259,3 @@ } } } - -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/main/frontend/src/app/pages/header/header.component.spec.ts b/src/main/frontend/src/app/pages/header/header.component.spec.ts index 06ac6568..5f967c75 100644 --- a/src/main/frontend/src/app/pages/header/header.component.spec.ts +++ b/src/main/frontend/src/app/pages/header/header.component.spec.ts @@ -120,7 +120,7 @@ describe('HeaderComponent', () => { }); it('should display login button', () => { - const loginButton = fixture.debugElement.query(By.css('.github-login-btn')); + const loginButton = fixture.debugElement.query(By.css('app-pill-button')); expect(loginButton).toBeTruthy(); }); @@ -133,7 +133,7 @@ describe('HeaderComponent', () => { it('should call onLoginWithGitHub when login button is clicked', () => { spyOn(component, 'onLoginWithGitHub'); - const loginButton = fixture.debugElement.query(By.css('.github-login-btn')); + const loginButton = fixture.debugElement.query(By.css('app-pill-button button')); loginButton.nativeElement.click(); expect(component.onLoginWithGitHub).toHaveBeenCalledWith(); @@ -142,7 +142,7 @@ describe('HeaderComponent', () => { it('should disable login button when loading', () => { mockAuthService.isLoading.set(true); fixture.detectChanges(); - const loginButton = fixture.debugElement.query(By.css('.github-login-btn')); + const loginButton = fixture.debugElement.query(By.css('app-pill-button button')); expect(loginButton.nativeElement.disabled).toBe(true); }); @@ -163,7 +163,7 @@ describe('HeaderComponent', () => { }); it('should not display login button', () => { - const loginButton = fixture.debugElement.query(By.css('.github-login-btn')); + const loginButton = fixture.debugElement.query(By.css('app-pill-button')); expect(loginButton).toBeFalsy(); }); diff --git a/src/main/frontend/src/app/pages/header/header.component.ts b/src/main/frontend/src/app/pages/header/header.component.ts index 852821b6..6d9f5899 100644 --- a/src/main/frontend/src/app/pages/header/header.component.ts +++ b/src/main/frontend/src/app/pages/header/header.component.ts @@ -4,11 +4,12 @@ import { NgOptimizedImage } from '@angular/common'; import { AuthService } from '../../services/auth.service'; import { LocationService } from '../../services/location.service'; import { GraphStateService } from '../../services/graph-state.service'; +import { PillButtonComponent } from '../../components/pill-button/pill-button.component'; @Component({ selector: 'app-header', standalone: true, - imports: [NgOptimizedImage, RouterLink, RouterLinkActive], + imports: [NgOptimizedImage, RouterLink, RouterLinkActive, PillButtonComponent], templateUrl: './header.component.html', styleUrl: './header.component.scss', }) diff --git a/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.html b/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.html index ef44c23d..e378bdf2 100644 --- a/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.html +++ b/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.html @@ -1,16 +1,4 @@ - + @if (modalOpen) { diff --git a/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.scss b/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.scss index dd8c512b..574bd5d5 100644 --- a/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.scss +++ b/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.scss @@ -1,20 +1,6 @@ -.release-btn { - background: none; - border: none; - padding: 0; - margin: 0; - box-shadow: none; - cursor: pointer; - position: fixed; - z-index: 25; - - @media (min-width: 999px) { - top: 1.5rem; - } - - @media (max-width: 992px) { - bottom: 1.25rem; - } +:host { + display: inline-flex; + align-items: center; } .release-modal-content { diff --git a/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.ts b/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.ts index 36e59a81..55736c52 100644 --- a/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.ts +++ b/src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.ts @@ -1,53 +1,34 @@ -import { Component, inject, Input, OnDestroy, OnInit } from '@angular/core'; -import { AsyncPipe, DatePipe, LowerCasePipe, NgStyle } from '@angular/common'; +import { Component, inject, Input, OnInit } from '@angular/core'; +import { AsyncPipe, DatePipe, LowerCasePipe } from '@angular/common'; import { ModalComponent } from '../../../components/modal/modal.component'; -import { AuthService } from '../../../services/auth.service'; import { BuildInfo, VersionService } from '../../../services/version.service'; import { Observable } from 'rxjs'; import { GestureComponent } from '../../../components/gesture/gesture.component'; +import { PillButtonComponent } from '../../../components/pill-button/pill-button.component'; @Component({ selector: 'app-release-catalogus', standalone: true, templateUrl: './release-catalogus.component.html', styleUrl: './release-catalogus.component.scss', - imports: [NgStyle, ModalComponent, AsyncPipe, DatePipe, LowerCasePipe, GestureComponent], + imports: [ModalComponent, AsyncPipe, DatePipe, LowerCasePipe, GestureComponent, PillButtonComponent], }) -export class ReleaseCatalogusComponent implements OnInit, OnDestroy { +export class ReleaseCatalogusComponent implements OnInit { private static readonly SESSION_KEY = 'releaseCatalogusShown'; @Input() showExtendedSupport = false; public modalOpen = false; - public isSmallScreen = false; public buildInfo$: Observable = inject(VersionService).getBuildInformation(); - protected authService: AuthService = inject(AuthService); - - private mediaQueryList: MediaQueryList | null = null; - private mediaListener: (() => void) | null = null; - toggleModal(): void { this.modalOpen = !this.modalOpen; } ngOnInit(): void { - this.mediaQueryList = globalThis.matchMedia('(max-width: 992px)'); - this.isSmallScreen = this.mediaQueryList?.matches ?? false; - this.mediaListener = (): void => { - this.isSmallScreen = this.mediaQueryList?.matches ?? false; - }; - this.mediaQueryList?.addEventListener('change', this.mediaListener); - if (!sessionStorage.getItem(ReleaseCatalogusComponent.SESSION_KEY)) { this.modalOpen = true; sessionStorage.setItem(ReleaseCatalogusComponent.SESSION_KEY, 'true'); } } - - ngOnDestroy(): void { - if (this.mediaQueryList && this.mediaListener) { - this.mediaQueryList.removeEventListener('change', this.mediaListener); - } - } } diff --git a/src/main/frontend/src/app/pages/release-graph/release-graph.component.html b/src/main/frontend/src/app/pages/release-graph/release-graph.component.html index 20b6fa84..663ad167 100644 --- a/src/main/frontend/src/app/pages/release-graph/release-graph.component.html +++ b/src/main/frontend/src/app/pages/release-graph/release-graph.component.html @@ -107,20 +107,18 @@ - +
+ + + +
@for (stickyLabel of stickyBranchLabels; track stickyLabel.label) { @@ -133,8 +131,6 @@ }
- - @if (dataForSkipModal) { Date: Mon, 15 Jun 2026 17:57:01 +0200 Subject: [PATCH 2/3] fix: adjust padding and positioning in header and release graph components for improved layout --- src/main/frontend/src/app/pages/header/header.component.scss | 5 +---- .../src/app/pages/release-graph/release-graph.component.scss | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/frontend/src/app/pages/header/header.component.scss b/src/main/frontend/src/app/pages/header/header.component.scss index 20513cbb..03203844 100644 --- a/src/main/frontend/src/app/pages/header/header.component.scss +++ b/src/main/frontend/src/app/pages/header/header.component.scss @@ -77,10 +77,7 @@ display: flex; align-items: center; padding-right: 2rem; - - .github-pill-button { - margin-top: 0.25rem; - } + padding-top: 0.25rem; .user-profile { position: relative; diff --git a/src/main/frontend/src/app/pages/release-graph/release-graph.component.scss b/src/main/frontend/src/app/pages/release-graph/release-graph.component.scss index 38b04434..9bda1a17 100644 --- a/src/main/frontend/src/app/pages/release-graph/release-graph.component.scss +++ b/src/main/frontend/src/app/pages/release-graph/release-graph.component.scss @@ -40,7 +40,7 @@ gap: 0.75rem; &.with-profile { - right: 12.5rem; + right: 12rem; } @media (max-width: 991px) { From e94839bc68b4d0b8a9a7a347b43eb65d7f9511fb Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Mon, 15 Jun 2026 18:04:34 +0200 Subject: [PATCH 3/3] fix: refactor pill-button component to use CSS masks for icons and improve loading state handling --- .../components/pill-button/icons/github.svg | 3 ++ .../app/components/pill-button/icons/help.svg | 3 ++ .../app/components/pill-button/icons/moon.svg | 3 ++ .../components/pill-button/icons/spinner.svg | 4 ++ .../pill-button/pill-button.component.html | 29 +------------- .../pill-button/pill-button.component.scss | 40 ++++++++++++++----- 6 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 src/main/frontend/src/app/components/pill-button/icons/github.svg create mode 100644 src/main/frontend/src/app/components/pill-button/icons/help.svg create mode 100644 src/main/frontend/src/app/components/pill-button/icons/moon.svg create mode 100644 src/main/frontend/src/app/components/pill-button/icons/spinner.svg diff --git a/src/main/frontend/src/app/components/pill-button/icons/github.svg b/src/main/frontend/src/app/components/pill-button/icons/github.svg new file mode 100644 index 00000000..b6ca4c06 --- /dev/null +++ b/src/main/frontend/src/app/components/pill-button/icons/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/main/frontend/src/app/components/pill-button/icons/help.svg b/src/main/frontend/src/app/components/pill-button/icons/help.svg new file mode 100644 index 00000000..b28e6d6e --- /dev/null +++ b/src/main/frontend/src/app/components/pill-button/icons/help.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/main/frontend/src/app/components/pill-button/icons/moon.svg b/src/main/frontend/src/app/components/pill-button/icons/moon.svg new file mode 100644 index 00000000..e560d8c5 --- /dev/null +++ b/src/main/frontend/src/app/components/pill-button/icons/moon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/main/frontend/src/app/components/pill-button/icons/spinner.svg b/src/main/frontend/src/app/components/pill-button/icons/spinner.svg new file mode 100644 index 00000000..c8364053 --- /dev/null +++ b/src/main/frontend/src/app/components/pill-button/icons/spinner.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/frontend/src/app/components/pill-button/pill-button.component.html b/src/main/frontend/src/app/components/pill-button/pill-button.component.html index 2809bfd6..25f11cef 100644 --- a/src/main/frontend/src/app/components/pill-button/pill-button.component.html +++ b/src/main/frontend/src/app/components/pill-button/pill-button.component.html @@ -6,33 +6,6 @@ [attr.title]="tooltip || null" (click)="clicked.emit()" > - @if (loading) { - - } @else { - @switch (icon) { - @case ('moon') { - - } - @case ('help') { - - } - @case ('github') { - - } - } - } + {{ label }} diff --git a/src/main/frontend/src/app/components/pill-button/pill-button.component.scss b/src/main/frontend/src/app/components/pill-button/pill-button.component.scss index 1a7e395a..22dc4762 100644 --- a/src/main/frontend/src/app/components/pill-button/pill-button.component.scss +++ b/src/main/frontend/src/app/components/pill-button/pill-button.component.scss @@ -20,14 +20,36 @@ .pill-icon { width: 20px; height: 20px; - fill: #6c757d; - transition: all 0.2s ease-in-out; flex-shrink: 0; - } + background-color: #6c757d; + transition: background-color 0.2s ease-in-out; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-size: contain; + mask-size: contain; - .spinner { - flex-shrink: 0; - animation: spin 1s linear infinite; + &.pill-icon--moon { + -webkit-mask-image: url('./icons/moon.svg'); + mask-image: url('./icons/moon.svg'); + } + + &.pill-icon--help { + -webkit-mask-image: url('./icons/help.svg'); + mask-image: url('./icons/help.svg'); + } + + &.pill-icon--github { + -webkit-mask-image: url('./icons/github.svg'); + mask-image: url('./icons/github.svg'); + } + + &.pill-icon--spinner { + -webkit-mask-image: url('./icons/spinner.svg'); + mask-image: url('./icons/spinner.svg'); + animation: spin 1s linear infinite; + } } .toggle-label { @@ -59,11 +81,7 @@ box-shadow: 0 3px 6px rgba(30, 58, 138, 0.3); .pill-icon { - fill: #ffffff; - - path { - stroke: #ffffff; - } + background-color: #ffffff; } .toggle-label {