Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5736d8f
update animation logic and fix focus and trigger style delay
myrta2302 Nov 3, 2025
137d4fc
add animation prop to breadcrumbs
myrta2302 Nov 3, 2025
12c1ebe
update documentation examples
myrta2302 Nov 3, 2025
49b054d
Merge branch 'main' into 6538-bug-fix-post-menu-animation
myrta2302 Nov 3, 2025
7d49eb2
restore toggleMenu emit
myrta2302 Nov 3, 2025
acc0a7e
revert test
myrta2302 Nov 3, 2025
69c496a
Merge branch 'main' into 6538-bug-fix-post-menu-animation
myrta2302 Nov 4, 2025
7eb2f39
Merge branch 'main' into 6538-bug-fix-post-menu-animation
myrta2302 Nov 4, 2025
c82051b
update animation props logic
myrta2302 Nov 4, 2025
013d70d
revert file
myrta2302 Nov 4, 2025
e39b70d
emit events also for no animation case
myrta2302 Nov 4, 2025
698b33b
show control only for menu variant
myrta2302 Nov 4, 2025
799d860
fix e2e error
myrta2302 Nov 4, 2025
aec4869
add changeset
myrta2302 Nov 4, 2025
eab4e5f
review comments update
myrta2302 Nov 6, 2025
46ba2f7
changeset update
myrta2302 Nov 6, 2025
947679b
minor
myrta2302 Nov 6, 2025
f5dac59
generalize animation run
myrta2302 Nov 6, 2025
6f2e62f
fix e2e error
myrta2302 Nov 6, 2025
7328a02
minor
myrta2302 Nov 6, 2025
ad74696
fix e2e error
myrta2302 Nov 6, 2025
46d9592
minor
myrta2302 Nov 6, 2025
a7d1ca4
update
myrta2302 Nov 6, 2025
87ab0af
identation error
myrta2302 Nov 6, 2025
3e79ea0
Merge branch 'main' into 6538-bug-fix-post-menu-animation
myrta2302 Nov 6, 2025
00b54f1
fix tooltip misync
myrta2302 Nov 7, 2025
1fad5bc
tooltip fix and obsolete focus logic removal
myrta2302 Nov 7, 2025
53c41f5
fix e2e test error
myrta2302 Nov 7, 2025
afe29ae
revert console logs
myrta2302 Nov 7, 2025
c855b0b
lint error again
myrta2302 Nov 7, 2025
68fd70c
Merge branch 'main' into 6538-bug-fix-post-menu-animation
myrta2302 Nov 10, 2025
64d07bb
remove obsolete logic
myrta2302 Nov 10, 2025
3feb44f
revert file
myrta2302 Nov 10, 2025
5370cf2
revert adding props in language switch and breadcrumbs
myrta2302 Nov 13, 2025
17ec978
Update seven-breads-press.md
myrta2302 Nov 13, 2025
29bac1c
revert files
myrta2302 Nov 13, 2025
1ce729a
Merge branch 'main' into 6538-bug-fix-post-menu-animation
myrta2302 Nov 13, 2025
c2857ce
update post-menu animation
myrta2302 Nov 13, 2025
877f53a
updated popovercontainer
myrta2302 Nov 13, 2025
010d01d
lint error
myrta2302 Nov 13, 2025
31c0035
update
myrta2302 Nov 13, 2025
2251b43
lint error
myrta2302 Nov 13, 2025
6b991e2
Merge branch 'main' into 6538-bug-fix-post-menu-animation
myrta2302 Nov 21, 2025
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
5 changes: 5 additions & 0 deletions .changeset/seven-breads-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-components': minor
---

Enabled a 'pop-in' animation for the `post-breadcrumbs` concatenated version and the `post-language-switch` menu variant components.
20 changes: 16 additions & 4 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import { BannerType } from "./components/post-banner/banner-types";
import { ButtonType } from "./components/post-closebutton/button-types";
import { SwitchVariant } from "./components/post-language-switch/switch-variants";
import { Placement } from "@floating-ui/dom";
import { AnimationName } from "./components/post-popovercontainer/post-popovercontainer";
export { HeadingLevel } from "./types/index";
export { BannerType } from "./components/post-banner/banner-types";
export { ButtonType } from "./components/post-closebutton/button-types";
export { SwitchVariant } from "./components/post-language-switch/switch-variants";
export { Placement } from "@floating-ui/dom";
export { AnimationName } from "./components/post-popovercontainer/post-popovercontainer";
export namespace Components {
interface PostAccordion {
/**
Expand Down Expand Up @@ -334,6 +336,11 @@ export namespace Components {
"for": string;
}
interface PostMenu {
/**
* Sets the animation type
* @default 'pop-in'
*/
"animation": 'pop-in' | null;
/**
* Hides the popover menu and restores focus to the previously focused element.
*/
Expand Down Expand Up @@ -407,7 +414,7 @@ export namespace Components {
* Animation style
* @default null
*/
"animation"?: 'pop-in' | null;
"animation"?: AnimationName | null;
/**
* Whether or not to display a little pointer arrow
* @default false
Expand Down Expand Up @@ -446,12 +453,12 @@ export namespace Components {
"safeSpace"?: 'triangle' | 'trapezoid';
/**
* Programmatically display the popovercontainer
* @param target A focusable element inside the <post-popover-trigger> component that controls the popover
* @param target A focusable element inside the trigger component that controls the popover
*/
"show": (target: HTMLElement) => Promise<void>;
/**
* Toggle popovercontainer display
* @param target A focusable element inside the <post-popover-trigger> component that controls the popover
* @param target A focusable element inside the trigger component that controls the popover
* @param force Pass true to always show or false to always hide
*/
"toggle": (target: HTMLElement, force?: boolean) => Promise<boolean>;
Expand Down Expand Up @@ -1285,6 +1292,11 @@ declare namespace LocalJSX {
"for": string;
}
interface PostMenu {
/**
* Sets the animation type
* @default 'pop-in'
*/
"animation"?: 'pop-in' | null;
/**
* An accessible name for the menu.
*/
Expand Down Expand Up @@ -1334,7 +1346,7 @@ declare namespace LocalJSX {
* Animation style
* @default null
*/
"animation"?: 'pop-in' | null;
"animation"?: AnimationName | null;
/**
* Whether or not to display a little pointer arrow
* @default false
Expand Down
15 changes: 10 additions & 5 deletions packages/components/src/components/post-menu/post-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export class PostMenu {
checkRequiredAndType(this, 'label', 'string');
}

/**
* Sets the animation type
*/
@Prop() readonly animation: 'pop-in' | null = 'pop-in';

/**
* Holds the current visibility state of the menu.
* This state is internally managed to track whether the menu is open (`true`) or closed (`false`),
Expand Down Expand Up @@ -149,7 +154,7 @@ export class PostMenu {

@EventFrom('post-popovercontainer')
private readonly handlePostShown = (event: CustomEvent<{ first?: boolean }>) => {
// Only for the first open
// Only for the first open
if (event.detail.first) {
// Add "menu" and "menuitem" aria roles and aria-label
this.host.setAttribute('role', 'menu');
Expand All @@ -164,10 +169,9 @@ export class PostMenu {
};

@EventFrom('post-popovercontainer')
private readonly handlePostToggled = (event: CustomEvent<{ isOpen: boolean }>) => {
this.isVisible = event.detail.isOpen;
private readonly handlePostBeforeToggle = (event: CustomEvent<{ willOpen: boolean }>) => {
this.isVisible = event.detail.willOpen;
this.toggleMenu.emit(this.isVisible);

if (this.isVisible) {
this.lastFocusedElement = this.root?.activeElement as HTMLElement;
requestAnimationFrame(() => {
Expand Down Expand Up @@ -249,8 +253,9 @@ export class PostMenu {
<Host data-version={version}>
<post-popovercontainer
onPostShow={this.handlePostShown}
onPostToggle={this.handlePostToggled}
onPostBeforeToggle={this.handlePostBeforeToggle}
placement={this.placement}
animation={this.animation || null}
ref={e => (this.popoverRef = e)}
>
<div part="menu">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ interface PopoverElement {
togglePopover: (force?: boolean) => boolean;
}

const ANIMATIONS = {
'pop-in': popIn,
} as const;

export type AnimationName = keyof typeof ANIMATIONS;

export type PostPopoverElement = HTMLElement & PopoverElement;

/**
Expand Down Expand Up @@ -118,7 +124,7 @@ export class PostPopovercontainer {
/**
* Animation style
*/
@Prop() readonly animation?: 'pop-in' | null = null;
@Prop() readonly animation?: AnimationName | null = null;

/**
* Whether or not to display a little pointer arrow
Expand Down Expand Up @@ -150,6 +156,11 @@ export class PostPopovercontainer {
checkEmptyOrOneOf(this, 'safeSpace', ['triangle', 'trapezoid']);
}

@Watch('animation')
validateAnimation() {
checkEmptyOrOneOf(this, 'animation', Object.keys(ANIMATIONS));
}

/**
* Updates cursor position for safe space feature when popover is open.
* Sets CSS custom properties for dynamic styling of safe area.
Expand Down Expand Up @@ -181,7 +192,7 @@ export class PostPopovercontainer {

/**
* Programmatically display the popovercontainer
* @param target A focusable element inside the <post-popover-trigger> component that controls the popover
* @param target A focusable element inside the trigger component that controls the popover
*/
@Method()
async show(target: HTMLElement) {
Expand Down Expand Up @@ -209,7 +220,8 @@ export class PostPopovercontainer {
this.postShow.emit({ first: this.hasOpenedOnce });
if (this.hasOpenedOnce) this.hasOpenedOnce = false;
} else {
this.runOpenAnimation(content);
const animationFn = ANIMATIONS[this.animation];
this.runOpenAnimation(animationFn, content);
}
}

Expand All @@ -232,7 +244,7 @@ export class PostPopovercontainer {
}

// Cancel any running animation
if (this.animation !== null && this.currentAnimation) {
if (this.currentAnimation) {
this.currentAnimation.cancel();
this.currentAnimation = null;
}
Expand All @@ -242,15 +254,57 @@ export class PostPopovercontainer {
this.postHide.emit();
}

/**
* Programmatically hide the popovercontainer
*/
@Method()
async hide() {
if (!this.toggleTimeoutId) {
this.eventTarget = null;
this.host.hidePopover();
this.postHide.emit();
}
}

/**
* Toggle popovercontainer display
* @param target A focusable element inside the trigger component that controls the popover
* @param force Pass true to always show or false to always hide
*/
@Method()
async toggle(target: HTMLElement, force?: boolean): Promise<boolean> {
this.eventTarget = target;
// Prevent instant double toggle
if (!this.toggleTimeoutId) {
this.calculatePosition();
this.host.togglePopover(force);
this.toggleTimeoutId = null;
}

return this.host.matches(':where(:popover-open, .popover-open)');
}

/**
* Runs the animation and emits the toggle/show/hide events in the correct timing
*/

private async runOpenAnimation(element: HTMLElement) {
private async runOpenAnimation(
animationFn: (el: HTMLElement) => Animation | undefined,
element: HTMLElement,
) {
let animation: Animation | undefined;

try {
animation = popIn(element);
animation = animationFn(element);
if (!animation) {
// Fallback: no animation, just emit open events directly
this.postBeforeToggle.emit({ willOpen: true });
this.postBeforeShow.emit({ first: this.hasOpenedOnce });
this.postToggle.emit({ isOpen: true });
this.postShow.emit({ first: this.hasOpenedOnce });

return;
}

this.currentAnimation = animation;

Expand Down Expand Up @@ -278,36 +332,6 @@ export class PostPopovercontainer {
}
}

/**
* Programmatically hide the popovercontainer
*/
@Method()
async hide() {
if (!this.toggleTimeoutId) {
this.eventTarget = null;
this.host.hidePopover();
this.postHide.emit();
}
}

/**
* Toggle popovercontainer display
* @param target A focusable element inside the <post-popover-trigger> component that controls the popover
* @param force Pass true to always show or false to always hide
*/
@Method()
async toggle(target: HTMLElement, force?: boolean): Promise<boolean> {
this.eventTarget = target;
// Prevent instant double toggle
if (!this.toggleTimeoutId) {
this.calculatePosition();
this.host.togglePopover(force);
this.toggleTimeoutId = null;
}

return this.host.matches(':where(:popover-open, .popover-open)');
}

/**
* Start or stop auto updates based on popovercontainer events.
* Popovercontainers can be closed or opened with other methods than class members,
Expand All @@ -330,6 +354,7 @@ export class PostPopovercontainer {
* an influence on popovercontainer positioning
*/
private startAutoupdates() {
if (!this.eventTarget || !this.host) return;
this.clearAutoUpdate = autoUpdate(
this.eventTarget,
this.host,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ Programmatically display the popovercontainer

#### Parameters

| Name | Type | Description |
| -------- | ------------- | ----------------------------------------------------------------------------------------- |
| `target` | `HTMLElement` | A focusable element inside the <post-popover-trigger> component that controls the popover |
| Name | Type | Description |
| -------- | ------------- | -------------------------------------------------------------------------- |
| `target` | `HTMLElement` | A focusable element inside the trigger component that controls the popover |

#### Returns

Expand All @@ -80,10 +80,10 @@ Toggle popovercontainer display

#### Parameters

| Name | Type | Description |
| -------- | ------------- | ----------------------------------------------------------------------------------------- |
| `target` | `HTMLElement` | A focusable element inside the <post-popover-trigger> component that controls the popover |
| `force` | `boolean` | Pass true to always show or false to always hide |
| Name | Type | Description |
| -------- | ------------- | -------------------------------------------------------------------------- |
| `target` | `HTMLElement` | A focusable element inside the trigger component that controls the popover |
| `force` | `boolean` | Pass true to always show or false to always hide |

#### Returns

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const meta: MetaComponent = {
placement: 'bottom',
padding: '',
backgroundColor: '',
animation: 'pop-in',
},
argTypes: {
id: {
Expand Down Expand Up @@ -44,6 +45,7 @@ const meta: MetaComponent = {
};

function render(args: Args) {
console.log(args.animation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.log(args.animation);

// Construct the style string conditionally based on padding and backgroundColor
const styles = [
args.padding ? `--post-menu-padding: ${args.padding};` : '',
Expand All @@ -62,6 +64,7 @@ function render(args: Args) {
id="${args.id}"
placement="${args.placement !== 'bottom' ? args.placement : nothing}"
label="Example menu"
animation="${args.animation ? args.animation : nothing}"
>
<post-menu-item><button>Example 1</button></post-menu-item>
<post-menu-item><a href="#">Example 2</a></post-menu-item>
Expand Down
Loading