Skip to content

Commit 94017ad

Browse files
authored
feat(core/popover): implement popover component IX-3942 (#2574)
1 parent ceec4d6 commit 94017ad

102 files changed

Lines changed: 7292 additions & 208 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/ix-3942-popover.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@siemens/ix': minor
3+
---
4+
5+
Add **ix-popover** with optional **ix-popover-header**, **ix-popover-content**, **ix-popover-image**, and **ix-popover-footer** for anchored floating panels. Supports click and hover triggers, placement, nesting, focus management, and programmatic open/close.

packages/angular-standalone-test-app/src/app/app.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,8 @@ export const routePaths: IxPreviewRoutes = {
421421
import('../preview-examples/pill').then((m) => m.default),
422422
'preview/pill-variants': () =>
423423
import('../preview-examples/pill-variants').then((m) => m.default),
424+
'preview/popover': () =>
425+
import('../preview-examples/popover').then((m) => m.default),
424426
'preview/popover-news': () =>
425427
import('../preview-examples/popover-news').then((m) => m.default),
426428
'preview/push-card': () =>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2026 Siemens AG
3+
4+
SPDX-License-Identifier: MIT
5+
6+
This source code is licensed under the MIT license found in the
7+
LICENSE file in the root directory of this source tree.
8+
-->
9+
10+
<ix-button id="triggerId">Open</ix-button>
11+
<ix-popover
12+
#popover
13+
trigger="triggerId"
14+
placement="bottom"
15+
trigger-mode="click"
16+
has-spike
17+
close-on-click-outside
18+
aria-label="Release highlights"
19+
>
20+
<ix-popover-header
21+
icon="info"
22+
icon-color="color-info"
23+
aria-label-close-icon-button="Close popover"
24+
>
25+
Release highlights
26+
<ix-pill slot="additional-items" variant="info">New</ix-pill>
27+
</ix-popover-header>
28+
<ix-popover-image
29+
[image]="popoverImageSrc"
30+
image-alt="Release preview"
31+
></ix-popover-image>
32+
<ix-popover-content>
33+
Check out the new dashboard and improved performance metrics.
34+
</ix-popover-content>
35+
<ix-popover-footer alignment="vertical">
36+
<span slot="start">v4.0.0</span>
37+
<ix-button variant="secondary" (click)="popover.hidePopover()"
38+
>Cancel</ix-button
39+
>
40+
<ix-button>Save</ix-button>
41+
</ix-popover-footer>
42+
</ix-popover>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 Siemens AG
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
import { Component } from '@angular/core';
11+
import { addIcons } from '@siemens/ix-icons';
12+
import { iconInfo } from '@siemens/ix-icons/icons';
13+
import {
14+
IxButton,
15+
IxPill,
16+
IxPopover,
17+
IxPopoverContent,
18+
IxPopoverFooter,
19+
IxPopoverHeader,
20+
IxPopoverImage,
21+
} from '@siemens/ix-angular/standalone';
22+
23+
addIcons({ iconInfo });
24+
25+
const POPOVER_IMAGE_SRC =
26+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='160'%3E%3Crect fill='%232a2a4a' width='100%25' height='100%25'/%3E%3Ctext x='50%25' y='50%25' fill='%23e0e0e0' text-anchor='middle' dy='.3em' font-size='18'%3ERelease%20preview%3C/text%3E%3C/svg%3E";
27+
28+
@Component({
29+
selector: 'app-example',
30+
imports: [
31+
IxButton,
32+
IxPill,
33+
IxPopover,
34+
IxPopoverHeader,
35+
IxPopoverImage,
36+
IxPopoverContent,
37+
IxPopoverFooter,
38+
],
39+
templateUrl: './popover.html',
40+
})
41+
export default class Popover {
42+
readonly popoverImageSrc = POPOVER_IMAGE_SRC;
43+
}

packages/angular-test-app/src/app/app-routing.module.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ import Pane from '../preview-examples/pane';
182182
import PaneLayout from '../preview-examples/pane-layout';
183183
import Pill from '../preview-examples/pill';
184184
import PillVariants from '../preview-examples/pill-variants';
185+
import Popover from '../preview-examples/popover';
185186
import PopoverNews from '../preview-examples/popover-news';
186187
import TooltipWithIcon from '../preview-examples/tooltip-with-icon';
187188
import ProgressIndicator from '../preview-examples/progress-indicator';
@@ -824,6 +825,7 @@ const routes: Routes = [
824825
{ path: 'pane-layout', component: PaneLayout },
825826
{ path: 'pill', component: Pill },
826827
{ path: 'pill-variants', component: PillVariants },
828+
{ path: 'popover', component: Popover },
827829
{ path: 'popover-news', component: PopoverNews },
828830
{ path: 'radio-button', component: Radiobutton },
829831
{ path: 'radio', component: Radio },
@@ -874,10 +876,6 @@ const routes: Routes = [
874876
component: Pill,
875877
},
876878
{ path: 'pill-variants', component: PillVariants },
877-
{
878-
path: 'popover-news',
879-
component: PopoverNews,
880-
},
881879
{
882880
path: 'push-card',
883881
component: PushCard,

packages/angular-test-app/src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ import Pane from '../preview-examples/pane';
193193
import PaneLayout from '../preview-examples/pane-layout';
194194
import Pill from '../preview-examples/pill';
195195
import PillVariants from '../preview-examples/pill-variants';
196+
import Popover from '../preview-examples/popover';
196197
import PopoverNews from '../preview-examples/popover-news';
197198
import ProgressIndicator from '../preview-examples/progress-indicator';
198199
import ProgressIndicatorCircular from '../preview-examples/progress-indicator-circular';
@@ -426,6 +427,7 @@ import WorkflowVertical from '../preview-examples/workflow-vertical';
426427
PaneLayout,
427428
Pill,
428429
PillVariants,
430+
Popover,
429431
PopoverNews,
430432
Radiobutton,
431433
Radio,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2026 Siemens AG
3+
4+
SPDX-License-Identifier: MIT
5+
6+
This source code is licensed under the MIT license found in the
7+
LICENSE file in the root directory of this source tree.
8+
-->
9+
10+
<ix-button id="triggerId">Open</ix-button>
11+
<ix-popover
12+
#popover
13+
trigger="triggerId"
14+
placement="bottom"
15+
trigger-mode="click"
16+
has-spike
17+
close-on-click-outside
18+
aria-label="Release highlights"
19+
>
20+
<ix-popover-header
21+
icon="info"
22+
icon-color="color-info"
23+
aria-label-close-icon-button="Close popover"
24+
>
25+
Release highlights
26+
<ix-pill slot="additional-items" variant="info">New</ix-pill>
27+
</ix-popover-header>
28+
<ix-popover-image
29+
image="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='160'%3E%3Crect fill='%232a2a4a' width='100%25' height='100%25'/%3E%3Ctext x='50%25' y='50%25' fill='%23e0e0e0' text-anchor='middle' dy='.3em' font-size='18'%3ERelease%20preview%3C/text%3E%3C/svg%3E"
30+
image-alt="Release preview"
31+
></ix-popover-image>
32+
<ix-popover-content>
33+
Check out the new dashboard and improved performance metrics.
34+
</ix-popover-content>
35+
<ix-popover-footer alignment="vertical">
36+
<span slot="start">v4.0.0</span>
37+
<ix-button variant="secondary" (click)="popover.hidePopover()"
38+
>Cancel</ix-button
39+
>
40+
<ix-button>Save</ix-button>
41+
</ix-popover-footer>
42+
</ix-popover>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 Siemens AG
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
import { Component } from '@angular/core';
11+
import { addIcons } from '@siemens/ix-icons';
12+
import { iconInfo } from '@siemens/ix-icons/icons';
13+
14+
addIcons({ iconInfo });
15+
16+
@Component({
17+
standalone: false,
18+
selector: 'app-example',
19+
templateUrl: './popover.html',
20+
})
21+
export default class Popover {}

packages/angular/src/components.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,6 +2195,142 @@ export class IxPill {
21952195
export declare interface IxPill extends Components.IxPill {}
21962196

21972197

2198+
@ProxyCmp({
2199+
inputs: ['closeOnClickOutside', 'hasSpike', 'placement', 'show', 'trigger', 'triggerMode'],
2200+
methods: ['showPopover', 'hidePopover']
2201+
})
2202+
@Component({
2203+
selector: 'ix-popover',
2204+
changeDetection: ChangeDetectionStrategy.OnPush,
2205+
template: '<ng-content></ng-content>',
2206+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2207+
inputs: ['closeOnClickOutside', 'hasSpike', 'placement', 'show', 'trigger', 'triggerMode'],
2208+
outputs: ['showChange', 'showChanged'],
2209+
standalone: false
2210+
})
2211+
export class IxPopover {
2212+
protected el: HTMLIxPopoverElement;
2213+
@Output() showChange = new EventEmitter<CustomEvent<boolean>>();
2214+
@Output() showChanged = new EventEmitter<CustomEvent<boolean>>();
2215+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
2216+
c.detach();
2217+
this.el = r.nativeElement;
2218+
}
2219+
}
2220+
2221+
2222+
export declare interface IxPopover extends Components.IxPopover {
2223+
/**
2224+
* Fires before visibility changes. Cancel to prevent. @since 5.1.0
2225+
*/
2226+
showChange: EventEmitter<CustomEvent<boolean>>;
2227+
/**
2228+
* Fires after visibility has changed @since 5.1.0
2229+
*/
2230+
showChanged: EventEmitter<CustomEvent<boolean>>;
2231+
}
2232+
2233+
2234+
@ProxyCmp({
2235+
inputs: ['noPadding']
2236+
})
2237+
@Component({
2238+
selector: 'ix-popover-content',
2239+
changeDetection: ChangeDetectionStrategy.OnPush,
2240+
template: '<ng-content></ng-content>',
2241+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2242+
inputs: ['noPadding'],
2243+
standalone: false
2244+
})
2245+
export class IxPopoverContent {
2246+
protected el: HTMLIxPopoverContentElement;
2247+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
2248+
c.detach();
2249+
this.el = r.nativeElement;
2250+
}
2251+
}
2252+
2253+
2254+
export declare interface IxPopoverContent extends Components.IxPopoverContent {}
2255+
2256+
2257+
@ProxyCmp({
2258+
inputs: ['alignment']
2259+
})
2260+
@Component({
2261+
selector: 'ix-popover-footer',
2262+
changeDetection: ChangeDetectionStrategy.OnPush,
2263+
template: '<ng-content></ng-content>',
2264+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2265+
inputs: ['alignment'],
2266+
standalone: false
2267+
})
2268+
export class IxPopoverFooter {
2269+
protected el: HTMLIxPopoverFooterElement;
2270+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
2271+
c.detach();
2272+
this.el = r.nativeElement;
2273+
}
2274+
}
2275+
2276+
2277+
export declare interface IxPopoverFooter extends Components.IxPopoverFooter {}
2278+
2279+
2280+
@ProxyCmp({
2281+
inputs: ['ariaLabelCloseIconButton', 'hideClose', 'icon', 'iconColor']
2282+
})
2283+
@Component({
2284+
selector: 'ix-popover-header',
2285+
changeDetection: ChangeDetectionStrategy.OnPush,
2286+
template: '<ng-content></ng-content>',
2287+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2288+
inputs: ['ariaLabelCloseIconButton', 'hideClose', 'icon', 'iconColor'],
2289+
outputs: ['closeClick'],
2290+
standalone: false
2291+
})
2292+
export class IxPopoverHeader {
2293+
protected el: HTMLIxPopoverHeaderElement;
2294+
@Output() closeClick = new EventEmitter<CustomEvent<MouseEvent>>();
2295+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
2296+
c.detach();
2297+
this.el = r.nativeElement;
2298+
}
2299+
}
2300+
2301+
2302+
export declare interface IxPopoverHeader extends Components.IxPopoverHeader {
2303+
/**
2304+
* Fires when close button is clicked.
2305+
Cancel to prevent closing. @since 5.1.0
2306+
*/
2307+
closeClick: EventEmitter<CustomEvent<MouseEvent>>;
2308+
}
2309+
2310+
2311+
@ProxyCmp({
2312+
inputs: ['image', 'imageAlt']
2313+
})
2314+
@Component({
2315+
selector: 'ix-popover-image',
2316+
changeDetection: ChangeDetectionStrategy.OnPush,
2317+
template: '<ng-content></ng-content>',
2318+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2319+
inputs: ['image', 'imageAlt'],
2320+
standalone: false
2321+
})
2322+
export class IxPopoverImage {
2323+
protected el: HTMLIxPopoverImageElement;
2324+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
2325+
c.detach();
2326+
this.el = r.nativeElement;
2327+
}
2328+
}
2329+
2330+
2331+
export declare interface IxPopoverImage extends Components.IxPopoverImage {}
2332+
2333+
21982334
@ProxyCmp({
21992335
inputs: ['helperText', 'label', 'max', 'min', 'showTextAsTooltip', 'size', 'status', 'textAlignment', 'type', 'value']
22002336
})

packages/angular/src/declare-components.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ export const DIRECTIVES = [
7575
d.IxPane,
7676
d.IxPaneLayout,
7777
d.IxPill,
78+
d.IxPopover,
79+
d.IxPopoverContent,
80+
d.IxPopoverFooter,
81+
d.IxPopoverHeader,
82+
d.IxPopoverImage,
7883
d.IxProgressIndicator,
7984
d.IxPushCard,
8085
d.IxRadio,

0 commit comments

Comments
 (0)