diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.scss b/src/app/admin/admin-sidebar/admin-sidebar.component.scss index a679155c323..3f7d3b66b67 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.scss +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.scss @@ -56,6 +56,20 @@ padding-bottom: var(--ds-admin-sidebar-item-padding); } + // Menu item layout to align the optional icon and text label + .ds-menu-item { + display: flex; + width: 100%; + align-items: center; + box-sizing: border-box; + gap: var(--ds-admin-sidebar-item-padding); + + span { + flex: 1; + min-width: 0; + } + } + // These classes handle the collapsing behavior .sidebar-section-wrapper { diff --git a/src/app/shared/menu/menu-item/external-link-menu-item.component.html b/src/app/shared/menu/menu-item/external-link-menu-item.component.html index c95c63fdf5a..a9b3c56ad35 100644 --- a/src/app/shared/menu/menu-item/external-link-menu-item.component.html +++ b/src/app/shared/menu/menu-item/external-link-menu-item.component.html @@ -5,4 +5,8 @@ [href]="href" [title]="item.text | translate" (click)="$event.stopPropagation()" ->{{item.text | translate}} +>@if (item.icon) { + + } + {{item.text | translate}} + diff --git a/src/app/shared/menu/menu-item/link-menu-item.component.html b/src/app/shared/menu/menu-item/link-menu-item.component.html index f96084e6e13..7f15fe7e527 100644 --- a/src/app/shared/menu/menu-item/link-menu-item.component.html +++ b/src/app/shared/menu/menu-item/link-menu-item.component.html @@ -9,4 +9,8 @@ (keydown.enter)="navigate($event)" href="javascript:void(0);" tabindex="0" ->{{item.text | translate}} +>@if (item.icon) { + + } + {{item.text | translate}} + diff --git a/src/app/shared/menu/menu-item/link-menu-item.component.spec.ts b/src/app/shared/menu/menu-item/link-menu-item.component.spec.ts index be98fc884e4..30ab6b68f0a 100644 --- a/src/app/shared/menu/menu-item/link-menu-item.component.spec.ts +++ b/src/app/shared/menu/menu-item/link-menu-item.component.spec.ts @@ -87,4 +87,29 @@ describe('LinkMenuItemComponent', () => { expect(routerParamsQuery.length).toBe(1); expect(routerParamsQuery[0].queryParams).toBe(queryParams); }); + + describe('icon rendering', () => { + beforeEach(() => { + component.item.icon = undefined; + fixture.detectChanges(); + }); + + it('should render the icon when item.icon is provided', () => { + component.item.icon = 'cog'; + fixture.detectChanges(); + + const icon = debugElement.query(By.css('i.fa-cog')); + + expect(icon).toBeTruthy(); + expect(icon.nativeElement.getAttribute('aria-hidden')).toBe('true'); + }); + + it('should not render the icon when item.icon is not provided', () => { + fixture.detectChanges(); + + const icon = debugElement.query(By.css('i.fas')); + + expect(icon).toBeFalsy(); + }); + }); }); diff --git a/src/app/shared/menu/menu-item/models/external-link.model.ts b/src/app/shared/menu/menu-item/models/external-link.model.ts index 6e9e1dae1b3..0b1e86fe664 100644 --- a/src/app/shared/menu/menu-item/models/external-link.model.ts +++ b/src/app/shared/menu/menu-item/models/external-link.model.ts @@ -9,4 +9,5 @@ export class ExternalLinkMenuItemModel implements MenuItemModel { disabled?: boolean; text: string; href: string; + icon?: string; } diff --git a/src/app/shared/menu/menu-item/models/link.model.ts b/src/app/shared/menu/menu-item/models/link.model.ts index ff98fb4c533..51ea4a0e721 100644 --- a/src/app/shared/menu/menu-item/models/link.model.ts +++ b/src/app/shared/menu/menu-item/models/link.model.ts @@ -12,4 +12,5 @@ export class LinkMenuItemModel implements MenuItemModel { text: string; link: string; queryParams?: Params | null; + icon?: string; } diff --git a/src/app/shared/menu/menu-item/models/menu-item.model.ts b/src/app/shared/menu/menu-item/models/menu-item.model.ts index 3bfc632df47..0851825b549 100644 --- a/src/app/shared/menu/menu-item/models/menu-item.model.ts +++ b/src/app/shared/menu/menu-item/models/menu-item.model.ts @@ -6,4 +6,5 @@ import { MenuItemType } from '../../menu-item-type.model'; export interface MenuItemModel { type: MenuItemType; disabled?: boolean; + icon?: string; } diff --git a/src/app/shared/menu/menu-item/models/onclick.model.ts b/src/app/shared/menu/menu-item/models/onclick.model.ts index 48fa80235b9..163d2abbebe 100644 --- a/src/app/shared/menu/menu-item/models/onclick.model.ts +++ b/src/app/shared/menu/menu-item/models/onclick.model.ts @@ -8,5 +8,6 @@ export class OnClickMenuItemModel implements MenuItemModel { type = MenuItemType.ONCLICK; disabled?: boolean; text: string; + icon?: string; function: () => void; } diff --git a/src/app/shared/menu/menu-item/models/text.model.ts b/src/app/shared/menu/menu-item/models/text.model.ts index b5da66c4e21..9dac7193f58 100644 --- a/src/app/shared/menu/menu-item/models/text.model.ts +++ b/src/app/shared/menu/menu-item/models/text.model.ts @@ -8,4 +8,5 @@ export class TextMenuItemModel implements MenuItemModel { type = MenuItemType.TEXT; disabled?: boolean; text: string; + icon?: string; } diff --git a/src/app/shared/menu/menu-item/onclick-menu-item.component.html b/src/app/shared/menu/menu-item/onclick-menu-item.component.html index cada06e1b37..9c464ecb8e2 100644 --- a/src/app/shared/menu/menu-item/onclick-menu-item.component.html +++ b/src/app/shared/menu/menu-item/onclick-menu-item.component.html @@ -8,8 +8,17 @@ (keyup.space)="activate($event)" (keyup.enter)="activate($event)" [attr.data-test]="item.text" - >{{item.text | translate}} + >@if (item.icon) { + + } + {{item.text | translate}} + } @if (item.disabled) { - {{item.text | translate}} + + @if (item.icon) { + + } + {{item.text | translate}} + } diff --git a/src/app/shared/menu/menu-item/onclick-menu-item.component.spec.ts b/src/app/shared/menu/menu-item/onclick-menu-item.component.spec.ts index a73a0cfff29..0ac94335cae 100644 --- a/src/app/shared/menu/menu-item/onclick-menu-item.component.spec.ts +++ b/src/app/shared/menu/menu-item/onclick-menu-item.component.spec.ts @@ -38,6 +38,7 @@ describe('OnClickMenuItemComponent', () => { spyOn(item, 'function'); fixture = TestBed.createComponent(OnClickMenuItemComponent); component = fixture.componentInstance; + component.item = item; debugElement = fixture.debugElement; fixture.detectChanges(); }); @@ -55,4 +56,41 @@ describe('OnClickMenuItemComponent', () => { debugElement.query(By.css('a.ds-menu-item')).triggerEventHandler('click', new Event(('click'))); expect(item.function).toHaveBeenCalled(); }); + + describe('icon rendering', () => { + beforeEach(() => { + item.icon = undefined; + item.disabled = false; + fixture.detectChanges(); + }); + + it('should render the icon when item.icon is provided and enabled', () => { + item.icon = 'users'; + fixture.detectChanges(); + + const icon = debugElement.query(By.css('a.ds-menu-item i.fa-users')); + + expect(icon).toBeTruthy(); + expect(icon.nativeElement.getAttribute('aria-hidden')).toBe('true'); + }); + + it('should render the icon when item.icon is provided and disabled', () => { + item.icon = 'users'; + item.disabled = true; + fixture.detectChanges(); + + const icon = debugElement.query(By.css('span.ds-menu-item i.fa-users')); + + expect(icon).toBeTruthy(); + expect(icon.nativeElement.getAttribute('aria-hidden')).toBe('true'); + }); + + it('should not render the icon when item.icon is not provided', () => { + fixture.detectChanges(); + + const icon = debugElement.query(By.css('i.fas')); + + expect(icon).toBeFalsy(); + }); + }); }); diff --git a/src/app/shared/menu/menu-item/text-menu-item.component.html b/src/app/shared/menu/menu-item/text-menu-item.component.html index e2dd334caf5..2c220607526 100644 --- a/src/app/shared/menu/menu-item/text-menu-item.component.html +++ b/src/app/shared/menu/menu-item/text-menu-item.component.html @@ -1 +1,9 @@ -{{item.text | translate}} +@if (item.icon) { + + } + {{item.text | translate}} +