Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api-goldens/element-ng/side-panel/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,23 @@ export class SiSidePanelComponent implements OnInit, OnDestroy, OnChanges {
// @public (undocumented)
export class SiSidePanelContentComponent implements OnInit {
constructor();
readonly back: _angular_core.OutputEmitterRef<void>;
readonly backButtonLabel: _angular_core.InputSignal<TranslatableString>;
readonly closeButtonLabel: _angular_core.InputSignal<TranslatableString>;
// @deprecated (undocumented)
readonly collapsibleInput: _angular_core.InputSignalWithTransform<boolean | undefined, unknown>;
readonly displayMode: _angular_core.InputSignal<SidePanelDisplayMode | undefined>;
readonly enterFullscreenLabel: _angular_core.InputSignal<TranslatableString>;
readonly exitFullscreenLabel: _angular_core.InputSignal<TranslatableString>;
focusBackButton(): void;
readonly heading: _angular_core.InputSignal<TranslatableString>;
readonly navigateConfig: _angular_core.InputSignal<SidePanelNavigateConfig | undefined>;
readonly primaryActions: _angular_core.InputSignal<(MenuItem | ContentActionBarMainItem)[]>;
readonly searchable: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly searchEvent: _angular_core.OutputEmitterRef<string>;
readonly searchPlaceholder: _angular_core.InputSignal<TranslatableString>;
readonly secondaryActions: _angular_core.InputSignal<(MenuItem | MenuItem_2)[]>;
readonly showBackButton: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly showMobileDrawerBadge: _angular_core.InputSignalWithTransform<boolean, unknown>;
// @deprecated
readonly statusActions: _angular_core.InputSignal<StatusItem[]>;
Expand Down
18 changes: 18 additions & 0 deletions playwright/e2e/element-examples/si-side-panel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { expect, test } from '../../support/test-helpers';
test.describe('si-side-panel', () => {
const example = 'si-side-panel/si-side-panel';
const exampleCollapsible = 'si-side-panel/si-side-panel-collapsible';
const exampleInnerNavigation = 'si-side-panel/si-side-panel-inner-navigation';

const exampleCollapsibleLegacyStatusActions =
'si-side-panel/si-side-panel-collapsible-legacy-status-actions';
Expand Down Expand Up @@ -87,4 +88,21 @@ test.describe('si-side-panel', () => {
await expect(page.locator('si-side-panel .fullscreen-button')).toBeHidden();
await si.runVisualAndA11yTests();
});

test(exampleInnerNavigation, async ({ page, si }) => {
await si.visitExample(exampleInnerNavigation);

await page.getByRole('button', { name: 'Open side panel' }).click();
const firstItem = page.getByRole('button', { name: 'Title link item 1' });
await firstItem.click();

const backButton = page.getByRole('button', { name: 'Back to main view' });
await expect(backButton).toBeFocused();
await expect(page.getByText('Content detail 1', { exact: true })).toBeVisible();
await si.runVisualAndA11yTests('detail');

await backButton.click();
await expect(firstItem).toBeFocused();
await si.runVisualAndA11yTests('list');
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
- button "Back to main view"
- paragraph: Content detail 1
- button "Close"
- text: Property 1
- textbox "Placeholder"
- text: Property 2
- textbox "Placeholder"
- text: Property 3
- combobox "Property 3":
- option "Placeholder" [disabled] [selected]
- option "Option 1"
- option "Option 2"
- text: Property 4
- spinbutton "Property 4"
- text: °C Group label
- checkbox "Label Label Label Label"
- text: Label
- checkbox
- text: Label Group label
- radio
- text: Label
- radio
- text: Label Section title
- switch "Toggle label"
- text: Toggle label
- main:
- link "Siemens logo":
- /url: "#/"
- heading "Application name" [level=1]
- button "Side panel"
- button "Open side panel"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
- paragraph: Content title 1
- button "Close"
- list:
- listitem:
- button "Title link item 1"
- listitem:
- button "Title link item 2"
- listitem:
- button "Title link item 3"
- listitem:
- button "Title link item 4"
- listitem:
- button "Title link item 5"
- main:
- link "Siemens logo":
- /url: "#/"
- heading "Application name" [level=1]
- button "Side panel"
- button "Open side panel"
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
<div class="rpanel-header pt-5">
<p class="si-h5 my-0 ms-6 auto-hide text-truncate">{{ heading() | translate }}</p>
<div class="d-flex align-items-center min-width-0 auto-hide">
@if (showBackButton()) {
<button
#backButton
type="button"
class="btn btn-icon btn-tertiary ms-4 auto-hide"
[attr.aria-label]="backButtonLabel() | translate"
[tabindex]="focusable() ? null : '-1'"
(click)="back.emit()"
>
<si-icon class="flip-rtl" [icon]="icons.elementLeft4" />
</button>
}
<p
class="si-h5 my-0 auto-hide text-truncate"
[class.ms-2]="showBackButton()"
[class.ms-6]="!showBackButton()"
>
{{ heading() | translate }}
</p>
</div>
@if ((primaryActions().length || secondaryActions().length) && focusable()) {
<si-content-action-bar
class="auto-hide ms-auto"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
* Copyright (c) Siemens 2016 - 2026
* SPDX-License-Identifier: MIT
*/
import { Component, signal } from '@angular/core';
import { Component, signal, viewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { page, userEvent } from 'vitest/browser';

import { SiSidePanelContentComponent } from './si-side-panel-content.component';
import { SiSidePanelModule } from './si-side-panel.module';
import { SiSidePanelService } from './si-side-panel.service';
import { SidePanelDisplayMode, SidePanelNavigateConfig } from './side-panel.model';
Expand All @@ -14,13 +16,19 @@ import { SidePanelDisplayMode, SidePanelNavigateConfig } from './side-panel.mode
template: `<si-side-panel [collapsed]="false">
<si-side-panel-content
heading="Title"
backButtonLabel="Back to devices"
[showBackButton]="showBackButton()"
[displayMode]="displayMode()"
[navigateConfig]="navigateConfig"
(back)="back()"
/>
</si-side-panel>`
})
class TestHostComponent {
readonly sidePanelContent = viewChild.required(SiSidePanelContentComponent);
readonly showBackButton = signal(false);
readonly displayMode = signal<SidePanelDisplayMode | undefined>(undefined);
readonly back = vi.fn();
readonly navigateConfig: SidePanelNavigateConfig = {
type: 'link',
label: 'Navigate',
Expand Down Expand Up @@ -55,6 +63,24 @@ describe('SiSidePanelContentComponent', () => {
expect(sidePanelService.toggle).toHaveBeenCalled();
});

it('should show the optional back button and emit when activated', async () => {
const backButton = page.getByRole('button', { name: 'Back to devices' });

await expect.element(backButton).not.toBeInTheDocument();

component.showBackButton.set(true);
await fixture.whenStable();

await expect.element(backButton).toBeInTheDocument();

component.sidePanelContent().focusBackButton();
await expect.element(backButton).toHaveFocus();

await userEvent.click(backButton);

expect(component.back).toHaveBeenCalledOnce();
});

it('should show fullscreen/navigation button based on display mode', () => {
const getNavigateLink = (): HTMLAnchorElement | null =>
element.querySelector<HTMLAnchorElement>('a[href="/details"]');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import {
computed,
DestroyRef,
effect,
ElementRef,
inject,
input,
OnInit,
output,
signal
signal,
viewChild
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, RouterLink } from '@angular/router';
Expand All @@ -23,6 +25,7 @@ import {
elementDoubleLeft,
elementDoubleRight,
elementExport,
elementLeft4,
elementPinch,
elementZoom
} from '@siemens/element-icons';
Expand Down Expand Up @@ -96,6 +99,7 @@ export interface StatusItem extends MenuItemLegacy {
})
export class SiSidePanelContentComponent implements OnInit {
private static idCounter = 0;
private readonly backButton = viewChild<ElementRef<HTMLButtonElement>>('backButton');
protected readonly panelContentId = `__si-side-panel-content-${SiSidePanelContentComponent.idCounter++}`;
/**
* @deprecated This input is no longer used. The collapsible state is managed by the SiSidePanelService.
Expand All @@ -114,6 +118,28 @@ export class SiSidePanelContentComponent implements OnInit {
*/
readonly heading = input<TranslatableString>('');

/**
* Whether to show the back button.
*
* @defaultValue false
*/
readonly showBackButton = input(false, { transform: booleanAttribute });

/**
* Aria label for the optional back button.
*
* @defaultValue
* ```
* t(() => $localize`:@@SI_SIDE_PANEL.BACK:Back`)
* ```
*/
readonly backButtonLabel = input(t(() => $localize`:@@SI_SIDE_PANEL.BACK:Back`));

/** Focuses the back button when it is available. */
focusBackButton(): void {
this.backButton()?.nativeElement.focus();
}

/**
* Input list of primary action items
*
Expand Down Expand Up @@ -240,6 +266,11 @@ export class SiSidePanelContentComponent implements OnInit {
*/
readonly searchEvent = output<string>();

/**
* Emitted when the back button is activated.
*/
readonly back = output<void>();

protected readonly activatedRoute = inject(ActivatedRoute, { optional: true });
protected readonly service = inject(SiSidePanelService);
protected readonly isCollapsed = signal(false);
Expand All @@ -258,6 +289,7 @@ export class SiSidePanelContentComponent implements OnInit {
elementDoubleLeft,
elementDoubleRight,
elementExport,
elementLeft4,
elementPinch,
elementZoom
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export interface SiTranslatableKeys {
'SI_SEARCH_BAR.CLEAR_BUTTON'?: string;
'SI_SELECT.NO-RESULTS-FOUND'?: string;
'SI_SELECT.SEARCH-PLACEHOLDER'?: string;
'SI_SIDE_PANEL.BACK'?: string;
'SI_SIDE_PANEL.CLOSE'?: string;
'SI_SIDE_PANEL.ENTER_FULLSCREEN'?: string;
'SI_SIDE_PANEL.EXIT_FULLSCREEN'?: string;
Expand Down
Loading
Loading