mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
- Add new reward-order-confirmation feature library with components and store - Implement checkout completion orchestrator service for order finalization - Migrate checkout/oms/crm models to Zod schemas for better type safety - Add order creation facade and display order schemas - Update shopping cart facade with order completion flow - Add comprehensive tests for shopping cart facade - Update routing to include order confirmation page
513 lines
12 KiB
Markdown
513 lines
12 KiB
Markdown
# @isa/ui/progress-bar
|
|
|
|
A lightweight Angular progress bar component supporting both determinate and indeterminate modes.
|
|
|
|
## Overview
|
|
|
|
The Progress Bar library provides a simple, performant component for visualizing progress or loading states. It supports two modes: determinate (with specific progress value) and indeterminate (continuous loading animation), making it suitable for various use cases from file uploads to background processing indicators.
|
|
|
|
## Table of Contents
|
|
|
|
- [Features](#features)
|
|
- [Quick Start](#quick-start)
|
|
- [API Reference](#api-reference)
|
|
- [Usage Examples](#usage-examples)
|
|
- [Styling and Customization](#styling-and-customization)
|
|
- [Testing](#testing)
|
|
- [Dependencies](#dependencies)
|
|
|
|
## Features
|
|
|
|
- **Two display modes** - Determinate (percentage-based) and indeterminate (continuous animation)
|
|
- **Customizable progress** - Configurable value and max value for flexible percentage calculations
|
|
- **Standalone component** - Fully standalone, no module imports required
|
|
- **Signal-based inputs** - Reactive updates using Angular signals
|
|
- **Computed width** - Automatically calculates bar width based on value/maxValue ratio
|
|
- **OnPush change detection** - Optimized for performance
|
|
- **ViewEncapsulation.None** - Allows global styling customization
|
|
|
|
## Quick Start
|
|
|
|
### 1. Import the Component
|
|
|
|
```typescript
|
|
import { Component, signal } from '@angular/core';
|
|
import { ProgressBarComponent, ProgressBarMode } from '@isa/ui/progress-bar';
|
|
|
|
@Component({
|
|
selector: 'app-upload',
|
|
standalone: true,
|
|
imports: [ProgressBarComponent],
|
|
template: '...'
|
|
})
|
|
export class UploadComponent {
|
|
uploadProgress = signal(0);
|
|
}
|
|
```
|
|
|
|
### 2. Determinate Mode (Default)
|
|
|
|
```html
|
|
<!-- Show specific progress percentage -->
|
|
<ui-progress-bar
|
|
[value]="uploadProgress()"
|
|
[maxValue]="100"
|
|
></ui-progress-bar>
|
|
```
|
|
|
|
### 3. Indeterminate Mode
|
|
|
|
```html
|
|
<!-- Show continuous loading animation -->
|
|
<ui-progress-bar
|
|
[mode]="'indeterminate'"
|
|
></ui-progress-bar>
|
|
```
|
|
|
|
### 4. Custom Value Range
|
|
|
|
```html
|
|
<!-- Progress out of custom max value -->
|
|
<ui-progress-bar
|
|
[value]="processedItems()"
|
|
[maxValue]="totalItems()"
|
|
></ui-progress-bar>
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### ProgressBarComponent
|
|
|
|
Standalone component that displays a visual progress indicator.
|
|
|
|
**Selector:** `ui-progress-bar`
|
|
|
|
#### Inputs
|
|
|
|
| Input | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `mode` | `ProgressBarMode` | `'determinate'` | Display mode: `'determinate'` or `'indeterminate'` |
|
|
| `value` | `number` | `50` | Current progress value (used in determinate mode) |
|
|
| `maxValue` | `number` | `100` | Maximum value for percentage calculation |
|
|
|
|
#### ProgressBarMode Type
|
|
|
|
```typescript
|
|
export const ProgressBarMode = {
|
|
Determinate: 'determinate', // Percentage-based progress
|
|
Indeterminate: 'indeterminate' // Continuous loading animation
|
|
} as const;
|
|
|
|
export type ProgressBarMode =
|
|
(typeof ProgressBarMode)[keyof typeof ProgressBarMode];
|
|
```
|
|
|
|
#### Computed Properties
|
|
|
|
##### `modeClass(): string`
|
|
|
|
Returns the CSS class for the current mode:
|
|
- `'ui-progress-bar__determinate'` - For determinate mode
|
|
- `'ui-progress-bar__indeterminate'` - For indeterminate mode
|
|
|
|
##### `width(): string`
|
|
|
|
Calculates the progress bar width:
|
|
- **Indeterminate mode**: Returns `'100%'`
|
|
- **Determinate mode**: Returns `(value / maxValue * 100)%`
|
|
|
|
#### Host Classes
|
|
|
|
- `ui-progress-bar` - Always applied
|
|
- `ui-progress-bar__determinate` or `ui-progress-bar__indeterminate` - Based on mode
|
|
|
|
#### Template Structure
|
|
|
|
```html
|
|
<div class="ui-progress-bar__bar" [style.width]="width()"></div>
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### File Upload Progress
|
|
|
|
```typescript
|
|
import { Component, signal } from '@angular/core';
|
|
import { ProgressBarComponent } from '@isa/ui/progress-bar';
|
|
|
|
@Component({
|
|
selector: 'app-file-upload',
|
|
standalone: true,
|
|
imports: [ProgressBarComponent],
|
|
template: `
|
|
<div class="upload-container">
|
|
<h3>Uploading {{ fileName() }}</h3>
|
|
|
|
<ui-progress-bar
|
|
[value]="uploadProgress()"
|
|
[maxValue]="100"
|
|
data-what="upload-progress-bar"
|
|
></ui-progress-bar>
|
|
|
|
<p>{{ uploadProgress() }}% complete</p>
|
|
</div>
|
|
`
|
|
})
|
|
export class FileUploadComponent {
|
|
fileName = signal('document.pdf');
|
|
uploadProgress = signal(0);
|
|
|
|
async uploadFile(file: File): Promise<void> {
|
|
this.fileName.set(file.name);
|
|
this.uploadProgress.set(0);
|
|
|
|
// Simulate upload progress
|
|
const interval = setInterval(() => {
|
|
this.uploadProgress.update(p => {
|
|
if (p >= 100) {
|
|
clearInterval(interval);
|
|
return 100;
|
|
}
|
|
return p + 10;
|
|
});
|
|
}, 500);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Loading Indicator
|
|
|
|
```typescript
|
|
import { Component, signal } from '@angular/core';
|
|
import { ProgressBarComponent, ProgressBarMode } from '@isa/ui/progress-bar';
|
|
|
|
@Component({
|
|
selector: 'app-data-loader',
|
|
standalone: true,
|
|
imports: [ProgressBarComponent],
|
|
template: `
|
|
@if (isLoading()) {
|
|
<ui-progress-bar
|
|
[mode]="'indeterminate'"
|
|
data-what="loading-indicator"
|
|
></ui-progress-bar>
|
|
}
|
|
|
|
<div class="content">
|
|
@for (item of data(); track item.id) {
|
|
<div>{{ item.name }}</div>
|
|
}
|
|
</div>
|
|
`
|
|
})
|
|
export class DataLoaderComponent {
|
|
isLoading = signal(true);
|
|
data = signal<DataItem[]>([]);
|
|
|
|
async loadData(): Promise<void> {
|
|
this.isLoading.set(true);
|
|
try {
|
|
const result = await this.dataService.fetch();
|
|
this.data.set(result);
|
|
} finally {
|
|
this.isLoading.set(false);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Batch Processing Progress
|
|
|
|
```typescript
|
|
import { Component, computed, signal } from '@angular/core';
|
|
import { ProgressBarComponent } from '@isa/ui/progress-bar';
|
|
|
|
@Component({
|
|
selector: 'app-batch-processor',
|
|
standalone: true,
|
|
imports: [ProgressBarComponent],
|
|
template: `
|
|
<div class="batch-progress">
|
|
<h3>Processing Items</h3>
|
|
|
|
<ui-progress-bar
|
|
[value]="processedCount()"
|
|
[maxValue]="totalCount()"
|
|
></ui-progress-bar>
|
|
|
|
<p>
|
|
{{ processedCount() }} of {{ totalCount() }} items processed
|
|
({{ percentComplete() }}%)
|
|
</p>
|
|
</div>
|
|
`
|
|
})
|
|
export class BatchProcessorComponent {
|
|
processedCount = signal(0);
|
|
totalCount = signal(100);
|
|
|
|
percentComplete = computed(() => {
|
|
const total = this.totalCount();
|
|
if (total === 0) return 0;
|
|
return Math.round((this.processedCount() / total) * 100);
|
|
});
|
|
|
|
async processItems(items: Item[]): Promise<void> {
|
|
this.totalCount.set(items.length);
|
|
this.processedCount.set(0);
|
|
|
|
for (const item of items) {
|
|
await this.processItem(item);
|
|
this.processedCount.update(c => c + 1);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Multi-Step Progress
|
|
|
|
```typescript
|
|
import { Component, computed, signal } from '@angular/core';
|
|
import { ProgressBarComponent } from '@isa/ui/progress-bar';
|
|
|
|
@Component({
|
|
selector: 'app-wizard',
|
|
standalone: true,
|
|
imports: [ProgressBarComponent],
|
|
template: `
|
|
<div class="wizard">
|
|
<ui-progress-bar
|
|
[value]="currentStep()"
|
|
[maxValue]="totalSteps()"
|
|
></ui-progress-bar>
|
|
|
|
<div class="step-indicator">
|
|
Step {{ currentStep() }} of {{ totalSteps() }}
|
|
</div>
|
|
|
|
<div class="step-content">
|
|
@switch (currentStep()) {
|
|
@case (1) {
|
|
<div>Step 1: Basic Information</div>
|
|
}
|
|
@case (2) {
|
|
<div>Step 2: Address Details</div>
|
|
}
|
|
@case (3) {
|
|
<div>Step 3: Payment Method</div>
|
|
}
|
|
@case (4) {
|
|
<div>Step 4: Review & Confirm</div>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button
|
|
[disabled]="currentStep() === 1"
|
|
(click)="previousStep()"
|
|
>
|
|
Previous
|
|
</button>
|
|
<button
|
|
[disabled]="currentStep() === totalSteps()"
|
|
(click)="nextStep()"
|
|
>
|
|
Next
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`
|
|
})
|
|
export class WizardComponent {
|
|
currentStep = signal(1);
|
|
totalSteps = signal(4);
|
|
|
|
nextStep(): void {
|
|
this.currentStep.update(s => Math.min(s + 1, this.totalSteps()));
|
|
}
|
|
|
|
previousStep(): void {
|
|
this.currentStep.update(s => Math.max(s - 1, 1));
|
|
}
|
|
}
|
|
```
|
|
|
|
## Styling and Customization
|
|
|
|
### Default Classes
|
|
|
|
```html
|
|
<!-- Determinate mode -->
|
|
<ui-progress-bar class="ui-progress-bar ui-progress-bar__determinate">
|
|
<div class="ui-progress-bar__bar" style="width: 50%;"></div>
|
|
</ui-progress-bar>
|
|
|
|
<!-- Indeterminate mode -->
|
|
<ui-progress-bar class="ui-progress-bar ui-progress-bar__indeterminate">
|
|
<div class="ui-progress-bar__bar" style="width: 100%;"></div>
|
|
</ui-progress-bar>
|
|
```
|
|
|
|
### Custom Styling
|
|
|
|
```scss
|
|
// Base progress bar container
|
|
.ui-progress-bar {
|
|
width: 100%;
|
|
height: 4px;
|
|
background-color: #e0e0e0;
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
// The moving/filling bar
|
|
.ui-progress-bar__bar {
|
|
height: 100%;
|
|
background-color: #2196f3;
|
|
transition: width 0.3s ease-in-out;
|
|
}
|
|
|
|
// Determinate mode specific styles
|
|
.ui-progress-bar__determinate .ui-progress-bar__bar {
|
|
background: linear-gradient(90deg, #1976d2 0%, #2196f3 100%);
|
|
}
|
|
|
|
// Indeterminate mode animation
|
|
.ui-progress-bar__indeterminate .ui-progress-bar__bar {
|
|
animation: indeterminate-progress 2s linear infinite;
|
|
background: linear-gradient(
|
|
90deg,
|
|
transparent 0%,
|
|
#2196f3 50%,
|
|
transparent 100%
|
|
);
|
|
}
|
|
|
|
@keyframes indeterminate-progress {
|
|
0% {
|
|
transform: translateX(-100%);
|
|
}
|
|
100% {
|
|
transform: translateX(100%);
|
|
}
|
|
}
|
|
|
|
// Color variants
|
|
.ui-progress-bar--success .ui-progress-bar__bar {
|
|
background-color: #4caf50;
|
|
}
|
|
|
|
.ui-progress-bar--warning .ui-progress-bar__bar {
|
|
background-color: #ff9800;
|
|
}
|
|
|
|
.ui-progress-bar--error .ui-progress-bar__bar {
|
|
background-color: #f44336;
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
The library uses **Jest** for testing.
|
|
|
|
### Running Tests
|
|
|
|
```bash
|
|
# Run tests for progress-bar
|
|
npx nx test ui-progress-bar --skip-nx-cache
|
|
|
|
# Run tests with coverage
|
|
npx nx test ui-progress-bar --code-coverage --skip-nx-cache
|
|
|
|
# Run tests in watch mode
|
|
npx nx test ui-progress-bar --watch
|
|
```
|
|
|
|
### Test Coverage
|
|
|
|
Tests should cover:
|
|
|
|
- **Mode switching** - Determinate and indeterminate modes
|
|
- **Width calculation** - Correct percentage calculation from value/maxValue
|
|
- **Boundary conditions** - 0%, 100%, values exceeding maxValue
|
|
- **Signal reactivity** - Updates when value/maxValue change
|
|
- **Class application** - Correct mode classes applied
|
|
|
|
## Dependencies
|
|
|
|
### Required Libraries
|
|
|
|
- `@angular/core` - Angular framework (v20.1.2)
|
|
|
|
### Path Alias
|
|
|
|
Import from: `@isa/ui/progress-bar`
|
|
|
|
### Peer Dependencies
|
|
|
|
- Angular 20.1.2 or higher
|
|
- TypeScript 5.8.3 or higher
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Appropriate Mode
|
|
|
|
Choose the mode based on whether progress is measurable:
|
|
|
|
```html
|
|
<!-- Measurable progress - use determinate -->
|
|
<ui-progress-bar [value]="uploadedBytes" [maxValue]="totalBytes"></ui-progress-bar>
|
|
|
|
<!-- Unknown duration - use indeterminate -->
|
|
<ui-progress-bar [mode]="'indeterminate'"></ui-progress-bar>
|
|
```
|
|
|
|
### 2. Provide Context
|
|
|
|
Always accompany the progress bar with text:
|
|
|
|
```html
|
|
<div class="progress-container">
|
|
<label>Uploading...</label>
|
|
<ui-progress-bar [value]="progress" [maxValue]="100"></ui-progress-bar>
|
|
<span>{{ progress }}%</span>
|
|
</div>
|
|
```
|
|
|
|
### 3. Handle Edge Cases
|
|
|
|
Validate values to prevent division by zero or negative percentages:
|
|
|
|
```typescript
|
|
percentComplete = computed(() => {
|
|
const max = this.maxValue();
|
|
const val = this.value();
|
|
|
|
if (max === 0) return 0;
|
|
return Math.min(Math.max((val / max) * 100, 0), 100);
|
|
});
|
|
```
|
|
|
|
### 4. Smooth Updates
|
|
|
|
For smoother visual transitions, update progress in reasonable increments:
|
|
|
|
```typescript
|
|
// Good: Update every 5-10%
|
|
updateProgress(newValue: number) {
|
|
if (newValue - this.progress() >= 5) {
|
|
this.progress.set(newValue);
|
|
}
|
|
}
|
|
|
|
// Bad: Update every tiny increment
|
|
updateProgress(newValue: number) {
|
|
this.progress.set(newValue); // Updates too frequently
|
|
}
|
|
```
|
|
|
|
## License
|
|
|
|
Internal ISA Frontend library - not for external distribution.
|