From 5165fd1433a4b3b0545f67fa8d15b71dac5df6f4 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Tue, 23 Jul 2024 09:53:25 -0400 Subject: [PATCH] ACS-8434: progress component (#9960) --- docs/README.md | 9 +- docs/core/components/progress.component.md | 1 + .../src/lib/progress/progress.component.html | 23 ++++ .../src/lib/progress/progress.component.md | 79 ++++++++++++++ .../lib/progress/progress.component.spec.ts | 103 ++++++++++++++++++ .../src/lib/progress/progress.component.ts | 87 +++++++++++++++ lib/core/src/public-api.ts | 1 + 7 files changed, 299 insertions(+), 4 deletions(-) create mode 120000 docs/core/components/progress.component.md create mode 100644 lib/core/src/lib/progress/progress.component.html create mode 100644 lib/core/src/lib/progress/progress.component.md create mode 100644 lib/core/src/lib/progress/progress.component.spec.ts create mode 100644 lib/core/src/lib/progress/progress.component.ts diff --git a/docs/README.md b/docs/README.md index 9828579e23..a3fb7ce0d4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -81,10 +81,11 @@ for more information about installing and using the source code. A collection of Angular components for generic use. -| Name | Description | -|-----------------------------------------------|------------------------------| -| [Avatar](core/components/avatar.component.md) | Displays user avatars. | -| [Button](core/components/button.component.md) | A standard button component. | +| Name | Description | +|---------------------------------------------------|------------------------------| +| [Avatar](core/components/avatar.component.md) | Displays user avatars. | +| [Button](core/components/button.component.md) | A standard button component. | +| [Progress](core/components/progress.component.md) | A progress bar component. | ### Components diff --git a/docs/core/components/progress.component.md b/docs/core/components/progress.component.md new file mode 120000 index 0000000000..db78ceb2dc --- /dev/null +++ b/docs/core/components/progress.component.md @@ -0,0 +1 @@ +../../../lib/core/src/lib/progress/progress.component.md \ No newline at end of file diff --git a/lib/core/src/lib/progress/progress.component.html b/lib/core/src/lib/progress/progress.component.html new file mode 100644 index 0000000000..89de51e3d6 --- /dev/null +++ b/lib/core/src/lib/progress/progress.component.html @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/lib/core/src/lib/progress/progress.component.md b/lib/core/src/lib/progress/progress.component.md new file mode 100644 index 0000000000..333cefcdbd --- /dev/null +++ b/lib/core/src/lib/progress/progress.component.md @@ -0,0 +1,79 @@ +# Progress Component + +`standalone`, `component` + +The Progress component is a simple component that can be used to display progress bars. + +## Usage + +```html + +``` + +### Variant + +The progress bar supports the following variants: + +- bar (default) +- spinner + +```html + + +``` + +### Mode + +The progress bar supports the following modes: + +- For progress spinner, the mode can be either `indeterminate` or `determinate`. +- For progress bar, the mode can be either `determinate`, `indeterminate`, `buffer`, or `query`. + +> For the spinner variant, setting the mode to unsupported values will default to `indeterminate`. + +Example: + +```html + + +``` + +### Color + +The progress bar supports the following colors: + +- default +- primary +- accent +- warn + +```html + + + +``` + +## API + +Import the following standalone components: + +```typescript +import { ProgressComponent } from '@alfresco/adf-core'; +``` + +## Properties + +| Name | Type | Default | Description | +|-----------|-----------------|-----------------|-----------------------------------------------------------| +| `variant` | ProgressVariant | `bar` | The progress bar variant. | +| `value` | number | | The value of the progress bar. | +| `color` | ProgressColor | | The color of the progress bar. | +| `mode` | ProgressMode | `indeterminate` | The mode of the progress bar. | +| `testId` | string | | The button test id (uses `data-automation-id` attribute). | + +### Accessibility + +The button component has been designed to be accessible. The following attributes are available: + +- `ariaLabel`: The button aria label. +- `ariaHidden`: Whether the button should be hidden from the accessibility tree. diff --git a/lib/core/src/lib/progress/progress.component.spec.ts b/lib/core/src/lib/progress/progress.component.spec.ts new file mode 100644 index 0000000000..f65046b7e1 --- /dev/null +++ b/lib/core/src/lib/progress/progress.component.spec.ts @@ -0,0 +1,103 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatProgressBarHarness } from '@angular/material/progress-bar/testing'; +import { ProgressComponent } from './progress.component'; + +describe('ProgressComponent', () => { + let component: ProgressComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MatProgressBarModule, MatProgressSpinnerModule, NoopAnimationsModule, ProgressComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(ProgressComponent); + loader = TestbedHarnessEnvironment.loader(fixture); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should default to bar variant and indeterminate mode', () => { + expect(component.variant).toBe('bar'); + expect(component.mode).toBe('indeterminate'); + }); + + it('should change mode to determinate when value is set', () => { + component.value = 50; + expect(component.mode).toBe('determinate'); + }); + + it('should not change mode if variant is spinner and an invalid mode is set', () => { + component.variant = 'spinner'; + component.mode = 'query'; // Invalid for spinner + expect(component.mode).toBe('indeterminate'); + }); + + it('should accept buffer mode when variant is bar', () => { + component.variant = 'bar'; + component.mode = 'buffer'; + expect(component.mode).toBe('buffer'); + }); + + it('should ignore setting value to undefined', () => { + component.value = 50; + component.value = undefined; + expect(component.value).toBeUndefined(); + expect(component.mode).toBe('determinate'); // Mode remains unchanged + }); + + it('should apply ariaLabel if provided', async () => { + const testLabel = 'Progress Label'; + component.ariaLabel = testLabel; + fixture.detectChanges(); + + const progress = await loader.getHarness(MatProgressBarHarness); + const host = await progress.host(); + expect(await host.getAttribute('aria-label')).toBe(testLabel); + }); + + it('should hide from accessibility tree if ariaHidden is true', async () => { + component.ariaHidden = true; + fixture.detectChanges(); + + const progress = await loader.getHarness(MatProgressBarHarness); + const host = await progress.host(); + + expect(await host.getAttribute('aria-hidden')).toBe('true'); + }); + + it('should set testId if provided', () => { + const testId = 'progress-test-id'; + component.testId = testId; + fixture.detectChanges(); + const progressBarElement = fixture.nativeElement.querySelector(`[data-automation-id="${testId}"]`); + expect(progressBarElement).not.toBeNull(); + }); +}); diff --git a/lib/core/src/lib/progress/progress.component.ts b/lib/core/src/lib/progress/progress.component.ts new file mode 100644 index 0000000000..4aee682118 --- /dev/null +++ b/lib/core/src/lib/progress/progress.component.ts @@ -0,0 +1,87 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Input, ViewEncapsulation } from '@angular/core'; +import { MatProgressBarModule, ProgressBarMode } from '@angular/material/progress-bar'; +import { MatProgressSpinnerModule, ProgressSpinnerMode } from '@angular/material/progress-spinner'; +import { CommonModule } from '@angular/common'; +import { ThemePalette } from '@angular/material/core'; + +export type ProgressVariant = 'bar' | 'spinner' | undefined; +export type ProgressMode = ProgressBarMode | ProgressSpinnerMode; +export type ProgressColor = ThemePalette; + +@Component({ + selector: 'adf-progress', + standalone: true, + imports: [CommonModule, MatProgressBarModule, MatProgressSpinnerModule], + templateUrl: './progress.component.html', + encapsulation: ViewEncapsulation.None +}) +export class ProgressComponent { + private _mode: ProgressMode = 'indeterminate'; + private _value?: number; + + @Input() variant: ProgressVariant = 'bar'; + @Input() color?: ProgressColor; + @Input() ariaLabel?: string; + @Input() ariaHidden?: boolean; + @Input() testId?: string; + + /** + * The value of the progress bar or spinner. + * Changes the mode to `determinate` if a value is provided. + * + * @returns The progress value + */ + get value(): number | undefined { + return this._value; + } + + @Input() + set value(value: number | undefined) { + if (value !== undefined) { + this._mode = 'determinate'; + } + this._value = value; + } + + /** + * The progress bar display mode. Defaults to `indeterminate`. + * + * For progress spinner, the mode can be either `indeterminate` or `determinate`. + * For progress bar, the mode can be either `determinate`, `indeterminate`, `buffer`, or `query`. + * + * @returns The progress mode + */ + get mode(): ProgressMode { + return this._mode; + } + + @Input() + set mode(value: ProgressMode) { + if (this.variant === 'spinner') { + if (value === 'indeterminate' || value === 'determinate') { + this._mode = value; + } else { + this._mode = 'indeterminate'; + } + } else { + this._mode = value; + } + } +} diff --git a/lib/core/src/public-api.ts b/lib/core/src/public-api.ts index 84ce1ca93f..6c2bf9d0c8 100644 --- a/lib/core/src/public-api.ts +++ b/lib/core/src/public-api.ts @@ -18,6 +18,7 @@ export * from './lib/about/index'; export * from './lib/avatar/avatar.component'; export * from './lib/button/button.component'; +export * from './lib/progress/progress.component'; export * from './lib/viewer/index'; export * from './lib/toolbar/index'; export * from './lib/pagination/index';