diff --git a/api-goldens/element-ng/navbar-vertical-next/index.api.md b/api-goldens/element-ng/navbar-vertical-next/index.api.md index a4bf6a810c..4e6c07a20c 100644 --- a/api-goldens/element-ng/navbar-vertical-next/index.api.md +++ b/api-goldens/element-ng/navbar-vertical-next/index.api.md @@ -19,6 +19,7 @@ export class SiNavbarVerticalNextComponent implements OnChanges, OnInit { collapse(): void; readonly collapsed: _angular_core.ModelSignal; expand(): void; + readonly inlineCollapse: _angular_core.InputSignalWithTransform; readonly skipLinkMainContentLabel: _angular_core.InputSignal<_siemens_element_translate_ng_translate.TranslatableString>; readonly skipLinkNavigationLabel: _angular_core.InputSignal<_siemens_element_translate_ng_translate.TranslatableString>; readonly stateId: _angular_core.InputSignal; diff --git a/playwright/e2e/element-examples/navbar-vertical-next.spec.ts b/playwright/e2e/element-examples/navbar-vertical-next.spec.ts index c4864b9c44..3727ad8bea 100644 --- a/playwright/e2e/element-examples/navbar-vertical-next.spec.ts +++ b/playwright/e2e/element-examples/navbar-vertical-next.spec.ts @@ -54,6 +54,22 @@ test.describe('navbar vertical next', () => { await si.runVisualAndA11yTests('always-flyout'); }); + test(example + ' inline collapse toggle', async ({ page, si }) => { + await si.visitExample(example); + + await page.getByRole('checkbox', { name: 'Inline collapse' }).check(); + + await page.locator('si-navbar-vertical-next nav .collapse-toggle button').click(); + await expect( + page.locator('si-navbar-vertical-next.nav-inline-collapse.nav-collapsed') + ).toBeVisible(); + await expect(page.locator('si-navbar-vertical-next .nav-content[inert]')).toHaveCount(1); + await expect(page.locator('si-navbar-vertical-next .inline-collapse-toggle')).toBeVisible(); + + await si.waitForAllAnimationsToComplete(); + await si.runVisualAndA11yTests('inline-collapse'); + }); + test.skip('it should show tooltip only on keyboard interaction', async ({ page, si }) => { await si.visitExample(example); await page.getByLabel('Toggle', { exact: true }).click(); diff --git a/playwright/e2e/element-examples/static.spec.ts b/playwright/e2e/element-examples/static.spec.ts index 621ee716aa..a29f2dd21a 100644 --- a/playwright/e2e/element-examples/static.spec.ts +++ b/playwright/e2e/element-examples/static.spec.ts @@ -80,6 +80,7 @@ test('si-layouts/content-full-layout-fixed-height', ({ si }) => si.static()); test('si-layouts/content-tile-layout-full-scroll', ({ si }) => si.static()); test('si-loading-spinner/si-loading-spinner', ({ si }) => si.static({ maxDiffPixels: 31 })); test('si-navbar-vertical/si-navbar-vertical-text', ({ si }) => si.static()); +test('si-navbar-vertical-next/si-navbar-vertical-next-text', ({ si }) => si.static()); test('si-ncharts/si-micro-charts', ({ si }) => si.static()); test('si-pagination/si-pagination', ({ si }) => si.static()); test('si-phone-number-input/si-phone-number-input', ({ si }) => si.static()); diff --git a/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse-element-examples-chromium-dark-linux.png b/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse-element-examples-chromium-dark-linux.png new file mode 100644 index 0000000000..36caf5ff21 --- /dev/null +++ b/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse-element-examples-chromium-dark-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01171e71cdd0a107d2556eb73053cfdfe5a9c75d1e2b9baf4d942bf24fe4d28c +size 11929 diff --git a/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse-element-examples-chromium-light-linux.png b/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse-element-examples-chromium-light-linux.png new file mode 100644 index 0000000000..bfb7cf100f --- /dev/null +++ b/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse-element-examples-chromium-light-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b72d5b08d32ec75040808cc3252646ccf3f0cb116e83ca5d62e5ae2f7c019fdd +size 11684 diff --git a/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse.yaml b/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse.yaml new file mode 100644 index 0000000000..4efb8b358a --- /dev/null +++ b/playwright/snapshots/navbar-vertical-next.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next--inline-collapse.yaml @@ -0,0 +1,30 @@ +- link "Jump to Main content" +- link "Jump to Navigation" +- banner: + - link "Siemens logo": + - /url: "#/" + - heading "Navbar Vertical Next Example" [level=1] +- navigation: + - button "Toggle" + - button "Search..." + - link "Home": + - /url: "#/viewer/viewer/home" + - link "Energy & sustainability": + - /url: "#/viewer/viewer/energy" + - button "User management" + - group "User management" + - link "Test coverage": + - /url: "#/viewer/viewer/coverage" + - button "Documentation" + - group "Documentation" + - button "Action" + - link "Configuration": + - /url: "#/viewer/viewer/configuration" +- button "Toggle" +- main: + - heading "Here is a title" [level=2] + - text: Content with path 'home' Control panel + - checkbox "Always flyout" + - text: Always flyout + - checkbox "Inline collapse" [checked] + - text: Inline collapse \ No newline at end of file diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text-element-examples-chromium-dark-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text-element-examples-chromium-dark-linux.png new file mode 100644 index 0000000000..975dfb1f61 --- /dev/null +++ b/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text-element-examples-chromium-dark-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa850a90e3de0a77f0229170b712d1b7b3a70f22cf1179c978df7e7a5baf2e43 +size 22184 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text-element-examples-chromium-light-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text-element-examples-chromium-light-linux.png new file mode 100644 index 0000000000..476fa7b3bf --- /dev/null +++ b/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text-element-examples-chromium-light-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dbe04b1ccb1a1746c6a22b1c944b5e2feafcfcdba41a949e4c247c0ac080f53 +size 21250 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text.yaml b/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text.yaml new file mode 100644 index 0000000000..290f3ae010 --- /dev/null +++ b/playwright/snapshots/static.spec.ts-snapshots/si-navbar-vertical-next--si-navbar-vertical-next-text.yaml @@ -0,0 +1,27 @@ +- link "Jump to Main content" +- link "Jump to Navigation" +- banner: + - link "Siemens logo": + - /url: "#/" + - heading "Navbar vertical next text only" [level=1] + - button "Jane Smith" +- navigation: + - button "Toggle" [expanded] + - button "Home" + - group "Home" + - button "Documentation" + - group "Documentation" + - text: All the rest + - link "Energy & Operations": + - /url: "#/viewer/viewer/energy" + - link "Test Coverage": + - /url: "#/viewer/viewer/coverage" + - link "Configuration": + - /url: "#/viewer/viewer/configuration" +- main: + - heading "Here is a title" [level=2] + - text: Here goes the content Control panel + - checkbox "Always flyout" + - text: Always flyout + - checkbox "Inline collapse" + - text: Inline collapse \ No newline at end of file diff --git a/projects/element-ng/navbar-vertical-next/si-navbar-vertical-next.component.html b/projects/element-ng/navbar-vertical-next/si-navbar-vertical-next.component.html index 322dd15cc7..be3cded52d 100644 --- a/projects/element-ng/navbar-vertical-next/si-navbar-vertical-next.component.html +++ b/projects/element-ng/navbar-vertical-next/si-navbar-vertical-next.component.html @@ -2,6 +2,7 @@ @if (smallScreen() && !collapsed()) { } + @let inlineCollapsed = inlineCollapse() && collapsed(); + @if (inlineCollapsed) { + + } }
>('navToggleButton'); + private readonly inlineToggleButton = + viewChild>('inlineToggleButton'); + /** * @defaultValue * ``` @@ -142,6 +169,13 @@ export class SiNavbarVerticalNextComponent implements OnChanges, OnInit { // Indicates if the user prefers a collapsed navbar. Relevant for resizing. private preferCollapse = false; + protected readonly toggleIcon = computed(() => { + if (this.inlineCollapse()) { + return this.collapsed() ? this.icons.elementLayoutPane2 : this.icons.elementLayoutPane2Right; + } + return this.collapsed() ? this.icons.elementDoubleRight : this.icons.elementDoubleLeft; + }); + constructor() { this.breakpointObserver .observe(`(max-width: ${BOOTSTRAP_BREAKPOINTS.lgMinimum}px)`) @@ -177,11 +211,27 @@ export class SiNavbarVerticalNextComponent implements OnChanges, OnInit { } protected toggleCollapse(): void { - if (this.collapsed()) { + const wasCollapsed = this.collapsed(); + if (wasCollapsed) { this.expand(); } else { this.collapse(); } + // In inline-collapse mode the visible toggle button swaps between the + // in-nav button (expanded) and the in-page button (collapsed). Move focus + // to the newly visible one after the view updates so keyboard users don't + // lose their place. + if (this.inlineCollapse()) { + afterNextRender( + () => { + const next = wasCollapsed + ? this.navToggleButton()?.nativeElement + : this.inlineToggleButton()?.nativeElement; + next?.focus(); + }, + { injector: this.injector } + ); + } } /** Expands the vertical navbar. */ diff --git a/projects/element-ng/navbar-vertical-next/si-navbar-vertical-next.spec.ts b/projects/element-ng/navbar-vertical-next/si-navbar-vertical-next.spec.ts index 83d7c1a4e2..b47fbaf15f 100644 --- a/projects/element-ng/navbar-vertical-next/si-navbar-vertical-next.spec.ts +++ b/projects/element-ng/navbar-vertical-next/si-navbar-vertical-next.spec.ts @@ -67,6 +67,7 @@ class EmptyComponent {} [textOnly]="textOnly()" [stateId]="stateId" [collapsed]="collapsed()" + [inlineCollapse]="inlineCollapse()" [alwaysFlyout]="alwaysFlyout()" > @@ -160,6 +161,7 @@ class TestHostComponent { readonly textOnly = signal(true); stateId?: string; readonly collapsed = signal(false); + readonly inlineCollapse = signal(false); readonly alwaysFlyout = signal(false); readonly showDeclarativeFlyoutGroup = signal(false); readonly showDeclarativeNavigationGroup = signal(false); @@ -401,4 +403,90 @@ describe('SiNavbarVerticalNext', () => { expect(await harness.isCollapsed()).toBe(true); }); }); + + describe('with inlineCollapse', () => { + beforeEach(() => { + component.inlineCollapse.set(true); + component.textOnly.set(false); + component.showDeclarativeNavigationGroup.set(true); + }); + + it('should add nav-inline-collapse host class', async () => { + await fixture.whenStable(); + const host = page.getByRole('navigation').element().closest('si-navbar-vertical-next')!; + expect(host).toHaveClass('nav-inline-collapse'); + }); + + it('should render an in-page toggle when collapsed and not when expanded', async () => { + component.collapsed.set(true); + await fixture.whenStable(); + + const host = fixture.nativeElement.querySelector('si-navbar-vertical-next') as HTMLElement; + expect(host.querySelector('.inline-collapse-toggle')).toBeInTheDocument(); + + component.collapsed.set(false); + await fixture.whenStable(); + expect(host.querySelector('.inline-collapse-toggle')).not.toBeInTheDocument(); + }); + + it('should drop inert when expanded', async () => { + component.collapsed.set(false); + await fixture.whenStable(); + + const host = fixture.nativeElement.querySelector('si-navbar-vertical-next') as HTMLElement; + expect(host).not.toHaveClass('nav-collapsed'); + + const content = host.querySelector('.nav-content') as HTMLElement; + expect(content).not.toHaveAttribute('inert'); + }); + + it('should move focus to the in-page toggle when collapsing', async () => { + component.collapsed.set(false); + await fixture.whenStable(); + + const host = fixture.nativeElement.querySelector('si-navbar-vertical-next') as HTMLElement; + const navToggle = host.querySelector('nav .collapse-toggle button') as HTMLButtonElement; + navToggle.focus(); + expect(document.activeElement).toBe(navToggle); + + await userEvent.click(navToggle); + await fixture.whenStable(); + + const inlineToggle = host.querySelector('.inline-collapse-toggle') as HTMLButtonElement; + expect(inlineToggle).toBeInTheDocument(); + expect(document.activeElement).toBe(inlineToggle); + }); + + it('should move focus to the in-nav toggle when expanding', async () => { + component.collapsed.set(true); + await fixture.whenStable(); + + const host = fixture.nativeElement.querySelector('si-navbar-vertical-next') as HTMLElement; + const inlineToggle = host.querySelector('.inline-collapse-toggle') as HTMLButtonElement; + inlineToggle.focus(); + expect(document.activeElement).toBe(inlineToggle); + + await userEvent.click(inlineToggle); + await fixture.whenStable(); + + const navToggle = host.querySelector('nav .collapse-toggle button') as HTMLButtonElement; + expect(document.activeElement).toBe(navToggle); + }); + + it('should expose correct aria-expanded on both toggles', async () => { + component.collapsed.set(true); + await fixture.whenStable(); + + const host = fixture.nativeElement.querySelector('si-navbar-vertical-next') as HTMLElement; + const navToggle = host.querySelector('nav .collapse-toggle button') as HTMLButtonElement; + const inlineToggle = host.querySelector('.inline-collapse-toggle') as HTMLButtonElement; + + expect(navToggle).toHaveAttribute('aria-expanded', 'false'); + expect(inlineToggle).toHaveAttribute('aria-expanded', 'false'); + + component.collapsed.set(false); + await fixture.whenStable(); + expect(navToggle).toHaveAttribute('aria-expanded', 'true'); + }); + }); }); diff --git a/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-badges.html b/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-badges.html index 2252e93ae1..b34e86bfb9 100644 --- a/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-badges.html +++ b/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-badges.html @@ -10,6 +10,7 @@

Navbar Vertical Next Badges Example

stateId="navbar-vertical-next-badges" toggleButtonText="Toggle" [alwaysFlyout]="alwaysFlyout" + [inlineCollapse]="inlineCollapse" > @@ -94,9 +95,18 @@

Badge Examples

Control panel
- - - +
+
+ + + +
+
+ + + +
+
diff --git a/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-badges.ts b/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-badges.ts index 29a336726a..80a0ced22a 100644 --- a/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-badges.ts +++ b/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-badges.ts @@ -60,6 +60,7 @@ export class SampleComponent implements OnInit { logEvent = inject(LOG_EVENT); alwaysFlyout = false; + inlineCollapse = false; ngOnInit(): void { this.router.navigate(['home'], { relativeTo: this.activeRoute }); diff --git a/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-text.html b/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-text.html index 804abcf2cd..7d1c83dfdf 100644 --- a/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-text.html +++ b/src/app/examples/si-navbar-vertical-next/si-navbar-vertical-next-text.html @@ -30,6 +30,7 @@

Navbar vertical next text only

toggleButtonText="Toggle" [textOnly]="true" [alwaysFlyout]="alwaysFlyout" + [inlineCollapse]="inlineCollapse" >