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
10 changes: 5 additions & 5 deletions playwright/cps-accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ const components: ComponentEntry[] = [
}
},
// { route: '/paginator', name: 'Paginator', selector: 'cps-paginator' },
// {
// route: '/progress-circular',
// name: 'Progress circular',
// selector: 'cps-progress-circular'
// },
{
route: '/progress-circular',
name: 'Progress circular',
selector: 'cps-progress-circular'
},
// {
// route: '/progress-linear',
// name: 'Progress linear',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
"optional": false,
"readonly": false,
"type": "number | string",
"default": "40",
"default": "2.5rem",
"description": "Diameter of the progress bar, of type number denoting pixels or string."
},
{
"name": "strokeWidth",
"optional": false,
"readonly": false,
"type": "number | string",
"default": "4",
"default": "0.25rem",
"description": "Thickness of the progress bar, of type number denoting pixels or string."
},
{
Expand All @@ -28,6 +28,14 @@
"type": "string",
"default": "calm",
"description": "Color of the progress bar."
},
{
"name": "ariaLabel",
"optional": false,
"readonly": false,
"type": "string",
"default": "Loading",
"description": "Accessible label announced by screen readers to describe what is loading.\nFalls back to \"Loading\" when empty value is provided."
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
<app-component-docs-viewer [componentData]="componentData">
<!-- Example of component's usage -->
<div class="progr-circ-page-warning">
<b>This is not a page loader!</b> <br />
If you need to show a spinner while page content is loading, use
<a routerLink="/loader">Loader component</a>
<div class="progr-circ-page-info-card">
<cps-icon icon="info-circle" color="info" size="normal"></cps-icon>
<div>
<b>This is not a page loader!</b><br />
For a loading spinner, check out the
<a routerLink="/loader">Loader component</a>
</div>
</div>
<div class="progr-circ-group">
<cps-progress-circular
color="luxury"
diameter="120"
strokeWidth="8"></cps-progress-circular>
<cps-progress-circular diameter="60"></cps-progress-circular>
diameter="7.5rem"
strokeWidth="0.5rem"></cps-progress-circular>
<cps-progress-circular diameter="3.75rem"></cps-progress-circular>
<cps-progress-circular
color="surprise"
diameter="30"
strokeWidth="3"></cps-progress-circular>
diameter="1.875rem"
strokeWidth="0.1875rem"></cps-progress-circular>
<cps-progress-circular
color="prepared"
diameter="15"
strokeWidth="2"></cps-progress-circular>
diameter="0.9375rem"
strokeWidth="0.125rem"></cps-progress-circular>
</div>
</app-component-docs-viewer>
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
.progr-circ-group {
align-items: center;
gap: 48px;
gap: 3rem;
display: flex;
overflow: hidden;
}

.progr-circ-page-warning {
margin-bottom: 36px;
font-size: 20px;
.progr-circ-page-info-card {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.25rem;
margin-bottom: 2.25rem;
border-radius: 0.5rem;
border-left: 0.25rem solid var(--cps-color-info);
background: var(--cps-color-info-highlighten);
color: var(--cps-color-text-darkest);
font-size: 1rem;

a {
color: var(--cps-color-info-darken2);
font-weight: 600;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CpsProgressCircularComponent } from 'cps-ui-kit';
import { CpsIconComponent, CpsProgressCircularComponent } from 'cps-ui-kit';

Check warning on line 3 in projects/composition/src/app/pages/progress-circular-page/progress-circular-page.component.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
import { ComponentDocsViewerComponent } from '../../components/component-docs-viewer/component-docs-viewer.component';
import ComponentData from '../../api-data/cps-progress-circular.json';

@Component({
imports: [
CpsIconComponent,
CpsProgressCircularComponent,
ComponentDocsViewerComponent,
RouterModule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<cps-progress-circular
color="currentColor"
[diameter]="cvtIconSize"
strokeWidth="2">
strokeWidth="0.125rem">
</cps-progress-circular>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@
} @else {
<span class="cps-menu-item-spinner" role="status" aria-label="Loading">
<cps-progress-circular
[strokeWidth]="2"
strokeWidth="0.125rem"
[color]="compressed ? 'prepared-darken2' : 'calm'"
[diameter]="16">
diameter="1rem">
</cps-progress-circular>
</span>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<div
aria-hidden="true"
class="cps-progress-circular"
[style.width]="diameter"
[style.border]="strokeWidth + ' solid ' + color"></div>
[style.width]="cvtDiameter"
[style.border]="`${cvtStrokeWidth} solid ${cvtColor}`"></div>
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,33 @@ describe('CpsProgressCircularComponent', () => {
});

it('should have default values', () => {
expect(component.diameter).toBe('40px');
expect(component.strokeWidth).toBe('4px');
expect(component.diameter).toBe('2.5rem');
expect(component.strokeWidth).toBe('0.25rem');
expect(component.color).toBeTruthy();
});

it('should convert diameter on init', () => {
it('should convert numeric diameter on init', () => {
component.diameter = 50;
component.ngOnInit();
expect(component.diameter).toBe('50px');
expect(component.cvtDiameter).toBe('50px');
});

it('should keep diameter as string if already string', () => {
it('should keep string diameter as-is on init', () => {
component.diameter = '2rem';
component.ngOnInit();
expect(component.diameter).toBe('2rem');
expect(component.cvtDiameter).toBe('2rem');
});

it('should convert strokeWidth on init', () => {
it('should convert numeric strokeWidth on init', () => {
component.strokeWidth = 5;
component.ngOnInit();
expect(component.strokeWidth).toBe('5px');
expect(component.cvtStrokeWidth).toBe('5px');
});

it('should keep strokeWidth as string if already string', () => {
it('should keep string strokeWidth as-is on init', () => {
component.strokeWidth = '0.5rem';
component.ngOnInit();
expect(component.strokeWidth).toBe('0.5rem');
expect(component.cvtStrokeWidth).toBe('0.5rem');
});

it('should set custom color', () => {
Expand All @@ -61,4 +61,32 @@ describe('CpsProgressCircularComponent', () => {
);
expect(circle).toBeTruthy();
});

describe('Accessibility (aria-label)', () => {
it('should default aria-label to "Loading" when no label is provided', () => {
const host: HTMLElement = fixture.nativeElement;
expect(host.getAttribute('aria-label')).toBe('Loading');
});

it('should set aria-label from the ariaLabel input', () => {
fixture.componentRef.setInput('ariaLabel', 'Saving changes');
fixture.detectChanges();
const host: HTMLElement = fixture.nativeElement;
expect(host.getAttribute('aria-label')).toBe('Saving changes');
});

it('should update aria-label when ariaLabel input changes', () => {
fixture.componentRef.setInput('ariaLabel', 'Saving changes');
fixture.detectChanges();
fixture.componentRef.setInput('ariaLabel', 'Uploading file');
fixture.detectChanges();
const host: HTMLElement = fixture.nativeElement;
expect(host.getAttribute('aria-label')).toBe('Uploading file');
});

it('should have role="progressbar" on the host', () => {
const host: HTMLElement = fixture.nativeElement;
expect(host.getAttribute('role')).toBe('progressbar');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { DOCUMENT } from '@angular/common';
import { Component, Inject, Input, OnInit } from '@angular/core';
import {
AfterViewInit,
Component,
HostAttributeToken,
Input,
OnInit,
OnChanges,
ElementRef,
Renderer2,
inject,
type SimpleChanges
} from '@angular/core';
import { convertSize } from '../../utils/internal/size-utils';
import { getCSSColor } from '../../utils/colors-utils';

Expand All @@ -11,34 +22,92 @@ import { getCSSColor } from '../../utils/colors-utils';
imports: [],
selector: 'cps-progress-circular',
templateUrl: './cps-progress-circular.component.html',
styleUrls: ['./cps-progress-circular.component.scss']
styleUrls: ['./cps-progress-circular.component.scss'],
host: {
role: 'progressbar'
}
})
export class CpsProgressCircularComponent implements OnInit {
export class CpsProgressCircularComponent
implements OnInit, OnChanges, AfterViewInit
{
/**
* Diameter of the progress bar, of type number denoting pixels or string.
* @group Props
*/
@Input() diameter: number | string = 40;
@Input() diameter: number | string = '2.5rem';

/**
* Thickness of the progress bar, of type number denoting pixels or string.
* @group Props
*/
@Input() strokeWidth: number | string = 4;
@Input() strokeWidth: number | string = '0.25rem';

/**
* Color of the progress bar.
* @group Props
*/
@Input() color = 'calm';

// eslint-disable-next-line no-useless-constructor
constructor(@Inject(DOCUMENT) private document: Document) {}
/**
* Accessible label announced by screen readers to describe what is loading.
* Falls back to "Loading" when empty value is provided.
* @group Props
* @default Loading
*/
@Input() ariaLabel = '';

private readonly _elementRef = inject(ElementRef);
private readonly _document = inject(DOCUMENT);
private readonly _renderer = inject(Renderer2);
private readonly _staticAriaLabel = inject(
new HostAttributeToken('aria-label'),
{ optional: true }
);

cvtDiameter = '';
cvtStrokeWidth = '';
cvtColor = '';

ngOnInit(): void {
this.diameter = convertSize(this.diameter);
this.strokeWidth = convertSize(this.strokeWidth);
this.cvtDiameter = convertSize(this.diameter);
this.cvtStrokeWidth = convertSize(this.strokeWidth);
this.cvtColor = getCSSColor(this.color, this._document);
this._applyAriaLabel();
}

ngOnChanges(changes: SimpleChanges): void {
if (changes.ariaLabel) {
this._applyAriaLabel();
}
if (changes.diameter) {
this.cvtDiameter = convertSize(this.diameter);
}
if (changes.strokeWidth) {
this.cvtStrokeWidth = convertSize(this.strokeWidth);
}
if (changes.color) {
this.cvtColor = getCSSColor(this.color, this._document);
}
}

ngAfterViewInit(): void {
if (!this._elementRef.nativeElement.getAttribute('aria-label')) {
this._renderer.setAttribute(
this._elementRef.nativeElement,
'aria-label',
'Loading'
);
}
}

this.color = getCSSColor(this.color, this.document);
private _applyAriaLabel(): void {
const label = this.ariaLabel || this._staticAriaLabel;
if (label) {
this._renderer.setAttribute(
this._elementRef.nativeElement,
'aria-label',
label
);
}
}
}
Loading