Skip to content
Merged
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
15 changes: 15 additions & 0 deletions jest-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,18 @@ window.ResizeObserver = class ResizeObserver {
unobserve() {}
disconnect() {}
};

window.matchMedia =
window.matchMedia ||
function (query: string): MediaQueryList {
return {
matches: false,
media: query,
onchange: null,
addListener: () => {},
removeListener: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => false
} as unknown as MediaQueryList;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@
[attr.aria-expanded]="isExpanded"
[attr.aria-disabled]="disabled ? true : null"
[attr.aria-controls]="contentPanelId"
[@panelHeader]="
isExpanded
? {
value: 'visible',
params: {
borderStyle: bordered ? '1px solid ' + cvtBorderColor : ''
}
}
: 'hidden'
"
[@panelHeader]="{
value: isExpanded ? 'visible' : 'hidden',
params: {
borderStyle:
isExpanded && bordered ? '1px solid ' + cvtBorderColor : '',
transitionParams: resolvedTransitionType
}
}"
[class.disabled]="disabled"
[class.keyboard-active]="isKeyboardActive"
[style.cursor]="disabled ? 'default' : 'pointer'"
Expand All @@ -40,10 +38,7 @@
<div class="cps-expansion-panel-title">{{ headerTitle }}</div>
@if (showChevron && !disabled) {
<span class="cps-expansion-panel-chevron">
<cps-icon
icon="chevron-down"
size="small"
color="text-dark">
<cps-icon icon="chevron-down" size="small" color="text-dark">
</cps-icon>
</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,28 @@ describe('CpsExpansionPanelComponent', () => {
expect(header.classList).toContain('keyboard-active');
});
});

describe('reduced motion', () => {
afterEach(() => {
jest.restoreAllMocks();
});

it('should use the default transition duration by default', () => {
jest
.spyOn(window, 'matchMedia')
.mockReturnValue({ matches: false } as MediaQueryList);

expect(component.resolvedTransitionType).toBe(
'0.2s cubic-bezier(0.4, 0, 0.2, 1)'
);
});

it('should use a near-instant transition when the OS prefers reduced motion', () => {
jest
.spyOn(window, 'matchMedia')
.mockReturnValue({ matches: true } as MediaQueryList);

expect(component.resolvedTransitionType).toBe('1ms');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
Component,
ElementRef,
EventEmitter,
Inject,
Input,
OnChanges,
OnInit,
Output,
Renderer2,
ViewChild,
inject,
type SimpleChanges
} from '@angular/core';
import { CpsIconComponent, IconType } from '../cps-icon/cps-icon.component';
Expand All @@ -27,6 +27,10 @@
transition,
trigger
} from '@angular/animations';
import {
prefersReducedMotion,
REDUCED_MOTION_DURATION
} from '../../utils/internal/motion-utils';

const transitionType = '0.2s cubic-bezier(0.4, 0, 0.2, 1)';

Expand Down Expand Up @@ -54,7 +58,9 @@
}),
{ params: { borderStyle: '' } }
),
transition('visible <=> hidden', [animate(transitionType)]),
transition('visible <=> hidden', [animate('{{transitionParams}}')], {
params: { transitionParams: transitionType }
}),
transition('void => *', animate(0))
])
]
Expand Down Expand Up @@ -138,8 +144,6 @@

@ViewChild('panelContentElem') panelContentElem!: ElementRef;

private _contentExpandAnimation: AnimationFactory;
private _contentCollapseAnimation: AnimationFactory;
private _contentAnimationPlayer: AnimationPlayer | undefined;

readonly contentPanelId = generateUniqueId('cps-expansion-panel-content');
Expand All @@ -150,29 +154,35 @@
cvtBackgroundColor = '';
cvtBorderRadius = '';

constructor(
private _animationBuilder: AnimationBuilder,
@Inject(DOCUMENT) private document: Document,
private _renderer: Renderer2
) {
this._contentCollapseAnimation = this._animationBuilder.build([
private readonly _animationBuilder = inject(AnimationBuilder);
private readonly _document = inject(DOCUMENT);
private readonly _renderer = inject(Renderer2);

get resolvedTransitionType(): string {
return prefersReducedMotion() ? REDUCED_MOTION_DURATION : transitionType;
}

private _buildContentCollapseAnimation(): AnimationFactory {
return this._animationBuilder.build([
style({
height: '*'
}),
animate(
transitionType,
this.resolvedTransitionType,
style({
height: 0
})
)
]);
}

this._contentExpandAnimation = this._animationBuilder.build([
private _buildContentExpandAnimation(): AnimationFactory {
return this._animationBuilder.build([
style({
height: 0
}),
animate(
transitionType,
this.resolvedTransitionType,
style({
height: '*'
})
Expand All @@ -181,8 +191,8 @@
}

ngOnInit(): void {
this.cvtBorderColor = getCSSColor(this.borderColor, this.document);
this.cvtBackgroundColor = getCSSColor(this.backgroundColor, this.document);
this.cvtBorderColor = getCSSColor(this.borderColor, this._document);
this.cvtBackgroundColor = getCSSColor(this.backgroundColor, this._document);
this.cvtBorderRadius = convertSize(this.borderRadius);
this.cvtWidth = convertSize(this.width);

Expand All @@ -190,14 +200,14 @@
}

ngOnChanges(changes: SimpleChanges): void {
if (changes.borderColor) {
this.cvtBorderColor = getCSSColor(this.borderColor, this.document);
this.cvtBorderColor = getCSSColor(this.borderColor, this._document);

Check warning on line 204 in projects/cps-ui-kit/src/lib/components/cps-expansion-panel/cps-expansion-panel.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 205 in projects/cps-ui-kit/src/lib/components/cps-expansion-panel/cps-expansion-panel.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
if (changes.backgroundColor) {
this.cvtBackgroundColor = getCSSColor(
this.backgroundColor,
this.document
this._document
);

Check warning on line 210 in projects/cps-ui-kit/src/lib/components/cps-expansion-panel/cps-expansion-panel.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 211 in projects/cps-ui-kit/src/lib/components/cps-expansion-panel/cps-expansion-panel.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
if (changes.borderRadius) {
this.cvtBorderRadius = convertSize(this.borderRadius);
Expand Down Expand Up @@ -236,13 +246,15 @@

const el = this.panelContentElem?.nativeElement;
if (this.isExpanded) {
this._contentAnimationPlayer = this._contentCollapseAnimation.create(el);
this._contentAnimationPlayer =
this._buildContentCollapseAnimation().create(el);
this._contentAnimationPlayer.onDone(() => {
this._updateContentVisibilityStyles(false, el);
});
} else {
this._updateContentVisibilityStyles(true, el);
this._contentAnimationPlayer = this._contentExpandAnimation.create(el);
this._contentAnimationPlayer =
this._buildContentExpandAnimation().create(el);
}

this._contentAnimationPlayer.onStart(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ $color-inner: var(--cps-color-energy);
}
}

// Keep indicating progress under reduced motion, just slower, instead of freezing
@media (prefers-reduced-motion: reduce) {
&-text {
animation-duration: 8s !important;
animation-iteration-count: infinite !important;
}

&-circles-circle {
animation-duration: 3000ms !important;
animation-iteration-count: infinite !important;
}
}

@keyframes cps-loader-text-animation {
0%,
100% {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
[@animation]="{
value: overlayVisible ? 'open' : 'close',
params: {
showTransitionParams: showTransitionOptions,
hideTransitionParams: hideTransitionOptions
showTransitionParams: resolvedShowTransitionOptions,
hideTransitionParams: resolvedHideTransitionOptions
}
}"
(@animation.start)="onAnimationStart($event)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -799,4 +799,32 @@ describe('CpsMenuComponent', () => {
expect(component.scrollHandler).toBeNull();
});
});

describe('reduced motion', () => {
afterEach(() => {
jest.restoreAllMocks();
});

it('should use the configured transition options by default', () => {
jest
.spyOn(window, 'matchMedia')
.mockReturnValue({ matches: false } as MediaQueryList);

expect(component.resolvedShowTransitionOptions).toBe(
component.showTransitionOptions
);
expect(component.resolvedHideTransitionOptions).toBe(
component.hideTransitionOptions
);
});

it('should use a near-instant transition when the OS prefers reduced motion', () => {
jest
.spyOn(window, 'matchMedia')
.mockReturnValue({ matches: true } as MediaQueryList);

expect(component.resolvedShowTransitionOptions).toBe('1ms');
expect(component.resolvedHideTransitionOptions).toBe('1ms');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ import { CpsIconComponent } from '../cps-icon/cps-icon.component';
import { CpsProgressCircularComponent } from '../cps-progress-circular/cps-progress-circular.component';
import { PrimeNG } from 'primeng/config';
import { CPS_FOCUS_SERVICE } from '../../services/cps-focus/cps-focus.service';
import {
prefersReducedMotion,
REDUCED_MOTION_DURATION
} from '../../utils/internal/motion-utils';

type Nullable<T = void> = T | null | undefined;
type VoidListener = () => void | null | undefined;
Expand Down Expand Up @@ -219,6 +223,18 @@ export class CpsMenuComponent implements AfterViewInit, OnDestroy, OnChanges {
*/
@Output() containerMouseLeave = new EventEmitter<MouseEvent>();

get resolvedShowTransitionOptions(): string {
return prefersReducedMotion()
? REDUCED_MOTION_DURATION
: this.showTransitionOptions;
}

get resolvedHideTransitionOptions(): string {
return prefersReducedMotion()
? REDUCED_MOTION_DURATION
: this.hideTransitionOptions;
}

withIcons = true;
autoZIndex = true;
baseZIndex = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
animation: cps-progress-circular-animation 0.8s linear infinite;
}

// Keep indicating progress under reduced motion, just slower, instead of freezing
@media (prefers-reduced-motion: reduce) {
.cps-progress-circular {
animation-duration: 2.4s !important;
animation-iteration-count: infinite !important;
}
}

@keyframes cps-progress-circular-animation {
to {
transform: rotate(360deg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@
}
}

// Keep indicating progress under reduced motion, just slower, instead of freezing
@media (prefers-reduced-motion: reduce) {
.cps-progress-linear {
.inc {
animation-duration: 6s !important;
animation-iteration-count: infinite !important;
}
.dec {
animation-duration: 6s !important;
animation-iteration-count: infinite !important;
}
}
}

@keyframes increase {
from {
left: -5%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
<cps-icon [icon]="item.icon" size="normal"> </cps-icon>
<span
class="cps-sidebar-menu-item-label"
[@onExpand]="isExpanded ? 'expanded' : 'collapsed'">
[@onExpand]="{
value: isExpanded ? 'expanded' : 'collapsed',
params: { transitionParams: resolvedTransitionParams }
}">
{{ item.title }}
</span>
</a>
Expand Down Expand Up @@ -59,7 +62,10 @@
<cps-icon [icon]="item.icon" size="normal"> </cps-icon>
<span
class="cps-sidebar-menu-item-label"
[@onExpand]="isExpanded ? 'expanded' : 'collapsed'">
[@onExpand]="{
value: isExpanded ? 'expanded' : 'collapsed',
params: { transitionParams: resolvedTransitionParams }
}">
{{ item.title }}
</span>
</button>
Expand Down
Loading
Loading