# @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.
