# @isa/ui/buttons

A comprehensive button component library for Angular applications providing five specialized button components with consistent styling, loading states, and accessibility features.

## Overview

The UI Buttons library provides a complete suite of button components for the ISA application, each optimized for specific use cases. All components leverage Angular signals for reactive state management, support customizable sizes and colors, and follow the ISA design system. The library includes standard buttons, text/link-style buttons, icon-only buttons, info/help buttons, and stateful buttons with success/error states and auto-dismiss functionality.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Styling and Customization](#styling-and-customization)
- [Accessibility](#accessibility)
- [Testing](#testing)
- [Architecture Notes](#architecture-notes)

## Features

- **Five specialized button components** - Standard, text, icon, info, and stateful buttons
- **Reactive signal-based state** - All inputs and computed properties use Angular signals
- **Loading states** - Built-in pending/loading indicators with spinners
- **Customizable sizing** - Small, medium, and large sizes for all button types
- **Theme support** - Multiple color schemes (primary, secondary, brand, tertiary, neutral)
- **Stateful feedback** - Success/error states with configurable auto-dismiss
- **Disabled state handling** - Proper disabled styling and ARIA attributes
- **Keyboard navigation** - Full keyboard accessibility with tab index support
- **OnPush change detection** - Optimized performance with minimal re-renders
- **No encapsulation** - View encapsulation disabled for flexible styling
- **Animation support** - Smooth transitions for stateful button state changes

## Quick Start

### 1. Import Components

```typescript
import { Component } from '@angular/core';
import {
  ButtonComponent,
  TextButtonComponent,
  IconButtonComponent,
  InfoButtonComponent,
  StatefulButtonComponent
} from '@isa/ui/buttons';

@Component({
  selector: 'app-my-component',
  standalone: true,
  imports: [
    ButtonComponent,
    TextButtonComponent,
    IconButtonComponent,
    InfoButtonComponent,
    StatefulButtonComponent
  ],
  template: '...'
})
export class MyComponent {}
```

### 2. Use Standard Button

```html
<button uiButton color="primary" size="large" [pending]="isLoading">
  Submit Form
</button>
```

### 3. Use Text Button (Link Style)

```html
<button uiTextButton color="strong" (click)="handleClick()">
  Learn More
</button>
```

### 4. Use Icon Button

```html
<button uiIconButton
  name="isaActionEdit"
  color="primary"
  size="large"
  [pending]="isSaving">
</button>
```

### 5. Use Info Button

```html
<button uiInfoButton [disabled]="false">
  <ng-icon name="isaActionInfo"></ng-icon>
</button>
```

### 6. Use Stateful Button

```html
<ui-stateful-button
  [(state)]="buttonState"
  defaultContent="Save Changes"
  successContent="Changes Saved!"
  errorContent="Save Failed"
  errorAction="Retry"
  color="primary"
  [dismiss]="3000"
  (clicked)="handleSave()"
  (action)="handleRetry()">
</ui-stateful-button>
```

## Core Concepts

### Button Component Types

The library provides five specialized button components, each optimized for specific use cases:

#### 1. ButtonComponent (Standard Button)
- **Purpose**: Primary action buttons for forms, dialogs, and main interactions
- **Selector**: `ui-button, [uiButton]`
- **Key Features**:
  - Customizable size (small, medium, large)
  - Four color themes (primary, secondary, brand, tertiary)
  - Loading spinner overlay in pending state
  - Preserves button dimensions during loading
  - Disabled state support

#### 2. TextButtonComponent (Link-Style Button)
- **Purpose**: Secondary actions, navigation links, and lightweight interactions
- **Selector**: `ui-text-button, [uiTextButton]`
- **Key Features**:
  - Text-based styling (no background)
  - Three color themes (normal, strong, subtle)
  - Pending state with inline spinner
  - Smaller footprint than standard buttons

#### 3. IconButtonComponent (Icon-Only Button)
- **Purpose**: Compact actions, toolbar buttons, and icon-based interactions
- **Selector**: `ui-icon-button, [uiIconButton]`
- **Key Features**:
  - Icon-only display with configurable icon name
  - Five color themes (primary, secondary, brand, tertiary, neutral)
  - Three sizes with responsive icon sizing
  - Automatic loading icon replacement
  - Enhanced ARIA attributes for accessibility

#### 4. InfoButtonComponent (Help/Info Button)
- **Purpose**: Help buttons, tooltips triggers, and informational actions
- **Selector**: `ui-info-button, [uiInfoButton]`
- **Key Features**:
  - Fixed styling optimized for info icons
  - Pending and disabled states
  - Minimal visual footprint
  - Designed for use with tooltip directives

#### 5. StatefulButtonComponent (Success/Error States)
- **Purpose**: Form submissions, save actions, and operations requiring feedback
- **Selector**: `ui-stateful-button`
- **Key Features**:
  - Three states: default, success, error
  - Configurable content for each state
  - Auto-dismiss with configurable timeout
  - Width animations during state transitions
  - Optional error action button
  - Two-way binding for state management

### Signal-Based Reactivity

All button components use Angular signals for reactive state management:

```typescript
// Input signals
size = input<ButtonSize>('medium');
color = input<ButtonColor>('primary');
pending = input<boolean>(false);
disabled = input<boolean>(false);

// Computed signals
sizeClass = computed(() => `ui-button__${this.size()}`);
colorClass = computed(() => `ui-button__${this.color()}`);
pendingClass = computed(() => this.pending() ? 'ui-button__pending' : '');
disabledClass = computed(() => this.disabled() ? 'disabled' : '');
```

### Host Binding Pattern

Components use Angular host binding for dynamic CSS classes:

```typescript
host: {
  '[class]': '["ui-button", sizeClass(), colorClass(), pendingClass(), disabledClass()]',
  '[tabindex]': 'tabIndex()',
  '[disabled]': 'disabled()',
}
```

### Loading State Handling

All button components support loading states with different rendering strategies:

- **Standard Button**: Overlay spinner preserving button dimensions
- **Text Button**: Inline spinner replacing content
- **Icon Button**: Replaces icon with loading icon
- **Info Button**: Overlay spinner
- **Stateful Button**: Inherits from standard button behavior

## API Reference

### ButtonComponent

Standard button component with customizable appearance and loading state.

#### Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `size` | `ButtonSize` | `'medium'` | Button size: 'small', 'medium', or 'large' |
| `color` | `ButtonColor` | `'primary'` | Color theme: 'primary', 'secondary', 'brand', or 'tertiary' |
| `pending` | `boolean` | `false` | Shows loading spinner when true |
| `disabled` | `boolean` | `false` | Disables button when true |
| `tabIndex` | `number` | `0` | Tab index for keyboard navigation |

#### Example

```html
<button uiButton
  color="brand"
  size="large"
  [pending]="isLoading"
  [disabled]="!isValid"
  (click)="handleSubmit()">
  Submit Form
</button>
```

### TextButtonComponent

Link-style button for secondary actions and navigation.

#### Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `size` | `TextButtonSize` | `'medium'` | Button size: 'small', 'medium', or 'large' |
| `color` | `TextButtonColor` | `'normal'` | Color theme: 'normal', 'strong', or 'subtle' |
| `pending` | `boolean` | `false` | Shows loading spinner when true |
| `disabled` | `boolean` | `false` | Disables button when true |
| `tabIndex` | `number` | `0` | Tab index for keyboard navigation |

#### Example

```html
<button uiTextButton
  color="strong"
  size="medium"
  (click)="handleCancel()">
  Cancel
</button>
```

### IconButtonComponent

Icon-only button for compact actions and toolbars.

#### Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | `string` | `undefined` | Icon name from @isa/icons |
| `size` | `IconButtonSize` | `'large'` | Button size: 'small', 'medium', or 'large' |
| `color` | `IconButtonColor` | `'primary'` | Color theme: 'primary', 'secondary', 'brand', 'tertiary', or 'neutral' |
| `pending` | `boolean` | `false` | Shows loading icon when true |
| `disabled` | `boolean` | `false` | Disables button when true |
| `tabIndex` | `number` | `0` | Tab index for keyboard navigation |

#### Computed Properties

- `iconName`: Returns 'isaLoading' when pending, otherwise returns the input name
- `iconSize`: Returns computed icon size ('1rem', '1.25rem', or '1.5rem') based on button size

#### Example

```html
<button uiIconButton
  name="isaActionEdit"
  color="primary"
  size="large"
  [pending]="isSaving"
  [disabled]="!canEdit"
  (click)="handleEdit()">
</button>
```

### InfoButtonComponent

Help/info button optimized for tooltips and informational content.

#### Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `pending` | `boolean` | `false` | Shows loading spinner when true |
| `disabled` | `boolean` | `false` | Disables button when true |
| `tabIndex` | `number` | `0` | Tab index for keyboard navigation |

#### Example

```html
<button uiInfoButton
  [disabled]="false"
  (click)="showHelp()">
  <ng-icon name="isaActionInfo"></ng-icon>
</button>
```

### StatefulButtonComponent

Button with success/error states and auto-dismiss functionality.

#### Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `defaultContent` | `string` | required | Content displayed in default state |
| `successContent` | `string` | required | Content displayed in success state |
| `errorContent` | `string` | required | Content displayed in error state |
| `errorAction` | `string` | `undefined` | Optional action button text in error state |
| `defaultWidth` | `string` | `'100%'` | Button width in default state |
| `successWidth` | `string` | `'100%'` | Button width in success state |
| `errorWidth` | `string` | `'100%'` | Button width in error state |
| `dismiss` | `number` | `5000` | Auto-dismiss timeout in milliseconds (0 to disable) |
| `color` | `ButtonColor` | `'primary'` | Color theme |
| `size` | `ButtonSize` | `'medium'` | Button size |
| `pending` | `boolean` | `false` | Shows loading spinner when true |
| `disabled` | `boolean` | `false` | Disables button when true |

#### Outputs

| Output | Type | Description |
|--------|------|-------------|
| `clicked` | `void` | Emitted when button is clicked in default state |
| `action` | `void` | Emitted when error action button is clicked |

#### Two-Way Binding

| Property | Type | Description |
|----------|------|-------------|
| `state` | `StatefulButtonState` | Current state: 'default', 'success', or 'error' |

#### Example

```typescript
import { Component, signal } from '@angular/core';
import { StatefulButtonComponent } from '@isa/ui/buttons';

@Component({
  selector: 'app-save-form',
  standalone: true,
  imports: [StatefulButtonComponent],
  template: `
    <ui-stateful-button
      [(state)]="saveState"
      defaultContent="Save Changes"
      successContent="Changes Saved Successfully!"
      errorContent="Failed to Save Changes"
      errorAction="Retry"
      color="primary"
      size="large"
      [dismiss]="3000"
      [pending]="isSaving()"
      (clicked)="handleSave()"
      (action)="handleRetry()">
    </ui-stateful-button>
  `
})
export class SaveFormComponent {
  saveState = signal<'default' | 'success' | 'error'>('default');
  isSaving = signal(false);

  async handleSave(): Promise<void> {
    this.isSaving.set(true);
    try {
      await this.saveFormData();
      this.saveState.set('success');
    } catch (error) {
      this.saveState.set('error');
    } finally {
      this.isSaving.set(false);
    }
  }

  handleRetry(): void {
    this.saveState.set('default');
    this.handleSave();
  }
}
```

## Usage Examples

### Standard Button with Loading State

```typescript
import { Component, signal } from '@angular/core';
import { ButtonComponent } from '@isa/ui/buttons';

@Component({
  selector: 'app-submit-form',
  standalone: true,
  imports: [ButtonComponent],
  template: `
    <button uiButton
      color="primary"
      size="large"
      [pending]="isSubmitting()"
      [disabled]="!isFormValid()"
      (click)="handleSubmit()">
      Submit Application
    </button>
  `
})
export class SubmitFormComponent {
  isSubmitting = signal(false);
  isFormValid = signal(true);

  async handleSubmit(): Promise<void> {
    this.isSubmitting.set(true);
    try {
      await this.submitForm();
    } finally {
      this.isSubmitting.set(false);
    }
  }
}
```

### Text Button for Navigation

```typescript
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { TextButtonComponent } from '@isa/ui/buttons';

@Component({
  selector: 'app-navigation-links',
  standalone: true,
  imports: [TextButtonComponent],
  template: `
    <nav>
      <button uiTextButton color="strong" (click)="navigateHome()">
        Home
      </button>
      <button uiTextButton color="normal" (click)="navigateAbout()">
        About
      </button>
      <button uiTextButton color="subtle" (click)="navigateHelp()">
        Help
      </button>
    </nav>
  `
})
export class NavigationLinksComponent {
  constructor(private router: Router) {}

  navigateHome(): void {
    this.router.navigate(['/']);
  }

  navigateAbout(): void {
    this.router.navigate(['/about']);
  }

  navigateHelp(): void {
    this.router.navigate(['/help']);
  }
}
```

### Icon Button Toolbar

```typescript
import { Component, signal } from '@angular/core';
import { IconButtonComponent } from '@isa/ui/buttons';

@Component({
  selector: 'app-editor-toolbar',
  standalone: true,
  imports: [IconButtonComponent],
  template: `
    <div class="toolbar">
      <button uiIconButton
        name="isaActionEdit"
        color="primary"
        size="medium"
        [disabled]="!canEdit()"
        (click)="handleEdit()">
      </button>

      <button uiIconButton
        name="isaActionDelete"
        color="tertiary"
        size="medium"
        [disabled]="!canDelete()"
        (click)="handleDelete()">
      </button>

      <button uiIconButton
        name="isaActionSave"
        color="brand"
        size="medium"
        [pending]="isSaving()"
        (click)="handleSave()">
      </button>
    </div>
  `
})
export class EditorToolbarComponent {
  canEdit = signal(true);
  canDelete = signal(true);
  isSaving = signal(false);

  handleEdit(): void {
    console.log('Edit clicked');
  }

  handleDelete(): void {
    console.log('Delete clicked');
  }

  async handleSave(): Promise<void> {
    this.isSaving.set(true);
    try {
      await this.saveDocument();
    } finally {
      this.isSaving.set(false);
    }
  }
}
```

### Mixed Button Types in Dialog

```typescript
import { Component, signal } from '@angular/core';
import {
  ButtonComponent,
  TextButtonComponent,
  IconButtonComponent
} from '@isa/ui/buttons';

@Component({
  selector: 'app-confirmation-dialog',
  standalone: true,
  imports: [ButtonComponent, TextButtonComponent, IconButtonComponent],
  template: `
    <div class="dialog">
      <div class="dialog-header">
        <h2>Confirm Deletion</h2>
        <button uiIconButton
          name="isaActionClose"
          color="neutral"
          size="small"
          (click)="handleClose()">
        </button>
      </div>

      <div class="dialog-content">
        <p>Are you sure you want to delete this item? This action cannot be undone.</p>
      </div>

      <div class="dialog-actions">
        <button uiTextButton
          color="subtle"
          (click)="handleCancel()">
          Cancel
        </button>

        <button uiButton
          color="tertiary"
          [pending]="isDeleting()"
          (click)="handleDelete()">
          Delete Item
        </button>
      </div>
    </div>
  `
})
export class ConfirmationDialogComponent {
  isDeleting = signal(false);

  async handleDelete(): Promise<void> {
    this.isDeleting.set(true);
    try {
      await this.deleteItem();
      this.handleClose();
    } finally {
      this.isDeleting.set(false);
    }
  }

  handleCancel(): void {
    this.handleClose();
  }

  handleClose(): void {
    // Close dialog logic
  }

  private async deleteItem(): Promise<void> {
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}
```

## Styling and Customization

### Size Classes

All button components (except InfoButton) support three sizes:

```typescript
export const ButtonSize = {
  Small: 'small',
  Medium: 'medium',
  Large: 'large',
} as const;
```

CSS classes applied: `ui-button__small`, `ui-button__medium`, `ui-button__large`

### Color Themes

#### Standard Button Colors

```typescript
export const ButtonColor = {
  Primary: 'primary',    // Default blue theme
  Secondary: 'secondary', // Gray theme
  Brand: 'brand',        // ISA brand color
  Tertiary: 'tertiary',  // Red/warning theme
} as const;
```

CSS classes applied: `ui-button__primary`, `ui-button__secondary`, etc.

#### Text Button Colors

```typescript
export const TextButtonColor = {
  Normal: 'normal',   // Standard text color
  Strong: 'strong',   // Bold/emphasized text
  Subtle: 'subtle',   // Muted text
} as const;
```

#### Icon Button Colors

```typescript
export const IconButtonColor = {
  Primary: 'primary',
  Secondary: 'secondary',
  Brand: 'brand',
  Tertiary: 'tertiary',
  Neutral: 'neutral',  // Additional neutral theme
} as const;
```

### State Classes

All buttons apply these state-based classes:

- **Pending**: `ui-button__pending`, `ui-text-button__pending`, `ui-info-button--pending`
- **Disabled**: `disabled` (applied to all button types)

### Custom Styling

Override styles using specific class selectors:

```scss
// Custom primary button styling
.ui-button.ui-button__primary {
  background-color: custom-color;
  border-radius: 8px;
}

// Custom text button hover
.ui-text-button.ui-text-button__strong:hover {
  text-decoration: underline;
}

// Custom icon button sizing
.ui-icon-button.ui-icon-button__large {
  width: 48px;
  height: 48px;
}
```

## Accessibility

All button components implement comprehensive accessibility features:

### ARIA Attributes

#### Standard, Text, and Info Buttons
- `[disabled]`: Applied via host binding
- Proper button semantics via `<button>` element
- Tab index support for keyboard navigation

#### Icon Button (Enhanced ARIA)
```typescript
host: {
  '[attr.aria-disabled]': 'disabled()',
  '[attr.aria-label]': 'name()',        // Icon name as label
  '[attr.aria-busy]': 'pending()',      // Loading state
  '[attr.role]': 'pending() ? "progressbar" : "button"'
}
```

### Keyboard Navigation

All buttons support:
- **Tab**: Focus navigation
- **Enter/Space**: Trigger click event
- **Custom tab index**: Configurable via `tabIndex` input

### Screen Reader Support

- Icon buttons include `aria-label` with icon name
- Loading states announce via `aria-busy`
- Disabled states properly conveyed via `aria-disabled`
- Stateful buttons maintain role semantics during state changes

## Testing

The library uses **Jest** for testing.

### Running Tests

```bash
# Run tests for this library
npx nx test ui-buttons --skip-nx-cache

# Run tests with coverage
npx nx test ui-buttons --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test ui-buttons --watch
```

### Test Coverage

All components include comprehensive unit tests covering:

- **Input signals** - Size, color, pending, disabled states
- **Computed properties** - CSS class generation
- **Host bindings** - Dynamic class and attribute application
- **Event handling** - Click events, state changes (stateful button)
- **Loading states** - Spinner rendering and icon replacement
- **Accessibility** - ARIA attributes and keyboard navigation
- **State management** - Stateful button state transitions and auto-dismiss

## Architecture Notes

### Component Architecture

The library follows a consistent architecture pattern across all button types:

```
ButtonComponent (Standalone)
├─→ Signal-based inputs (size, color, pending, disabled, tabIndex)
├─→ Computed properties (CSS classes)
├─→ Host bindings (dynamic attributes and classes)
├─→ Template (content projection + conditional spinner)
└─→ ViewEncapsulation.None (flexible styling)
```

### Design Patterns

#### 1. Host Binding for Dynamic Styling

All components use host binding to apply CSS classes:

```typescript
host: {
  '[class]': '["ui-button", sizeClass(), colorClass(), pendingClass(), disabledClass()]',
  '[tabindex]': 'tabIndex()',
  '[disabled]': 'disabled()',
}
```

**Benefits**:
- No manual DOM manipulation required
- Reactive updates via signals
- Type-safe class application
- Optimized change detection

#### 2. Signal-Based Reactivity

Components leverage Angular signals for all reactive state:

```typescript
// Input signals
size = input<ButtonSize>('medium');
color = input<ButtonColor>('primary');
pending = input<boolean>(false);

// Computed signals
sizeClass = computed(() => `ui-button__${this.size()}`);
pendingClass = computed(() => this.pending() ? 'ui-button__pending' : '');
```

**Benefits**:
- Fine-grained reactivity
- Automatic dependency tracking
- Minimal re-renders with OnPush
- Clear data flow

#### 3. ViewEncapsulation.None

All components disable view encapsulation:

```typescript
encapsulation: ViewEncapsulation.None
```

**Rationale**:
- Allows global ISA design system styles to apply
- Enables custom theming and overrides
- Simplifies CSS class naming (no shadow DOM)
- Consistent with ISA application styling strategy

#### 4. Content Projection

Standard, text, and info buttons use content projection:

```html
<ng-content></ng-content>
@if (pending()) {
  <div class="ui-button__spinner-container">
    <ng-icon name="isaLoading"></ng-icon>
  </div>
}
```

**Benefits**:
- Flexible button content
- Supports text, icons, and mixed content
- Preserves button dimensions during loading

### Stateful Button Architecture

The StatefulButtonComponent uses a more complex architecture:

```
StatefulButtonComponent
├─→ Extends ButtonComponent functionality
├─→ State management (default, success, error)
├─→ Auto-dismiss timer with cleanup
├─→ Width animations via AnimationBuilder
├─→ Effect-based state watching
├─→ Two-way state binding with model()
└─→ Conditional error action button
```

**Key Implementation Details**:

1. **State Effect with Auto-Dismiss**:
```typescript
effect(() => {
  const currentState = this.state();
  const dismissTimeout = this.dismiss();

  this.clearDismissTimer();

  if (dismissTimeout && (currentState === 'success' || currentState === 'error')) {
    this.dismissTimer = setTimeout(() => {
      this.changeState('default');
    }, dismissTimeout);
  }
});
```

2. **Width Animations**:
```typescript
#makeWidthAnimation(width: string): void {
  const animation = this.#animationBuilder.build([
    style({ width: '*' }),
    animate('250ms', style({ width }))
  ]);

  const player = animation.create(this.buttonElement().nativeElement);
  player.play();
}
```

3. **Lifecycle Management**:
```typescript
ngOnDestroy(): void {
  this.clearDismissTimer(); // Prevent memory leaks
}
```

### Performance Considerations

1. **OnPush Change Detection**: All components use `ChangeDetectionStrategy.OnPush` for optimal performance
2. **Signal-Based Updates**: Computed properties only recalculate when dependencies change
3. **Minimal Re-renders**: Host bindings update only affected attributes
4. **Animation Performance**: StatefulButton uses AnimationBuilder for performant width transitions

### Known Architectural Considerations

#### 1. ViewEncapsulation Strategy (Low Priority)

**Current State**:
- All components use `ViewEncapsulation.None`
- Relies on BEM-like class naming for style isolation

**Consideration**:
- Could enable encapsulation with CSS custom properties
- Would require design system refactoring

**Impact**: Low - current approach works well with ISA design system

#### 2. Stateful Button Complexity (Medium Priority)

**Current State**:
- Combines multiple concerns (state, animations, timers)
- Complex lifecycle management

**Consideration**:
- Could extract animation logic to separate service
- Could use directive composition for state management
- Timer management could be centralized

**Impact**: Medium - component works well but is less maintainable than simpler buttons

## Dependencies

### Required Libraries

- `@angular/core` - Angular framework
- `@angular/animations` - Animations (StatefulButton only)
- `@ng-icons/core` - Icon rendering
- `@isa/icons` - ISA icon library (loading icon)

### Optional Dependencies

- `@angular/forms` - For use with reactive forms (stateful button state binding)

### Path Alias

Import from: `@isa/ui/buttons`

### Project Configuration

- **Project Name**: `ui-buttons`
- **Prefix**: `ui`
- **Testing**: Jest
- **Source Root**: `libs/ui/buttons/src`

## License

Internal ISA Frontend library - not for external distribution.
