From fd2cf1b53755f1f1aaf88ae7980307ba8ca52089 Mon Sep 17 00:00:00 2001 From: Alex Piatakov Date: Tue, 16 Jun 2026 01:59:58 +0100 Subject: [PATCH 1/5] feat: redesign navigation menu and layouts Refresh the main navigation with a token-driven brand-tinted style and add a user-selectable vertical (sidebar) or horizontal (top bar) layout. - Drive the sidebar look from --sidebar-* tokens in variables.scss (light and dark); the active accent follows the brand colour. - Replace the dual icon system with PrimeIcons and add group section labels. - Auto-expand the group of the current route; show child items as a flyout when the rail is collapsed; move the collapse toggle into the header. - Add MenuLayoutService and a horizontal top bar built on PrimeNG menubar, with a layout switch on the profile page; offset is driven by --header-width. - Make fixed bottom action bars track the live rail width so they no longer overlap content when the menu is collapsed or horizontal. Signed-off-by: Alex Piatakov --- frontend/src/app/app.component.ts | 13 +- frontend/src/app/app.module.ts | 4 +- .../policies/policies.component.scss | 2 +- .../global-events-reader-block.component.scss | 2 +- .../global-events-writer-block.component.scss | 2 +- .../request-document-block.component.scss | 4 +- .../roles-block/roles-block.component.scss | 2 +- .../policy-viewer.component.scss | 2 +- .../progress-tracker.component.scss | 2 +- .../project-data-export.component.scss | 2 +- .../src/app/services/menu-layout.service.ts | 69 ++ .../settings-view.component.scss | 3 +- .../views/branding/branding.component.scss | 7 +- .../src/app/views/new-header/menu.model.ts | 28 +- .../new-header/new-header.component.html | 280 ++++---- .../new-header/new-header.component.scss | 635 +++++++++++------- .../views/new-header/new-header.component.ts | 116 +++- .../root-profile/root-profile.component.html | 15 + .../root-profile/root-profile.component.ts | 14 + .../app/views/schemas/schemas.component.scss | 2 +- .../token-config/token-config.component.scss | 2 +- frontend/src/variables.scss | 35 + 22 files changed, 817 insertions(+), 424 deletions(-) create mode 100644 frontend/src/app/services/menu-layout.service.ts diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 2017dd898a..dba60dda81 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -57,9 +57,20 @@ export class AppComponent implements OnInit { document.documentElement.style.setProperty('--vh', `${vh}px`); } - resizeMenu(type: 'COLLAPSE' | 'EXPAND' | 'NO_MARGIN') { + resizeMenu(type: 'COLLAPSE' | 'EXPAND' | 'NO_MARGIN' | 'HORIZONTAL') { const progressFooter = document.getElementById('block-progress-footer'); switch (type) { + case 'HORIZONTAL': { + // Rail width is zeroed by the `layout-horizontal` root class, so the page + // only needs its left gutter removed; the top offset comes from --header-height. + document.body.style.setProperty('--header-width', '0px'); + document.getElementById('main-content')!.style.left = '0'; + document.getElementById('main-content')!.removeAttribute('main-collapse-menu'); + if (progressFooter) { + progressFooter.style.paddingLeft = '48px'; + } + break; + } case 'COLLAPSE': { document.body.style.setProperty('--header-width', 'var(--header-width-collapse)'); document.getElementById('main-content')!.style.left = 'var(--header-width-collapse)'; diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 648edeb696..f9af9bf560 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -149,6 +149,7 @@ import { PolicyRepositoryService } from './services/policy-repository.service'; import { RelayerAccountsService } from './services/relayer-accounts.service'; import { RelayerAccountsComponent } from './views/relayer-accounts/relayer-accounts.component'; import { TreeTableModule } from 'primeng/treetable'; +import { MenubarModule } from 'primeng/menubar'; import { CredentialsPanelComponent } from './components/credentials/credentials-panel/credentials-panel.component'; const GuardianPreset = definePreset(Aura, { @@ -290,7 +291,8 @@ const GuardianPreset = definePreset(Aura, { CardModule, ToggleSwitchModule, AngularSvgIconModule.forRoot(), - TreeTableModule + TreeTableModule, + MenubarModule ], providers: [ WebSocketService, diff --git a/frontend/src/app/modules/policy-engine/policies/policies.component.scss b/frontend/src/app/modules/policy-engine/policies/policies.component.scss index 2d31db4b6b..1c6f9a23e4 100644 --- a/frontend/src/app/modules/policy-engine/policies/policies.component.scss +++ b/frontend/src/app/modules/policy-engine/policies/policies.component.scss @@ -1154,7 +1154,7 @@ .options-footer { padding: 12px 48px; - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); display: flex; justify-content: space-between; position: fixed; diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-reader-block/global-events-reader-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-reader-block/global-events-reader-block.component.scss index 1769f57f08..7e30b3c5ef 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-reader-block/global-events-reader-block.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-reader-block/global-events-reader-block.component.scss @@ -316,7 +316,7 @@ width: 100%; border-top: 1px solid var(--color-grey-3, #E1E7EF); background: var(--guardian-background, #FFF); - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); transition: padding-left 0.125s ease-in-out; z-index: 999; } diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-writer-block/global-events-writer-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-writer-block/global-events-writer-block.component.scss index 058b6f8ef3..d0530e9548 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-writer-block/global-events-writer-block.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-writer-block/global-events-writer-block.component.scss @@ -241,7 +241,7 @@ width: 100%; border-top: 1px solid var(--color-grey-3, #E1E7EF); background: var(--guardian-background, #FFF); - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); transition: padding-left 0.125s ease-in-out; z-index: 999; } diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss index 4a3f1259b0..98c1a4c47b 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss @@ -179,7 +179,7 @@ width: 100%; border-top: 1px solid var(--color-grey-3, #e1e7ef); background: var(--guardian-background, #fff); - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); transition: padding-left 0.125s ease-in-out; margin-top: 20px; @@ -548,7 +548,7 @@ width: 100%; border-top: 1px solid var(--color-grey-3, #e1e7ef); background: var(--guardian-background, #fff); - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); transition: padding-left 0.125s ease-in-out; margin-top: 20px; left: 50%; diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.scss index acf269bbda..073dd6f946 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.scss @@ -131,7 +131,7 @@ form { width: 100%; border-top: 1px solid var(--color-grey-3, #E1E7EF); background: var(--guardian-background, #FFF); - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); transition: padding-left 0.125s ease-in-out; z-index: 999; } diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss index 464e4b1632..db376de266 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss @@ -878,7 +878,7 @@ a.go-back-link svg { .progress-footer { padding: 12px 48px; - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); display: flex; justify-content: space-between; position: fixed; diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/progress-tracker/progress-tracker.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/progress-tracker/progress-tracker.component.scss index 1064e3f2ca..3277e3a46e 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/progress-tracker/progress-tracker.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/progress-tracker/progress-tracker.component.scss @@ -15,7 +15,7 @@ .progress-footer { padding: 12px 48px; - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); display: flex; justify-content: space-between; position: fixed; diff --git a/frontend/src/app/modules/policy-engine/project-data-export/project-data-export.component.scss b/frontend/src/app/modules/policy-engine/project-data-export/project-data-export.component.scss index 1ba74b3af8..e0a40aacfe 100644 --- a/frontend/src/app/modules/policy-engine/project-data-export/project-data-export.component.scss +++ b/frontend/src/app/modules/policy-engine/project-data-export/project-data-export.component.scss @@ -462,7 +462,7 @@ a { .progress-footer { padding: 12px 48px; - padding-left: calc(var(--header-width-expand) + 48px); + padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px); display: flex; justify-content: space-between; position: fixed; diff --git a/frontend/src/app/services/menu-layout.service.ts b/frontend/src/app/services/menu-layout.service.ts new file mode 100644 index 0000000000..f8bfd30bfa --- /dev/null +++ b/frontend/src/app/services/menu-layout.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; + +export type MenuLayout = 'vertical' | 'horizontal'; + +export interface MenuLayoutOption { + label: string; + value: MenuLayout; + icon: string; +} + +const MENU_LAYOUT_STORAGE_KEY = 'MAIN_HEADER_LAYOUT'; +const HORIZONTAL_CLASS = 'layout-horizontal'; + +@Injectable({ + providedIn: 'root' +}) +export class MenuLayoutService { + public readonly layouts: MenuLayoutOption[] = [ + { label: 'Vertical', value: 'vertical', icon: 'pi-bars' }, + { label: 'Horizontal', value: 'horizontal', icon: 'pi-window-maximize' } + ]; + + private readonly layout$ = new BehaviorSubject(this.readStoredLayout()); + + constructor() { + this.applyLayoutClass(this.layout$.value); + } + + public get layout(): MenuLayout { + return this.layout$.value; + } + + public get changes(): Observable { + return this.layout$.asObservable(); + } + + public setLayout(layout: MenuLayout): void { + const resolved = this.findLayout(layout).value; + try { + localStorage.setItem(MENU_LAYOUT_STORAGE_KEY, resolved); + } catch (error) { + console.error(error); + } + this.applyLayoutClass(resolved); + this.layout$.next(resolved); + } + + public toggle(): void { + this.setLayout(this.layout$.value === 'vertical' ? 'horizontal' : 'vertical'); + } + + private readStoredLayout(): MenuLayout { + try { + return this.findLayout(localStorage.getItem(MENU_LAYOUT_STORAGE_KEY)).value; + } catch (error) { + console.error(error); + return this.layouts[0].value; + } + } + + private applyLayoutClass(layout: MenuLayout): void { + document.documentElement.classList.toggle(HORIZONTAL_CLASS, layout === 'horizontal'); + } + + private findLayout(layout: string | null): MenuLayoutOption { + return this.layouts.find((item) => item.value === layout) || this.layouts[0]; + } +} diff --git a/frontend/src/app/views/admin/settings-view/settings-view.component.scss b/frontend/src/app/views/admin/settings-view/settings-view.component.scss index d44d529d0d..7e855cbac8 100644 --- a/frontend/src/app/views/admin/settings-view/settings-view.component.scss +++ b/frontend/src/app/views/admin/settings-view/settings-view.component.scss @@ -42,13 +42,14 @@ .actions-container { position: fixed; - left: 270px; + left: var(--header-width, var(--header-width-expand)); right: 0; bottom: 0; height: 64px; display: flex; align-items: center; justify-content: flex-end; + transition: left 0.125s ease-in-out; background-color: var(--color-grey-white); border-top: 1px solid var(--color-grey-3, #E1E7EF); padding: 0 48px; diff --git a/frontend/src/app/views/branding/branding.component.scss b/frontend/src/app/views/branding/branding.component.scss index 5892a6fea4..d5647ca068 100644 --- a/frontend/src/app/views/branding/branding.component.scss +++ b/frontend/src/app/views/branding/branding.component.scss @@ -1,3 +1,7 @@ +.guardian-page { + padding-bottom: 88px; +} + .not-exist { position: absolute; left: 48px; @@ -55,13 +59,14 @@ .actions-container { position: fixed; - left: 270px; + left: var(--header-width, var(--header-width-expand)); right: 0; bottom: 0; height: 64px; display: flex; align-items: center; justify-content: space-between; + transition: left 0.125s ease-in-out; background-color: var(--color-grey-white); border-top: 1px solid var(--color-grey-3, #E1E7EF); padding: 0 48px; diff --git a/frontend/src/app/views/new-header/menu.model.ts b/frontend/src/app/views/new-header/menu.model.ts index 556602aa15..dd180a770a 100644 --- a/frontend/src/app/views/new-header/menu.model.ts +++ b/frontend/src/app/views/new-header/menu.model.ts @@ -3,8 +3,10 @@ import { UserPermissions, UserRole } from '@guardian/interfaces'; export interface NavbarMenuItem { title: string; childItems?: NavbarMenuItem[]; - iconUrl?: string; - svgIcon?: string; + /** PrimeIcons class for the item, e.g. 'pi-wallet'. Inherits currentColor. */ + icon?: string; + /** Optional uppercase group label rendered above this item (vertical layout). */ + section?: string; routerLink?: string; active?: boolean; allowedUserRoles?: UserRole[]; @@ -14,7 +16,8 @@ const NAVBAR_MENU_STANDARD_REGISTRY: NavbarMenuItem[] = [ { title: 'Policies', allowedUserRoles: [UserRole.STANDARD_REGISTRY], - iconUrl: 'table', + icon: 'pi-objects-column', + section: 'Workspace', active: false, childItems: [ { @@ -53,7 +56,7 @@ const NAVBAR_MENU_STANDARD_REGISTRY: NavbarMenuItem[] = [ }, { title: 'Tokens', - iconUrl: 'twoRings', + icon: 'pi-bitcoin', allowedUserRoles: [UserRole.STANDARD_REGISTRY], active: false, childItems: [ @@ -69,7 +72,7 @@ const NAVBAR_MENU_STANDARD_REGISTRY: NavbarMenuItem[] = [ }, { title: 'Relayer Accounts', - svgIcon: 'wallet', + icon: 'pi-wallet', allowedUserRoles: [UserRole.STANDARD_REGISTRY], active: false, routerLink: '/relayer-accounts' @@ -78,7 +81,8 @@ const NAVBAR_MENU_STANDARD_REGISTRY: NavbarMenuItem[] = [ title: 'Administration', allowedUserRoles: [UserRole.STANDARD_REGISTRY], active: false, - iconUrl: 'stars', + icon: 'pi-sliders-h', + section: 'Administration', childItems: [ { title: 'Manage Roles', @@ -117,12 +121,12 @@ const NAVBAR_MENU_AUDITOR: NavbarMenuItem[] = [ title: 'Audit', allowedUserRoles: [UserRole.AUDITOR], active: false, - iconUrl: 'guard', + icon: 'pi-shield', routerLink: '/audit' }, { title: 'Trust Chain', - iconUrl: 'twoRings', + icon: 'pi-sitemap', allowedUserRoles: [UserRole.AUDITOR], active: false, routerLink: '/trust-chain' @@ -228,7 +232,8 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] { menu.push({ title: 'Policies', allowedUserRoles: [UserRole.STANDARD_REGISTRY], - iconUrl: 'table', + icon: 'pi-objects-column', + section: 'Workspace', active: false, childItems }); @@ -280,7 +285,7 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] { } menu.push({ title: 'Tokens', - iconUrl: 'twoRings', + icon: 'pi-bitcoin', allowedUserRoles: [UserRole.STANDARD_REGISTRY], active: false, childItems @@ -346,7 +351,8 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] { title: 'Administration', allowedUserRoles: [UserRole.STANDARD_REGISTRY], active: false, - iconUrl: 'stars', + icon: 'pi-sliders-h', + section: 'Administration', childItems }); } diff --git a/frontend/src/app/views/new-header/new-header.component.html b/frontend/src/app/views/new-header/new-header.component.html index f986c2d080..f359ca7571 100644 --- a/frontend/src/app/views/new-header/new-header.component.html +++ b/frontend/src/app/views/new-header/new-header.component.html @@ -1,196 +1,190 @@ @if (isLogin) { -