mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
313 lines
6.6 KiB
Markdown
313 lines
6.6 KiB
Markdown
# 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
|
|
|
|
```typescript
|
|
import { QuantityControlComponent } from '@isa/shared/quantity-control';
|
|
|
|
@Component({
|
|
// ...
|
|
imports: [QuantityControlComponent],
|
|
})
|
|
```
|
|
|
|
## Basic Usage
|
|
|
|
### Standalone
|
|
|
|
```html
|
|
<shared-quantity-control [value]="quantity" />
|
|
```
|
|
|
|
### With Reactive Forms
|
|
|
|
```typescript
|
|
export class MyComponent {
|
|
quantityControl = new FormControl(1);
|
|
}
|
|
```
|
|
|
|
```html
|
|
<shared-quantity-control [formControl]="quantityControl" />
|
|
```
|
|
|
|
### With Template-Driven Forms
|
|
|
|
```html
|
|
<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)
|
|
|
|
```html
|
|
<shared-quantity-control
|
|
[(value)]="quantity"
|
|
[min]="1"
|
|
[presetLimit]="10"
|
|
/>
|
|
```
|
|
**Dropdown:** 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Edit ✅
|
|
|
|
---
|
|
|
|
### Start From Zero
|
|
|
|
```html
|
|
<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)
|
|
|
|
```html
|
|
<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)
|
|
|
|
```html
|
|
<shared-quantity-control
|
|
[(value)]="quantity"
|
|
[min]="1"
|
|
[max]="50"
|
|
[presetLimit]="20"
|
|
/>
|
|
```
|
|
**Dropdown:** 1, 2, 3, ... 20, Edit ✅ (allows 21-50)
|
|
|
|
---
|
|
|
|
### Custom Range (5-15)
|
|
|
|
```html
|
|
<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
|
|
|
|
```typescript
|
|
export class ShoppingCartComponent {
|
|
quantityControl = new FormControl(1, [
|
|
Validators.min(1),
|
|
Validators.max(99)
|
|
]);
|
|
}
|
|
```
|
|
|
|
```html
|
|
<shared-quantity-control
|
|
[formControl]="quantityControl"
|
|
[min]="1"
|
|
[max]="99"
|
|
[presetLimit]="10"
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
### Disabled State
|
|
|
|
```html
|
|
<shared-quantity-control
|
|
[(value)]="quantity"
|
|
[disabled]="true"
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
### Custom ARIA Label
|
|
|
|
```html
|
|
<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:
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```css
|
|
.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
|
|
|
|
```css
|
|
: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
|