Progress Bars

The <smart-progress-bars> component renders either a list of progress steps (with statuses complete, current, upcoming) or a percentage-based bar with optional column labels. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract ProgressBarsBaseComponent defines the shared API — options (IProgressBarsOptions), cssClass (alias class), and the stepClick output. ProgressBarsStandardComponent is a barebones placeholder using native HTML. ProgressBarsComponent is the public wrapper that renders ProgressBarsStandardComponent by default and accepts a custom replacement via PROGRESS_BARS_STANDARD_COMPONENT_TOKEN.


Usage

<smart-progress-bars [options]="options" (stepClick)="onStepClick($event)" />

Components

ProgressBarsComponent (<smart-progress-bars>)

Main wrapper. Delegates to ProgressBarsStandardComponent by default. When PROGRESS_BARS_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. Re-emits stepClick.

ProgressBarsStandardComponent (<smart-progress-bars-standard>)

Barebones placeholder. Two rendering modes selected by options.layout:

Step mode (default; layouts simple, panels, bullets, panels-with-border, circles, bullets-and-text, circles-with-text): Renders <nav class="progress-bars"> (with aria-label from options.ariaLabel or default "Progress") containing an <ol> of steps. The data-layout attribute reflects options.layout so each visual variant can be styled. Each step renders as <a class="progress-bars-step-link"> (when href provided) or <button class="progress-bars-step-button"> (otherwise emits stepClick). Steps get current class and aria-current="step" when status === 'current'. The data-status attribute on the <li> reflects status (defaults to 'upcoming'). Supports iconTpl, index (e.g. "02"), name, and description.

Bar mode (layout progress-bar): Renders <div class="progress-bars-bar-wrapper"> with optional <h4 class="sr-only"> (from srOnlyTitle), optional <p class="progress-bars-title"> (from title), a track + <div class="progress-bars-fill" role="progressbar"> (width = clamped value 0–100), and optional <div class="progress-bars-columns"> row of column labels (each with active class when column.active === true).

ProgressBarsPresetComponent (<smart-progress-bars-preset>)

Fully-styled variation that extends ProgressBarsBaseComponent and is a drop-in replacement for ProgressBarsStandardComponent. Register it via PROGRESS_BARS_STANDARD_COMPONENT_TOKEN to restyle every <smart-progress-bars>, or use the <smart-progress-bars-preset> selector directly. It renders both modes off the shared IProgressBarsOptions API:

  • Percentage bar (layout: 'progress-bar'): a rounded track + animated bg-blue-600 fill sized to the clamped value (0–100). Shows a title header with a value% label when title is set, an sr-only <h4> from srOnlyTitle, and a grid of column captions from columns (active columns rendered semibold).
  • Stepper (all other layouts): a <nav>/<ol> of steps. Markers adapt to the layout — numbered/check circles for circles, circles-with-text, panels, panels-with-border; small dots for bullets, bullets-and-text; a colored top border (no marker) for simple. bullets-and-text and circles-with-text lay out vertically; the other step layouts lay out horizontally, with connector lines between markers for circles/bullets. Completed circle steps show a check SVG; current/upcoming circle steps show step.index (falling back to the 1-based position). Each step renders an <a> when step.href is set, otherwise a <button> that emits stepClick. Names/markers tint by status (complete/current/upcoming).

All classes are smart:-prefixed Tailwind with explicit dark: variants. The class recipes live in preset/preset-classes.util.ts (kept out of the public barrel; every export is ProgressBars-prefixed).

Because ProgressBarsComponent renders injected components via NgComponentOutlet (which passes inputs by canonical name), ProgressBarsPresetComponent overrides cssClass as input<string>('') without the class alias. Bind it as [cssClass] when using the <smart-progress-bars-preset> selector directly, or just pass class on <smart-progress-bars> (the wrapper forwards it).

ProgressBarsBaseComponent (abstract)

Abstract base directive. Exposes:

  • options: InputSignal<IProgressBarsOptions | undefined>
  • cssClass: InputSignal<string> (alias class)
  • stepClick: OutputEmitterRef<IProgressStepClick>

IProgressStepClick = { stepId: string }.

API

Inputs

InputTypeDefaultDescription
optionsInputSignal<IProgressBarsOptions | undefined>-Progress configuration
classInputSignal<string>''External CSS classes (alias for cssClass)

Outputs

OutputTypeDescription
stepClickOutputEmitterRef<IProgressStepClick>Emitted when a button-type step is clicked

Extending the base class

import {
  ChangeDetectionStrategy,
  Component,
  computed,
  input,
  ViewEncapsulation,
} from '@angular/core';

import {
  IProgressBarsOptions,
  PROGRESS_BARS_STANDARD_COMPONENT_TOKEN,
  ProgressBarsBaseComponent,
  ProgressBarsComponent,
} from '@smartsoft001/angular';

@Component({
  selector: 'docs-custom-progress-bars',
  template: `
    <div [class]="containerClasses()" [attr.aria-label]="options()?.ariaLabel">
      @if (options()?.title) {
        <p class="docs-progress-bars__title">{{ options()?.title }}</p>
      }

      @if (options()?.value !== undefined) {
        <div class="docs-progress-bars__track">
          <div
            class="docs-progress-bars__value"
            [style.width.%]="options()?.value"
          ></div>
        </div>
      }

      <ol class="docs-progress-bars__steps">
        @for (step of steps(); track step.id) {
          <li>
            <button
              type="button"
              class="docs-progress-bars__step"
              [class]="
                'docs-progress-bars__step--' + (step.status ?? 'upcoming')
              "
              [attr.aria-current]="step.status === 'current' ? 'step' : null"
              (click)="stepClick.emit({ stepId: step.id })"
            >
              <span class="docs-progress-bars__index">{{ step.index }}</span>
              <span class="docs-progress-bars__name">{{ step.name }}</span>
            </button>
          </li>
        }
      </ol>
    </div>
  `,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomProgressBarsComponent extends ProgressBarsBaseComponent {
  // NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
  override cssClass = input<string>('');

  readonly steps = computed(() => this.options()?.steps ?? []);

  readonly containerClasses = computed(() => {
    const classes = ['docs-progress-bars'];
    const extra = this.cssClass();
    if (extra) classes.push(extra);
    return classes.join(' ');
  });
}

@Component({
  selector: 'docs-progress-bars-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [ProgressBarsComponent],
  // The token swaps the standard progress bars for the custom one everywhere
  // below this component, so consumers keep writing `<smart-progress-bars>`.
  providers: [
    {
      provide: PROGRESS_BARS_STANDARD_COMPONENT_TOKEN,
      useValue: CustomProgressBarsComponent,
    },
  ],
  template: ` <smart-progress-bars [options]="options" /> `,
})
export class ProgressBarsCustomExampleComponent {
  readonly options: IProgressBarsOptions = {
    layout: 'progress-bar',
    title: 'Uploading files',
    ariaLabel: 'Upload progress',
    value: 50,
    steps: [
      { id: 'account', name: 'Account', index: '1', status: 'complete' },
      { id: 'profile', name: 'Profile', index: '2', status: 'current' },
      { id: 'review', name: 'Review', index: '3', status: 'upcoming' },
    ],
  };
}

Source

The component lives in packages/shared/angular/src/lib/components/progress-bars and is documented for Claude Code by the angular-components-progress-bars skill.