Files
ISA-Frontend/libs/ui/input-controls
Lorenz Hilpert fc6d29d62f Merged PR 2031: feat(crm): add customer bon redemption feature
feat(crm): add customer bon redemption feature

- New library @isa/crm/feature/customer-bon-redemption
- Implement bon validation and redemption flow
- Add SignalStore for state management
- Add resource pattern for reactive data loading
- Add facade for business logic abstraction
- Add Zod schemas for runtime validation
- Integrate with loyalty card API endpoints
- Add accessibility and E2E test attributes
- Remove mock provider (use real facade)
- Exclude generated swagger files from linting

Components:
- BonInputFieldComponent - input with validation
- BonDetailsDisplayComponent - shows validated bon
- BonRedemptionButtonComponent - redemption action

Data Access:
- CustomerBonRedemptionFacade - business logic
- CustomerBonCheckResource - reactive validation
- BonRedemptionStore - component state
- CrmSearchService - API integration (checkBon, addBon)

Issue: 5314

Related work items: #5314
2025-11-19 12:51:58 +00:00
..

@isa/ui/input-controls

A comprehensive collection of form input components and directives for Angular applications supporting reactive forms, template-driven forms, and accessibility features.

Overview

The Input Controls library provides a complete suite of reusable form components built on Angular's reactive forms API and Angular CDK. It includes text fields, checkboxes, dropdowns, chips, checklists, listboxes, and inline inputs - all designed to work seamlessly with Angular Forms while maintaining consistent styling and behavior across the ISA application.

Table of Contents

Features

  • Complete form control suite - 8 component types covering common input scenarios
  • Reactive Forms integration - ControlValueAccessor implementation for all form controls
  • Template-driven forms - Full ngModel support for two-way binding
  • Keyboard navigation - Arrow keys, Enter, Escape, and Tab support
  • Accessibility (a11y) - ARIA attributes, screen reader support, keyboard navigation
  • Customizable appearance - Multiple size and appearance variants for each control
  • Validation support - Integration with Angular's built-in validators
  • CDK integration - Built on Angular CDK primitives (listbox, a11y, overlay)
  • Signal-based inputs - Modern Angular signals for reactive property management
  • Type-safe - Full TypeScript support with generic type parameters

Quick Start

1. Import Components

import { Component } from '@angular/core';
import {
  CheckboxComponent,
  DropdownButtonComponent,
  DropdownOptionComponent,
  TextFieldComponent,
  InputControlDirective,
} from '@isa/ui/input-controls';
import { ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-my-form',
  imports: [
    ReactiveFormsModule,
    CheckboxComponent,
    DropdownButtonComponent,
    DropdownOptionComponent,
    TextFieldComponent,
    InputControlDirective,
  ],
  template: '...'
})
export class MyFormComponent {}

2. Basic Text Field

<ui-text-field>
  <input
    type="text"
    uiInputControl
    formControlName="username"
    placeholder="Enter username"
  />
</ui-text-field>

3. Checkbox

<ui-checkbox>
  <input type="checkbox" formControlName="acceptTerms" />
</ui-checkbox>

4. Dropdown

<ui-dropdown [(ngModel)]="selectedOption" label="Select option">
  <ui-dropdown-option [value]="1">Option 1</ui-dropdown-option>
  <ui-dropdown-option [value]="2">Option 2</ui-dropdown-option>
  <ui-dropdown-option [value]="3">Option 3</ui-dropdown-option>
</ui-dropdown>

Core Concepts

Component Categories

The library provides three main categories of form controls:

1. Text Input Controls

  • TextFieldComponent - Standard text input with container, labels, and errors
  • TextareaComponent - Multi-line text input
  • InlineInputComponent - Compact inline text input for space-constrained UIs

2. Selection Controls

  • CheckboxComponent - Single checkbox with bullet or checkbox appearance
  • ChecklistComponent - Multiple checkboxes working as a group
  • DropdownButtonComponent - Select dropdown with keyboard navigation
  • ChipsComponent - Single-select chip group
  • ListboxDirective - CDK listbox wrapper for custom list selections

3. Supporting Components

  • InputControlDirective - Core directive for input field integration
  • TextFieldContainerComponent - Layout container for text fields
  • TextFieldErrorsComponent - Validation error display
  • TextFieldClearComponent - Clear button for text inputs
  • CheckboxLabelDirective - Label styling for checkboxes
  • ChecklistValueDirective - Value binding for checklist items
  • ChipOptionComponent - Individual chip option
  • DropdownOptionComponent - Individual dropdown option
  • ListboxItemDirective - Individual listbox item

Control Value Accessor Pattern

All form controls implement Angular's ControlValueAccessor interface, enabling seamless integration with both reactive and template-driven forms:

export class DropdownButtonComponent<T> implements ControlValueAccessor {
  value = model<T>();
  disabled = model<boolean>(false);

  writeValue(obj: unknown): void { ... }
  registerOnChange(fn: (value: T) => void): void { ... }
  registerOnTouched(fn: () => void): void { ... }
  setDisabledState(isDisabled: boolean): void { ... }
}

Appearance Variants

Checkbox Appearances

const CheckboxAppearance = {
  Bullet: 'bullet',      // Round bullet-style selector
  Checkbox: 'checkbox',  // Traditional square checkbox (default)
} as const;

Text Field Sizes

const TextFieldSize = {
  Small: 'small',
  Medium: 'medium',      // Default
  Large: 'large',
} as const;

API Reference

CheckboxComponent

A customizable checkbox component supporting different visual appearances.

Selector: ui-checkbox

Inputs:

  • appearance: CheckboxAppearance - Visual style ('checkbox' | 'bullet'). Default: 'checkbox'

Usage:

<!-- Default checkbox appearance -->
<ui-checkbox>
  <input type="checkbox" />
</ui-checkbox>

<!-- Bullet appearance -->
<ui-checkbox [appearance]="CheckboxAppearance.Bullet">
  <input type="checkbox" />
</ui-checkbox>

DropdownButtonComponent

A dropdown select component with keyboard navigation and CDK overlay integration.

Selector: ui-dropdown

Inputs:

  • appearance: DropdownAppearance - Visual style. Default: 'accent-outline'
  • label: string - Dropdown label text
  • showSelectedValue: boolean - Show selected option text. Default: true
  • tabIndex: number - Tab index for keyboard navigation. Default: 0
  • id: string - Optional element ID

Outputs:

  • value: ModelSignal<T> - Two-way bindable selected value
  • disabled: ModelSignal<boolean> - Disabled state

Methods:

  • open(): void - Opens the dropdown overlay
  • close(): void - Closes the dropdown overlay
  • select(option, options?): void - Selects an option programmatically

Keyboard Navigation:

  • Arrow Down/Up - Navigate through options
  • Enter - Select highlighted option and close
  • Escape - Close dropdown

Usage:

interface Product {
  id: number;
  name: string;
}

products: Product[] = [
  { id: 1, name: 'Product A' },
  { id: 2, name: 'Product B' }
];
selectedProduct: Product;
<ui-dropdown
  [(value)]="selectedProduct"
  label="Select Product"
  appearance="accent-outline">
  @for (product of products; track product.id) {
    <ui-dropdown-option [value]="product">
      {{ product.name }}
    </ui-dropdown-option>
  }
</ui-dropdown>

ChipsComponent

Single-selection chip group with form integration.

Selector: ui-chips

Outputs:

  • value: ModelSignal<T> - Selected chip value
  • disabled: ModelSignal<boolean> - Disabled state

Methods:

  • select(value: T, options?): void - Selects a chip
  • toggle(value: T, options?): void - Toggles chip selection
  • isSelected(value: T): boolean - Checks if value is selected

Usage:

type Size = 'S' | 'M' | 'L' | 'XL';
selectedSize: Size = 'M';
<ui-chips [(ngModel)]="selectedSize">
  <ui-chip [value]="'S'">Small</ui-chip>
  <ui-chip [value]="'M'">Medium</ui-chip>
  <ui-chip [value]="'L'">Large</ui-chip>
  <ui-chip [value]="'XL'">Extra Large</ui-chip>
</ui-chips>

ChecklistComponent

Multi-select checkbox group returning an array of selected values.

Selector: ui-checklist

Outputs:

  • value: ModelSignal<unknown[]> - Array of selected values

Usage:

selectedFruits: string[] = ['apple', 'banana'];
<ui-checklist [(ngModel)]="selectedFruits">
  <label class="ui-checkbox-label">
    <ui-checkbox>
      <input type="checkbox" [uiChecklistValue]="'apple'">
    </ui-checkbox>
    Apple
  </label>
  <label class="ui-checkbox-label">
    <ui-checkbox>
      <input type="checkbox" [uiChecklistValue]="'banana'">
    </ui-checkbox>
    Banana
  </label>
</ui-checklist>

TextFieldComponent

Container component for text input fields with consistent styling.

Selector: ui-text-field

Inputs:

  • size: TextFieldSize - Field size ('small' | 'medium' | 'large'). Default: 'medium'

Content Projection:

  • Requires InputControlDirective on child input element

Usage:

<ui-text-field size="medium">
  <input
    type="text"
    uiInputControl
    formControlName="email"
    placeholder="Enter email"
  />
</ui-text-field>

InlineInputComponent

Compact inline text input for space-constrained interfaces.

Selector: ui-inline-input

Inputs:

  • size: InlineInputSize - Field size ('small' | 'medium'). Default: 'medium'

Usage:

<ui-inline-input size="small">
  <input type="text" uiInputControl [(ngModel)]="quantity" />
</ui-inline-input>

ListboxDirective

Wrapper directive for Angular CDK listbox with ISA styling.

Selector: [uiListbox]

Inputs (via CDK):

  • value: any - Selected value(s)
  • compareWith: (o1: any, o2: any) => boolean - Comparison function
  • disabled: boolean - Disabled state

Outputs (via CDK):

  • valueChange: EventEmitter - Emits when selection changes

Usage:

<div uiListbox [value]="selectedItem" (valueChange)="handleChange($event)">
  <div uiListboxItem [value]="item1">Item 1</div>
  <div uiListboxItem [value]="item2">Item 2</div>
</div>

Usage Examples

Reactive Form with Validation

import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import {
  TextFieldComponent,
  TextFieldContainerComponent,
  TextFieldErrorsComponent,
  InputControlDirective,
  CheckboxComponent,
  DropdownButtonComponent,
  DropdownOptionComponent
} from '@isa/ui/input-controls';

@Component({
  selector: 'app-user-form',
  imports: [
    ReactiveFormsModule,
    TextFieldComponent,
    TextFieldContainerComponent,
    TextFieldErrorsComponent,
    InputControlDirective,
    CheckboxComponent,
    DropdownButtonComponent,
    DropdownOptionComponent
  ],
  template: `
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <ui-text-field-container>
        <label>Email</label>
        <ui-text-field>
          <input
            type="email"
            uiInputControl
            formControlName="email"
            placeholder="user@example.com"
          />
        </ui-text-field>
        <ui-text-field-errors />
      </ui-text-field-container>

      <ui-dropdown formControlName="role" label="Select Role">
        <ui-dropdown-option [value]="'admin'">Administrator</ui-dropdown-option>
        <ui-dropdown-option [value]="'user'">User</ui-dropdown-option>
      </ui-dropdown>

      <label class="ui-checkbox-label">
        <ui-checkbox>
          <input type="checkbox" formControlName="acceptTerms" />
        </ui-checkbox>
        I accept the terms and conditions
      </label>

      <button type="submit" [disabled]="!userForm.valid">Submit</button>
    </form>
  `
})
export class UserFormComponent {
  #fb = inject(FormBuilder);

  userForm = this.#fb.group({
    email: ['', [Validators.required, Validators.email]],
    role: ['user', Validators.required],
    acceptTerms: [false, Validators.requiredTrue]
  });

  onSubmit() {
    if (this.userForm.valid) {
      console.log('Form submitted:', this.userForm.value);
    }
  }
}

Form Integration

Reactive Forms

All components implement ControlValueAccessor and work with FormControl:

import { FormControl, Validators } from '@angular/forms';

emailControl = new FormControl('', [Validators.required, Validators.email]);
roleControl = new FormControl<string>('user');
agreeControl = new FormControl(false, Validators.requiredTrue);
<ui-text-field>
  <input uiInputControl [formControl]="emailControl" />
</ui-text-field>

<ui-dropdown [formControl]="roleControl" label="Role">...</ui-dropdown>

<ui-checkbox><input [formControl]="agreeControl" /></ui-checkbox>

Template-Driven Forms

Use ngModel for two-way binding:

<ui-text-field>
  <input uiInputControl [(ngModel)]="email" name="email" />
</ui-text-field>

<ui-dropdown [(ngModel)]="role" name="role" label="Role">...</ui-dropdown>

Accessibility

Keyboard Navigation

All components support comprehensive keyboard navigation:

Dropdown:

  • Arrow Down/Up - Navigate through options
  • Enter - Select highlighted option and close
  • Escape - Close dropdown without selection
  • Tab - Move focus to next element

Listbox:

  • Arrow Down/Up - Navigate items
  • Home/End - Jump to first/last item
  • Space/Enter - Select item

Checkbox/Checklist:

  • Space - Toggle checkbox state
  • Tab - Move between checkboxes

ARIA Attributes

Components include proper ARIA attributes for screen readers:

<!-- Dropdown -->
<ui-dropdown
  role="combobox"
  aria-haspopup="listbox"
  [attr.aria-expanded]="isOpen()">
</ui-dropdown>

<!-- Listbox -->
<div uiListbox role="listbox">
  <div uiListboxItem role="option"></div>
</div>

Testing

The library uses Jest with Spectator for testing.

Running Tests

# Run tests for this library
npx nx test ui-input-controls --skip-nx-cache

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

# Run tests in watch mode
npx nx test ui-input-controls --watch

Test Coverage

The library includes comprehensive unit tests covering:

  • Component rendering - All visual states and variants
  • Form integration - ControlValueAccessor implementation
  • Keyboard navigation - Arrow keys, Enter, Escape, Tab
  • Validation - Error states and validation display
  • Accessibility - ARIA attributes and screen reader support
  • User interactions - Click, keyboard, focus events

Architecture Notes

Design Patterns

1. ControlValueAccessor Pattern

All form controls implement the CVA interface for seamless Angular Forms integration.

Benefits:

  • Works with both reactive and template-driven forms
  • Automatic validation integration
  • Consistent API across all controls

2. Signal-Based Reactivity

Components use Angular signals for reactive state management:

appearance = input<CheckboxAppearance>(CheckboxAppearance.Checkbox);
appearanceClass = computed(() =>
  this.appearance() === CheckboxAppearance.Bullet
    ? 'ui-checkbox__bullet'
    : 'ui-checkbox__checkbox'
);

3. CDK Integration

Leverages Angular CDK for complex interactions:

  • Overlay - Dropdown positioning (CdkConnectedOverlay)
  • A11y - Keyboard navigation (ActiveDescendantKeyManager)
  • Listbox - Accessible list selection (CdkListbox)

Performance Considerations

  1. OnPush Change Detection - All components use OnPush strategy
  2. Signal Reactivity - Computed signals prevent unnecessary re-renders
  3. KeyManager - Efficient keyboard navigation without DOM queries

Dependencies

Required Libraries

  • @angular/core - Angular framework
  • @angular/forms - Forms module
  • @angular/cdk/a11y - Accessibility utilities
  • @angular/cdk/listbox - Listbox primitive
  • @angular/cdk/overlay - Overlay positioning
  • @angular/cdk/collections - SelectionModel
  • @ng-icons/core - Icon system
  • @isa/icons - ISA icon library
  • lodash - Utility functions (isEqual)

Path Alias

Import from: @isa/ui/input-controls

License

Internal ISA Frontend library - not for external distribution.