npm package: @solopx/spx-ui-kit
Description: Angular component library with layout container, stepper, and theming support
Author: cromozooom
Latest version: 0.2.1

# spx-ui-kit

A reusable Angular component library for consistent, accessible layouts.

## Features

- Layout Container with fixed header and scrollable content
- Theming and responsive design
- Accessibility support
- Storybook documentation

## Usage

Import the `SpxLayoutContainerComponent` and use the `<spx-layout-container>` selector in your templates.

## Development

- Run `npm start` to launch Storybook
- Run `npm test` to execute unit tests

See CONTRIBUTING.md for more details.

# Stepper Documentation

## Overview

The **SpxStepper** is a powerful CDK-style Angular component for creating multi-step workflows with advanced validation, navigation control, and responsive design. It follows Angular's modern architecture using signals and standalone components.

## Installation

```bash
npm install @solopx/spx-ui-kit
```

## Basic Usage

### Simple Stepper

```typescript
import { Component, ViewChild } from '@angular/core';
import { SpxStepperComponent, SpxStepComponent, SpxStepPanelComponent } from '@solopx/spx-ui-kit';

@Component({
  selector: 'app-my-stepper',
  imports: [SpxStepperComponent, SpxStepComponent, SpxStepPanelComponent],
  template: `
    <spx-stepper #stepper>
      <spx-step value="step1" label="Basic Information">
        <spx-step-panel value="step1">
          <h3>Step 1: Enter your details</h3>
          <p>Content for step 1...</p>
          <button (click)="stepper.validateAndNext()">Next</button>
        </spx-step-panel>
      </spx-step>

      <spx-step value="step2" label="Confirmation">
        <spx-step-panel value="step2">
          <h3>Step 2: Confirm</h3>
          <p>Content for step 2...</p>
          <button (click)="stepper.validateAndPrevious()">Previous</button>
        </spx-step-panel>
      </spx-step>
    </spx-stepper>
  `,
})
export class MyStepperComponent {
  @ViewChild('stepper') stepper!: SpxStepperComponent;
}
```

## Components

### SpxStepperComponent

Main container component that manages step navigation and validation.

#### Inputs

| Input               | Type      | Default | Description                                             |
| ------------------- | --------- | ------- | ------------------------------------------------------- |
| `activeStep`        | `string`  | `''`    | Initial active step value                               |
| `linear`            | `boolean` | `false` | Enable linear mode (users must complete steps in order) |
| `allowPreviewSteps` | `boolean` | `false` | Allow clicking step headers to preview future steps     |

#### Outputs

| Output             | Type                    | Description                                          |
| ------------------ | ----------------------- | ---------------------------------------------------- |
| `activeStepChange` | `string`                | Emits when active step changes                       |
| `stepChange`       | `StepChangeEvent`       | Emits detailed step change information               |
| `beforeStepChange` | `BeforeStepChangeEvent` | Emits before step change (can be cancelled)          |
| `validateStep`     | `ValidateStepEvent`     | Emits for step validation with prevention capability |

### SpxStepComponent

Defines individual step configuration.

#### Inputs

| Input       | Type      | Default  | Description                    |
| ----------- | --------- | -------- | ------------------------------ |
| `value`     | `string`  | Required | Unique identifier for the step |
| `label`     | `string`  | `''`     | Display label for step header  |
| `completed` | `boolean` | `false`  | Mark step as completed         |
| `disabled`  | `boolean` | `false`  | Disable step interaction       |

### SpxStepPanelComponent

Container for step content with automatic visibility management.

#### Inputs

| Input   | Type     | Description                                               |
| ------- | -------- | --------------------------------------------------------- |
| `value` | `string` | Step value this panel belongs to (must match parent step) |

## Advanced Usage

### Linear Mode with Validation

```typescript
@Component({
  template: `
    <spx-stepper
      #stepper
      [linear]="true"
      [allowPreviewSteps]="false"
      (validateStep)="onValidateStep($event)"
    >
      <spx-step value="personal" label="Personal Info">
        <spx-step-panel value="personal">
          <form #personalForm="ngForm">
            <input [(ngModel)]="personalData.name" name="name" required />
            <input [(ngModel)]="personalData.email" name="email" required />
          </form>
          <button [disabled]="!stepper.canProceedToNextStep()" (click)="stepper.validateAndNext()">
            Next Step
          </button>
        </spx-step-panel>
      </spx-step>

      <spx-step value="address" label="Address">
        <spx-step-panel value="address">
          <!-- Address form content -->
        </spx-step-panel>
      </spx-step>
    </spx-stepper>
  `,
})
export class LinearStepperComponent {
  personalData = { name: '', email: '' };

  onValidateStep(event: ValidateStepEvent): void {
    if (event.step === 'personal' && event.direction === 'next') {
      if (!this.personalData.name || !this.personalData.email) {
        event.preventDefault(); // Prevent navigation
        alert('Please fill in all required fields');
      }
    }
  }
}
```

### Dynamic Workflow Steps

```typescript
@Component({
  template: `
    <spx-stepper #stepper (validateStep)="onValidateStep($event)">
      <!-- Initial step -->
      <spx-step value="workflow-type" label="Choose Workflow">
        <spx-step-panel value="workflow-type">
          <label>
            <input
              type="radio"
              [(ngModel)]="workflowType"
              value="simple"
              (change)="onWorkflowChange()"
            />
            Simple Workflow
          </label>
          <label>
            <input
              type="radio"
              [(ngModel)]="workflowType"
              value="advanced"
              (change)="onWorkflowChange()"
            />
            Advanced Workflow
          </label>
        </spx-step-panel>
      </spx-step>

      <!-- Conditional steps based on workflow type -->
      @if (workflowType === 'simple') {
      <spx-step value="simple-config" label="Simple Configuration">
        <spx-step-panel value="simple-config">
          <!-- Simple workflow content -->
        </spx-step-panel>
      </spx-step>
      } @if (workflowType === 'advanced') {
      <spx-step value="advanced-config" label="Advanced Configuration">
        <spx-step-panel value="advanced-config">
          <!-- Advanced workflow content -->
        </spx-step-panel>
      </spx-step>

      <spx-step value="advanced-settings" label="Advanced Settings">
        <spx-step-panel value="advanced-settings">
          <!-- Additional settings -->
        </spx-step-panel>
      </spx-step>
      }
    </spx-stepper>
  `,
})
export class DynamicWorkflowComponent {
  workflowType: 'simple' | 'advanced' | null = null;

  onWorkflowChange(): void {
    // Invalidate steps after workflow selection when changing
    if (this.stepper) {
      this.stepper.invalidateStepsAfter('workflow-type');
    }
  }
}
```

## Event System

### ValidateStepEvent

Emitted when step navigation is attempted. Use `preventDefault()` to stop navigation.

```typescript
interface ValidateStepEvent {
  step: string;           // Current step being validated
  direction: 'next' | 'previous'; // Navigation direction
  preventDefault(): void;  // Call to prevent navigation
}

onValidateStep(event: ValidateStepEvent): void {
  console.log(`Validating step: ${event.step}, direction: ${event.direction}`);

  // Custom validation logic
  if (event.step === 'payment' && !this.isPaymentValid()) {
    event.preventDefault();
    this.showPaymentError();
  }
}
```

### StepChangeEvent

Emitted after successful step change.

```typescript
interface StepChangeEvent {
  previousStep: string | null;
  currentStep: string;
  stepIndex: number;
}

onStepChange(event: StepChangeEvent): void {
  console.log(`Changed from ${event.previousStep} to ${event.currentStep}`);

  // Track analytics
  this.analytics.trackStepProgress(event.currentStep, event.stepIndex);
}
```

### BeforeStepChangeEvent

Emitted before step change occurs. Can be cancelled.

```typescript
interface BeforeStepChangeEvent {
  from: string | null;
  to: string;
  preventDefault(): void;
}

onBeforeStepChange(event: BeforeStepChangeEvent): void {
  // Show confirmation for certain transitions
  if (event.from === 'draft' && event.to === 'published') {
    const confirmed = confirm('Are you sure you want to publish?');
    if (!confirmed) {
      event.preventDefault();
    }
  }
}
```

## Programmatic Navigation

### ViewChild Access Pattern

```typescript
export class MyComponent {
  @ViewChild('stepper') stepper!: SpxStepperComponent;

  // Navigation methods
  goToNextStep(): void {
    this.stepper.validateAndNext();
  }

  goToPreviousStep(): void {
    this.stepper.validateAndPrevious();
  }

  goToSpecificStep(stepValue: string): void {
    this.stepper.setActiveStep(stepValue);
  }

  // State checking
  canProceed(): boolean {
    return this.stepper.canProceedToNextStep();
  }

  getCurrentStep(): string {
    return this.stepper.activeStepValue();
  }

  isStepCompleted(stepValue: string): boolean {
    return this.stepper.isStepCompleted(stepValue);
  }

  // Step management
  markStepComplete(stepValue: string): void {
    this.stepper.markStepCompleted(stepValue);
  }

  resetStepper(): void {
    this.stepper.reset();
  }
}
```

### Available Methods

| Method                        | Parameters | Return Type | Description                                        |
| ----------------------------- | ---------- | ----------- | -------------------------------------------------- |
| `validateAndNext()`           | -          | `boolean`   | Validate current step and move to next             |
| `validateAndPrevious()`       | -          | `boolean`   | Validate current step and move to previous         |
| `setActiveStep(value)`        | `string`   | `boolean`   | Set specific step as active                        |
| `reset()`                     | -          | `void`      | Reset stepper to initial state                     |
| `canProceedToNextStep()`      | -          | `boolean`   | Check if next step navigation is allowed           |
| `canNavigateToStep(value)`    | `string`   | `boolean`   | Check if navigation to specific step is allowed    |
| `isStepCompleted(value)`      | `string`   | `boolean`   | Check if step is marked as completed               |
| `markStepCompleted(value)`    | `string`   | `void`      | Mark step as completed                             |
| `markStepVisited(value)`      | `string`   | `void`      | Mark step as visited                               |
| `invalidateStepsAfter(value)` | `string`   | `void`      | Reset completion status for steps after given step |

### Computed Signals

| Signal              | Type                 | Description                    |
| ------------------- | -------------------- | ------------------------------ |
| `activeStepValue()` | `string`             | Currently active step value    |
| `activeStepIndex()` | `number`             | Index of currently active step |
| `stepsArray()`      | `SpxStepComponent[]` | Array of all step components   |
| `firstStepValue()`  | `string`             | Value of first step            |
| `lastStepValue()`   | `string`             | Value of last step             |

## Styling & Theming

### CSS Classes

The stepper uses BEM-style CSS classes for consistent styling:

```scss
// Stepper container
.stepper {
  display: flex;
  // ... stepper styles
}

// Individual step headers
.stepper__item {
  // Base step header styles

  &.active {
    // Active step styles
  }

  &.completed {
    // Completed step styles
  }

  &.disabled {
    // Disabled step styles
  }
}

// Step content panels
.spx-step-panel {
  // Panel container styles
}
```

### Custom Theming

```scss
// Override stepper variables
:root {
  --stepper-primary-color: #007bff;
  --stepper-completed-color: #28a745;
  --stepper-disabled-color: #6c757d;
}

// Custom step header styling
.stepper__item {
  border-color: var(--stepper-primary-color);

  &.completed {
    background-color: var(--stepper-completed-color);
  }
}
```

## Best Practices

### 1. Form Integration

```typescript
// Use reactive forms for better validation
@Component({
  template: `
    <spx-stepper (validateStep)="onValidateStep($event)">
      <spx-step value="form-step" label="Form Data">
        <spx-step-panel value="form-step">
          <form [formGroup]="myForm">
            <input formControlName="email" type="email" />
            <input formControlName="password" type="password" />
          </form>
        </spx-step-panel>
      </spx-step>
    </spx-stepper>
  `,
})
export class FormStepperComponent {
  myForm = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]],
  });

  onValidateStep(event: ValidateStepEvent): void {
    if (event.step === 'form-step' && event.direction === 'next') {
      if (this.myForm.invalid) {
        event.preventDefault();
        this.myForm.markAllAsTouched();
      }
    }
  }
}
```

### 2. Async Operations

```typescript
async onValidateStep(event: ValidateStepEvent): Promise<void> {
  if (event.step === 'save-step' && event.direction === 'next') {
    try {
      event.preventDefault(); // Prevent immediate navigation

      const result = await this.apiService.saveData(this.formData);

      if (result.success) {
        this.stepper.markStepCompleted('save-step');
        this.stepper.validateAndNext(); // Now proceed
      } else {
        alert('Save failed. Please try again.');
      }
    } catch (error) {
      console.error('Save error:', error);
    }
  }
}
```

### 3. State Management

```typescript
// Use services for complex state management
@Injectable()
export class StepperStateService {
  private state = signal({
    currentStep: 'step1',
    formData: {},
    completedSteps: new Set<string>(),
  });

  readonly currentStep = computed(() => this.state().currentStep);
  readonly isStepCompleted = (step: string) =>
    computed(() => this.state().completedSteps.has(step));

  updateStep(step: string): void {
    this.state.update((state) => ({
      ...state,
      currentStep: step,
    }));
  }

  completeStep(step: string): void {
    this.state.update((state) => ({
      ...state,
      completedSteps: new Set([...state.completedSteps, step]),
    }));
  }
}
```

## Accessibility

The stepper includes built-in accessibility features:

- **Keyboard navigation** with arrow keys and Enter/Space
- **ARIA labels** and roles for screen readers
- **Focus management** during step transitions
- **High contrast** support for step states

### Additional ARIA Support

```html
<spx-stepper role="tablist" aria-label="Multi-step form navigation">
  <spx-step value="step1" label="Personal Information" [attr.aria-describedby]="'step1-desc'">
    <spx-step-panel value="step1" role="tabpanel" [attr.aria-labelledby]="'step1-header'">
      <div id="step1-desc">Enter your personal details</div>
      <!-- Content -->
    </spx-step-panel>
  </spx-step>
</spx-stepper>
```

## Testing

### E2E Testing with Playwright

```typescript
import { test, expect } from '@playwright/test';

test('stepper navigation works correctly', async ({ page }) => {
  await page.goto('/stepper-demo');

  // Check initial state
  await expect(page.locator('[data-testid="step-header-step1"]')).toHaveClass(/active/);

  // Navigate to next step
  await page.locator('[data-testid="step1-next-btn"]').click();
  await expect(page.locator('[data-testid="step-header-step2"]')).toHaveClass(/active/);

  // Test validation prevention
  await page.locator('[data-testid="step2-next-btn"]').click();
  // Should stay on step2 if validation fails
  await expect(page.locator('[data-testid="step-header-step2"]')).toHaveClass(/active/);
});
```

### Unit Testing

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SpxStepperComponent } from '@solopx/spx-ui-kit';

describe('SpxStepperComponent', () => {
  let component: SpxStepperComponent;
  let fixture: ComponentFixture<SpxStepperComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [SpxStepperComponent],
    });
    fixture = TestBed.createComponent(SpxStepperComponent);
    component = fixture.componentInstance;
  });

  it('should navigate to next step when valid', () => {
    // Setup test steps
    component.setActiveStep('step1');

    // Test navigation
    const result = component.validateAndNext();
    expect(result).toBe(true);
    expect(component.activeStepValue()).toBe('step2');
  });
});
```

## Troubleshooting

### Common Issues

1. **Steps not appearing**: Ensure step values are unique and step panels have matching values
2. **Navigation not working**: Check that ViewChild reference is properly initialized
3. **Validation not preventing navigation**: Make sure to call `event.preventDefault()` in validation handler
4. **Form state lost**: Use proper form management and avoid removing elements from DOM

### Debug Mode

Enable debug logging:

```typescript
// Add to your component
ngAfterViewInit(): void {
  // Enable stepper debugging
  (this.stepper as any)._debug = true;
}
```

## Migration Guide

### From version 0.1.x to 0.2.x

- ✅ No breaking changes
- ✨ New features: Enhanced validation system, outlet directive
- 🔧 Improved: Better form state management, accessibility features

For detailed examples and live demos, visit our [Storybook documentation](https://your-storybook-url.com).