Files
ISA-Frontend/libs/ui/progress-bar/README.md
Lorenz Hilpert 2b5da00249 feat(checkout): add reward order confirmation feature with schema migrations
- 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
2025-10-21 14:28:52 +02:00

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.