Files
ISA-Frontend/libs/shared/quantity-control/README.md
2025-10-14 16:02:18 +00:00

6.6 KiB

Quantity Control

An accessible, feature-rich Angular quantity selector component with dropdown presets and manual input mode.

Features

  • Dropdown with presets - Quick selection from predefined values
  • Edit mode - Manual input for values beyond presets
  • Smart logic - Automatically shows/hides Edit based on constraints
  • Flexible range - Start from any value (0, 1, or custom)
  • Full accessibility - WCAG 2.1 AA compliant with screen reader support
  • Keyboard navigation - Arrow keys, Home, End, Enter, Escape
  • Form integration - Implements ControlValueAccessor
  • Type-safe - Full TypeScript support with proper validation

Installation

import { QuantityControlComponent } from '@isa/shared/quantity-control';

@Component({
  // ...
  imports: [QuantityControlComponent],
})

Basic Usage

Standalone

<shared-quantity-control [value]="quantity" />

With Reactive Forms

export class MyComponent {
  quantityControl = new FormControl(1);
}
<shared-quantity-control [formControl]="quantityControl" />

With Template-Driven Forms

<shared-quantity-control [(ngModel)]="quantity" />

API

Inputs

Input Type Default Description
value model<number> 1 Current quantity value (two-way binding)
disabled model<boolean> false Whether the control is disabled
min number 1 Minimum selectable value (starting point)
max number | undefined undefined Maximum selectable value (e.g., stock available)
presetLimit number 10 Number of preset options before requiring Edit
ariaLabel string undefined Custom ARIA label for accessibility

How It Works

Preset options generated: min to (min + presetLimit - 1)

Edit option shown when:

  • max is undefined (unlimited), OR
  • max > (min + presetLimit - 1) (stock exceeds presets)

Examples

Standard Use Case (1-10 with unlimited)

<shared-quantity-control
  [(value)]="quantity"
  [min]="1"
  [presetLimit]="10"
/>

Dropdown: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Edit


Start From Zero

<shared-quantity-control
  [(value)]="quantity"
  [min]="0"
  [presetLimit]="10"
/>

Dropdown: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, Edit


Limited Stock (No Edit)

<shared-quantity-control
  [(value)]="quantity"
  [min]="1"
  [max]="5"
  [presetLimit]="10"
/>

Dropdown: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 (No Edit - max is 5)


High Stock (With Edit)

<shared-quantity-control
  [(value)]="quantity"
  [min]="1"
  [max]="50"
  [presetLimit]="20"
/>

Dropdown: 1, 2, 3, ... 20, Edit (allows 21-50)


Custom Range (5-15)

<shared-quantity-control
  [(value)]="quantity"
  [min]="5"
  [presetLimit]="11"
  [max]="15"
/>

Dropdown: 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 (No Edit)


With Reactive Forms

export class ShoppingCartComponent {
  quantityControl = new FormControl(1, [
    Validators.min(1),
    Validators.max(99)
  ]);
}
<shared-quantity-control
  [formControl]="quantityControl"
  [min]="1"
  [max]="99"
  [presetLimit]="10"
/>

Disabled State

<shared-quantity-control
  [(value)]="quantity"
  [disabled]="true"
/>

Custom ARIA Label

<shared-quantity-control
  [(value)]="quantity"
  [ariaLabel]="'Select number of items to purchase'"
/>

Accessibility

The component implements the ARIA combobox pattern and is fully accessible:

  • Keyboard Navigation:

    • Space/Enter - Open dropdown
    • Arrow Up/Down - Navigate options
    • Home/End - Jump to first/last option
    • Enter/Space - Select option
    • Escape - Close dropdown or cancel edit
  • ARIA Attributes:

    • role="combobox" on host
    • role="listbox" on dropdown
    • role="option" on each item
    • aria-expanded, aria-haspopup, aria-controls
    • aria-activedescendant for keyboard focus
    • aria-selected for current value
  • Screen Reader Support:

    • Value changes announced
    • Edit mode transitions announced
    • Validation errors announced
  • E2E Testing:

    • data-what="quantity-control" on button
    • data-what="quantity-control-option" on options
    • data-which attributes with values

Validation

Automatic Validation

The component validates input in Edit mode:

// If min=1, entering 0 shows:
"Invalid quantity. Please enter a number greater than or equal to 1."

// If max=50, entering 100 shows:
"Invalid quantity. Maximum available is 50."

Form Validators

Use standard Angular validators with the form control:

quantityControl = new FormControl(1, [
  Validators.required,
  Validators.min(0),
  Validators.max(999),
]);

Behavior

Dropdown

  • Click button or press Space/Enter to open
  • Click option to select
  • Click outside (backdrop) to close
  • Press Escape to close

Edit Mode

  • Select "Edit" option from dropdown
  • Input field opens with current value pre-selected
  • Press Enter to save
  • Press Escape to cancel (reverts to original value)
  • Click outside (blur) to save

Styling

The component uses scoped CSS with these CSS classes:

.quantity-control-button       /* Main button */
.quantity-control-value        /* Value display */
.quantity-control-input        /* Edit mode input */
.options-list                  /* Dropdown container */
.quantity-control-option       /* Each option */
.quantity-control-option.active      /* Keyboard focused */
.quantity-control-option.selected    /* Current value */

Host Classes

:host.disabled    /* Disabled state */
:host.open        /* Dropdown open */
:host.edit-mode   /* Edit mode active */

Performance

  • OnPush change detection for optimal performance
  • Signal-based reactivity with computed values
  • Efficient keyboard manager from Angular CDK
  • Automatic cleanup on component destruction

Browser Support

  • Chrome/Edge (latest)
  • Firefox (latest)
  • Safari (latest)
  • Mobile browsers (iOS Safari, Chrome Android)

Dependencies

  • @angular/core ^20.1.2
  • @angular/forms ^20.1.2
  • @angular/cdk ^20.1.2
  • @ng-icons/core (for chevron icons)

Contributing

This component is part of the ISA Frontend monorepo. For changes:

  1. Update component code
  2. Update tests (when available)
  3. Update Storybook stories
  4. Update this README if API changes

License

Internal use only - ISA Frontend Project