# @isa/shared/product-format

Angular components for displaying product format information with icons and formatted text, supporting various media types like hardcover, paperback, audio, and digital formats.

## Overview

The Product Format library provides two complementary standalone components for displaying product format information. The `ProductFormatComponent` combines an icon with formatted text, while the `ProductFormatIconComponent` displays just the icon. Both components integrate with the `@isa/icons` library to render format-specific icons and support customizable styling through Tailwind CSS.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Supported Formats](#supported-formats)
- [Architecture Notes](#architecture-notes)
- [Testing](#testing)
- [Dependencies](#dependencies)

## Features

- **Combined format display** - Icon + text in a single component
- **Icon-only display** - Standalone icon component for compact layouts
- **Format-specific icons** - Automatic icon selection from ProductFormatIconGroup
- **Flexible text styling** - Regular or bold format detail text
- **Signal-based inputs** - Modern Angular signal inputs for reactivity
- **Case normalization** - Automatic lowercase conversion for icon names
- **Consistent sizing** - Standardized 1.5rem (24px) icon size
- **ISA design system** - Integration with ISA Tailwind color palette
- **E2E testing attributes** - Built-in `data-what` and `data-which` attributes
- **OnPush change detection** - Optimized performance
- **Standalone architecture** - Modern Angular components with explicit imports

## Quick Start

### 1. Display Format with Icon and Text

```typescript
import { Component } from '@angular/core';
import { ProductFormatComponent } from '@isa/shared/product-format';

@Component({
  selector: 'app-product-card',
  standalone: true,
  imports: [ProductFormatComponent],
  template: `
    <div class="product-info">
      <h3>{{ product.title }}</h3>

      <!-- Format with icon and text -->
      <shared-product-format
        [format]="product.format"
        [formatDetail]="product.formatDetail"
      />
    </div>
  `
})
export class ProductCardComponent {
  product = {
    title: 'The Great Gatsby',
    format: 'HC',  // Hardcover icon code
    formatDetail: 'Hardcover'
  };
}
```

### 2. Display Icon Only

```typescript
import { Component } from '@angular/core';
import { ProductFormatIconComponent } from '@isa/shared/product-format';

@Component({
  selector: 'app-product-list',
  standalone: true,
  imports: [ProductFormatIconComponent],
  template: `
    <div class="product-grid">
      @for (product of products; track product.id) {
        <div class="product-item">
          <!-- Icon only for compact display -->
          <shared-product-format-icon [format]="product.format" />
          <span>{{ product.title }}</span>
        </div>
      }
    </div>
  `
})
export class ProductListComponent {
  products = [
    { id: 1, format: 'HC', title: 'Book 1' },
    { id: 2, format: 'PB', title: 'Book 2' },
    { id: 3, format: 'EB', title: 'Book 3' }
  ];
}
```

### 3. Bold Format Detail

```typescript
import { Component } from '@angular/core';
import { ProductFormatComponent } from '@isa/shared/product-format';

@Component({
  selector: 'app-product-highlight',
  standalone: true,
  imports: [ProductFormatComponent],
  template: `
    <shared-product-format
      [format]="'HC'"
      [formatDetail]="'Limited Edition Hardcover'"
      [formatDetailsBold]="true"
    />
  `
})
export class ProductHighlightComponent {}
```

## Core Concepts

### Format Codes

Product formats are identified by short codes (typically 2 characters) that map to specific icons:

```typescript
// Common format codes
'HC'  → Hardcover
'PB'  → Paperback
'EB'  → E-Book
'AB'  → Audiobook
'CD'  → CD/Audio
'DL'  → Download
```

The format code is automatically converted to lowercase for icon lookup.

### Icon Integration

The components integrate with `@isa/icons` ProductFormatIconGroup:

```typescript
import { ProductFormatIconGroup } from '@isa/icons';
import { NgIcon, provideIcons } from '@ng-icons/core';

// Icons are automatically loaded from ProductFormatIconGroup
providers: [provideIcons(ProductFormatIconGroup)]
```

### Component Structure

Two components serve different display needs:

- **`ProductFormatComponent`**: Icon + text combination for detailed display
- **`ProductFormatIconComponent`**: Icon only for compact layouts

```typescript
// Full display
<shared-product-format format="HC" formatDetail="Hardcover" />
// Renders: [HC Icon] Hardcover

// Icon only
<shared-product-format-icon format="HC" />
// Renders: [HC Icon]
```

### Styling

Both components use ISA design system colors and consistent sizing:

- **Icon size**: `1.5rem` (24px) - `size="1.5rem"`
- **Icon color**: `text-isa-neutral-900` (dark neutral)
- **Text color**: `text-isa-secondary-900` (dark secondary)
- **Layout**: Flexbox with `gap-2` (0.5rem spacing)

## API Reference

### ProductFormatComponent

Combined icon and text display component for product formats.

**Selector:** `shared-product-format`

**Change Detection:** `OnPush`

**Standalone:** `true`

#### Inputs

##### `format` (required)
- **Type:** `InputSignal<string>`
- **Description:** Format code (e.g., 'HC', 'PB', 'EB')
- **Usage:** Determines which icon to display

##### `formatDetail` (required)
- **Type:** `InputSignal<string>`
- **Description:** Human-readable format description (e.g., 'Hardcover', 'Paperback')
- **Usage:** Displayed as text next to the icon

##### `formatDetailsBold` (optional)
- **Type:** `InputSignal<boolean>`
- **Default:** `false`
- **Description:** Whether to display format detail text in bold
- **Styling:**
  - `false`: `.isa-text-body-2-regular`
  - `true`: `.isa-text-body-2-bold`

#### Host Bindings

- **`class`**: `["flex", "items-center", "gap-2"]` - Flexbox layout with centered items
- **`data-what`**: `'product-format'` - E2E testing identifier
- **`data-which`**: `'shared-product-format'` - Component type identifier

#### Template Structure

```html
<shared-product-format-icon [format]="format()"></shared-product-format-icon>
<span
  [class.isa-text-body-2-regular]="!formatDetailsBold()"
  [class.isa-text-body-2-bold]="formatDetailsBold()"
  class="text-isa-secondary-900"
>
  {{ formatDetail() }}
</span>
```

#### Example

```typescript
<shared-product-format
  [format]="'HC'"
  [formatDetail]="'Hardcover'"
  [formatDetailsBold]="false"
/>
// Renders: [HC Icon] Hardcover (regular weight)

<shared-product-format
  [format]="'PB'"
  [formatDetail]="'Paperback'"
  [formatDetailsBold]="true"
/>
// Renders: [PB Icon] Paperback (bold weight)
```

### ProductFormatIconComponent

Icon-only display component for product formats.

**Selector:** `shared-product-format-icon`

**Standalone:** `true`

#### Inputs

##### `format` (required)
- **Type:** `InputSignal<string>`
- **Description:** Format code (e.g., 'HC', 'PB', 'EB')
- **Processing:** Automatically converted to lowercase via `formatLowerCase()` computed signal

#### Computed Properties

##### `formatLowerCase()`
- **Type:** `Signal<string>`
- **Returns:** Lowercase version of the format input
- **Example:** `'HC'` → `'hc'`, `'PB'` → `'pb'`
- **Purpose:** Icon names in ProductFormatIconGroup are lowercase

#### Host Bindings

- **`class`**: `["inline-block","text-isa-neutral-900", "size-6"]` - Inline block with neutral color and 24px size
- **`data-what`**: `'product-format-icon'` - E2E testing identifier
- **`data-which`**: `'shared-product-format-icon'` - Component type identifier

#### Template Structure

```html
<ng-icon [name]="formatLowerCase()" size="1.5rem"></ng-icon>
```

#### Example

```typescript
<shared-product-format-icon [format]="'HC'" />
// Renders: Hardcover icon (24x24px)

<shared-product-format-icon [format]="'EB'" />
// Renders: E-book icon (24x24px)
```

## Usage Examples

### Product Card with Format

```typescript
import { Component, input } from '@angular/core';
import { ProductFormatComponent } from '@isa/shared/product-format';
import { ProductImageDirective } from '@isa/shared/product-image';

interface Product {
  ean: string;
  title: string;
  author: string;
  format: string;
  formatDetail: string;
  price: number;
}

@Component({
  selector: 'app-product-card',
  standalone: true,
  imports: [ProductFormatComponent, ProductImageDirective],
  template: `
    <div class="product-card">
      <img
        sharedProductImage
        [ean]="product().ean"
        [imageWidth]="200"
        [imageHeight]="300"
        alt="{{ product().title }}"
      />

      <div class="product-info">
        <h3 class="isa-text-heading-4-bold">{{ product().title }}</h3>
        <p class="isa-text-body-2-regular">{{ product().author }}</p>

        <shared-product-format
          [format]="product().format"
          [formatDetail]="product().formatDetail"
        />

        <p class="price">{{ product().price | currency }}</p>
      </div>
    </div>
  `,
  styles: [`
    .product-card {
      display: flex;
      flex-direction: column;
      gap: 1rem;
      padding: 1rem;
      border: 1px solid var(--isa-neutral-300);
      border-radius: 0.5rem;
    }
  `]
})
export class ProductCardComponent {
  product = input.required<Product>();
}
```

### Product List with Icon Only

```typescript
import { Component } from '@angular/core';
import { ProductFormatIconComponent } from '@isa/shared/product-format';

@Component({
  selector: 'app-compact-product-list',
  standalone: true,
  imports: [ProductFormatIconComponent],
  template: `
    <table class="product-table">
      <thead>
        <tr>
          <th>Format</th>
          <th>Title</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>
        @for (product of products; track product.ean) {
          <tr>
            <td>
              <shared-product-format-icon [format]="product.format" />
            </td>
            <td>{{ product.title }}</td>
            <td>{{ product.price | currency }}</td>
          </tr>
        }
      </tbody>
    </table>
  `
})
export class CompactProductListComponent {
  products = [
    { ean: '1234567890123', format: 'HC', title: 'Book Title 1', price: 29.99 },
    { ean: '2345678901234', format: 'PB', title: 'Book Title 2', price: 14.99 },
    { ean: '3456789012345', format: 'EB', title: 'Book Title 3', price: 9.99 },
    { ean: '4567890123456', format: 'AB', title: 'Book Title 4', price: 19.99 }
  ];
}
```

### Format Filter/Facets

```typescript
import { Component, signal } from '@angular/core';
import { ProductFormatComponent } from '@isa/shared/product-format';

interface FormatFilter {
  code: string;
  label: string;
  count: number;
}

@Component({
  selector: 'app-format-filter',
  standalone: true,
  imports: [ProductFormatComponent],
  template: `
    <div class="filter-section">
      <h3 class="isa-text-heading-4-bold">Filter by Format</h3>

      <div class="format-options">
        @for (format of availableFormats; track format.code) {
          <label class="format-option">
            <input
              type="checkbox"
              [value]="format.code"
              (change)="toggleFormat(format.code)"
            />

            <shared-product-format
              [format]="format.code"
              [formatDetail]="format.label + ' (' + format.count + ')'"
              [formatDetailsBold]="isSelected(format.code)"
            />
          </label>
        }
      </div>
    </div>
  `,
  styles: [`
    .format-option {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      padding: 0.5rem;
      cursor: pointer;
    }

    .format-option:hover {
      background-color: var(--isa-neutral-100);
    }
  `]
})
export class FormatFilterComponent {
  availableFormats: FormatFilter[] = [
    { code: 'HC', label: 'Hardcover', count: 125 },
    { code: 'PB', label: 'Paperback', count: 342 },
    { code: 'EB', label: 'E-Book', count: 218 },
    { code: 'AB', label: 'Audiobook', count: 89 }
  ];

  selectedFormats = signal<Set<string>>(new Set());

  toggleFormat(code: string): void {
    this.selectedFormats.update(formats => {
      const newFormats = new Set(formats);
      if (newFormats.has(code)) {
        newFormats.delete(code);
      } else {
        newFormats.add(code);
      }
      return newFormats;
    });
  }

  isSelected(code: string): boolean {
    return this.selectedFormats().has(code);
  }
}
```

### Product Detail Page

```typescript
import { Component, signal } from '@angular/core';
import { ProductFormatComponent } from '@isa/shared/product-format';

interface ProductVariant {
  format: string;
  formatDetail: string;
  price: number;
  available: boolean;
}

@Component({
  selector: 'app-product-detail',
  standalone: true,
  imports: [ProductFormatComponent],
  template: `
    <div class="product-detail">
      <h2 class="isa-text-heading-2-bold">{{ productTitle }}</h2>

      <div class="format-selection">
        <h3 class="isa-text-heading-4-bold">Choose Format</h3>

        <div class="format-variants">
          @for (variant of variants; track variant.format) {
            <button
              class="variant-button"
              [class.selected]="selectedFormat() === variant.format"
              [disabled]="!variant.available"
              (click)="selectFormat(variant.format)"
            >
              <shared-product-format
                [format]="variant.format"
                [formatDetail]="variant.formatDetail"
                [formatDetailsBold]="selectedFormat() === variant.format"
              />

              <span class="variant-price">
                {{ variant.price | currency }}
              </span>

              @if (!variant.available) {
                <span class="unavailable">Out of Stock</span>
              }
            </button>
          }
        </div>
      </div>
    </div>
  `,
  styles: [`
    .variant-button {
      display: flex;
      align-items: center;
      justify-content: space-between;
      width: 100%;
      padding: 1rem;
      border: 2px solid var(--isa-neutral-300);
      border-radius: 0.5rem;
      cursor: pointer;
      transition: all 0.2s;
    }

    .variant-button:hover:not(:disabled) {
      border-color: var(--isa-accent-500);
    }

    .variant-button.selected {
      border-color: var(--isa-accent-500);
      background-color: var(--isa-accent-50);
    }

    .variant-button:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  `]
})
export class ProductDetailComponent {
  productTitle = 'The Great Gatsby';

  variants: ProductVariant[] = [
    { format: 'HC', formatDetail: 'Hardcover', price: 29.99, available: true },
    { format: 'PB', formatDetail: 'Paperback', price: 14.99, available: true },
    { format: 'EB', formatDetail: 'E-Book', price: 9.99, available: true },
    { format: 'AB', formatDetail: 'Audiobook', price: 19.99, available: false }
  ];

  selectedFormat = signal<string>('HC');

  selectFormat(format: string): void {
    this.selectedFormat.set(format);
  }
}
```

### Shopping Cart Item

```typescript
import { Component, input, output } from '@angular/core';
import { ProductFormatComponent } from '@isa/shared/product-format';

interface CartItem {
  id: number;
  title: string;
  format: string;
  formatDetail: string;
  quantity: number;
  price: number;
}

@Component({
  selector: 'app-cart-item',
  standalone: true,
  imports: [ProductFormatComponent],
  template: `
    <div class="cart-item">
      <div class="item-info">
        <h4 class="isa-text-body-1-bold">{{ item().title }}</h4>

        <shared-product-format
          [format]="item().format"
          [formatDetail]="item().formatDetail"
        />
      </div>

      <div class="item-controls">
        <div class="quantity-controls">
          <button (click)="decreaseQuantity()">-</button>
          <span>{{ item().quantity }}</span>
          <button (click)="increaseQuantity()">+</button>
        </div>

        <div class="item-price">
          {{ item().price * item().quantity | currency }}
        </div>

        <button (click)="remove()" class="remove-button">
          Remove
        </button>
      </div>
    </div>
  `
})
export class CartItemComponent {
  item = input.required<CartItem>();

  quantityChange = output<number>();
  removeItem = output<void>();

  increaseQuantity(): void {
    this.quantityChange.emit(this.item().quantity + 1);
  }

  decreaseQuantity(): void {
    if (this.item().quantity > 1) {
      this.quantityChange.emit(this.item().quantity - 1);
    }
  }

  remove(): void {
    this.removeItem.emit();
  }
}
```

### Format Legend/Guide

```typescript
import { Component } from '@angular/core';
import { ProductFormatComponent } from '@isa/shared/product-format';

@Component({
  selector: 'app-format-legend',
  standalone: true,
  imports: [ProductFormatComponent],
  template: `
    <div class="format-legend">
      <h3 class="isa-text-heading-4-bold">Format Guide</h3>

      <div class="legend-items">
        @for (format of formatLegend; track format.code) {
          <div class="legend-item">
            <shared-product-format
              [format]="format.code"
              [formatDetail]="format.label"
            />

            <p class="isa-text-body-2-regular text-isa-neutral-700">
              {{ format.description }}
            </p>
          </div>
        }
      </div>
    </div>
  `,
  styles: [`
    .legend-item {
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
      padding: 1rem;
      border-bottom: 1px solid var(--isa-neutral-200);
    }
  `]
})
export class FormatLegendComponent {
  formatLegend = [
    {
      code: 'HC',
      label: 'Hardcover',
      description: 'Durable hardbound book with rigid protective cover'
    },
    {
      code: 'PB',
      label: 'Paperback',
      description: 'Softcover book with flexible paper cover'
    },
    {
      code: 'EB',
      label: 'E-Book',
      description: 'Digital book format for e-readers and tablets'
    },
    {
      code: 'AB',
      label: 'Audiobook',
      description: 'Audio recording of book read aloud'
    }
  ];
}
```

## Supported Formats

### Common Media Formats

The library supports various product format codes through the ProductFormatIconGroup:

| Code | Format | Description |
|------|--------|-------------|
| `HC` | Hardcover | Hardbound books with rigid covers |
| `PB` | Paperback | Softcover books with flexible covers |
| `EB` | E-Book | Digital book formats |
| `AB` | Audiobook | Audio recordings of books |
| `CD` | CD/Audio | Physical audio discs |
| `DL` | Download | Digital download products |
| `BR` | Blu-ray | High-definition video discs |
| `DVD` | DVD | Standard definition video discs |

### Icon Naming Convention

All format codes are automatically converted to lowercase for icon lookup:

```typescript
format: 'HC'  →  icon name: 'hc'
format: 'PB'  →  icon name: 'pb'
format: 'EB'  →  icon name: 'eb'
```

This ensures consistent icon resolution regardless of input casing.

## Architecture Notes

### Design Patterns

#### Composition Pattern

`ProductFormatComponent` composes `ProductFormatIconComponent`:

```typescript
@Component({
  template: `
    <shared-product-format-icon [format]="format()"></shared-product-format-icon>
    <span>{{ formatDetail() }}</span>
  `,
  imports: [ProductFormatIconComponent]
})
export class ProductFormatComponent {}
```

**Benefits:**
- Code reuse - icon component used independently or within format component
- Single responsibility - each component has clear purpose
- Flexibility - consumers choose icon-only or icon+text display

#### Computed Signal for Case Normalization

Icon component uses computed signal for case conversion:

```typescript
formatLowerCase = computed(() => this.format()?.toLowerCase());
```

**Benefits:**
- Automatic reactivity when format input changes
- Efficient - only recalculates when format changes
- OnPush compatible
- No manual string manipulation in template

#### Host Class Binding

Both components use host bindings for consistent styling:

```typescript
// ProductFormatComponent
host: {
  '[class]': '["flex", "items-center", "gap-2"]',
  'data-what': 'product-format',
  'data-which': 'shared-product-format'
}

// ProductFormatIconComponent
host: {
  '[class]': '["inline-block","text-isa-neutral-900", "size-6"]',
  'data-what': 'product-format-icon',
  'data-which': 'shared-product-format-icon'
}
```

**Benefits:**
- Consistent styling without consumer configuration
- E2E testing attributes built-in
- Encapsulated component behavior

### OnPush Change Detection

`ProductFormatComponent` uses OnPush strategy:

```typescript
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
```

**Benefits:**
- Improved performance
- Works seamlessly with signal inputs
- Reduces unnecessary change detection cycles

### Icon Library Integration

The library integrates with `@ng-icons/core` and `@isa/icons`:

```typescript
import { NgIcon, provideIcons } from '@ng-icons/core';
import { ProductFormatIconGroup } from '@isa/icons';

providers: [provideIcons(ProductFormatIconGroup)]
```

**Benefits:**
- Centralized icon management
- Consistent icon rendering
- Automatic tree-shaking of unused icons

### Future Enhancements

Potential improvements identified:

1. **Dynamic Icon Size** - Input for configurable icon sizes
2. **Custom Icon Groups** - Support for additional icon sets
3. **Tooltip Integration** - Optional tooltips with format descriptions
4. **Format Validation** - Warning for unsupported format codes
5. **Accessibility** - ARIA labels for screen readers
6. **RTL Support** - Right-to-left language support
7. **Icon Color Variants** - Theme-based color customization

## Testing

The library uses **Jest** with **Spectator** for testing.

### Running Tests

```bash
# Run tests for this library
npx nx test shared-product-format --skip-nx-cache

# Run tests with coverage
npx nx test shared-product-format --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test shared-product-format --watch
```

### Test Structure

The library includes unit tests covering:

- **Component creation** - Tests component initialization
- **Input binding** - Tests format and formatDetail inputs
- **Icon rendering** - Tests ProductFormatIconComponent integration
- **Text rendering** - Tests formatDetail display
- **E2E attributes** - Tests data-what and data-which attributes
- **Case normalization** - Tests lowercase conversion

### Example Test

```typescript
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { ProductFormatComponent } from './product-format.component';
import { ProductFormatIconComponent } from './product-format-icon.component';

describe('ProductFormatComponent', () => {
  let spectator: Spectator<ProductFormatComponent>;
  const createComponent = createComponentFactory({
    component: ProductFormatComponent,
    imports: [ProductFormatIconComponent]
  });

  beforeEach(() => {
    spectator = createComponent({
      props: {
        format: 'HC',
        formatDetail: 'Hardcover'
      }
    });
  });

  it('should create', () => {
    expect(spectator.component).toBeTruthy();
  });

  it('should render ProductFormatIconComponent with correct format', () => {
    const icon = spectator.query(ProductFormatIconComponent);
    expect(icon).toBeTruthy();
    expect(icon?.format()).toBe('HC');
  });

  it('should render the formatDetail text', () => {
    expect(spectator.query('span')).toHaveText('Hardcover');
  });

  it('should set correct data attributes on host', () => {
    const host = spectator.element;
    expect(host).toHaveAttribute('data-what', 'product-format');
    expect(host).toHaveAttribute('data-which', 'shared-product-format');
  });
});
```

## Dependencies

### Required Libraries

- `@angular/core` - Angular framework for components and signals
- `@ng-icons/core` - Icon rendering engine
- `@isa/icons` - ISA icon library with ProductFormatIconGroup

### Path Alias

Import from: `@isa/shared/product-format`

### Exports

- `ProductFormatComponent` - Combined icon and text display
- `ProductFormatIconComponent` - Icon-only display

## Best Practices

### 1. Choose Appropriate Component

Use the right component for your layout:

```typescript
// Good - Combined component for detailed displays
<shared-product-format format="HC" formatDetail="Hardcover" />

// Good - Icon component for compact layouts
<shared-product-format-icon format="HC" />

// Avoid - Using combined component when space is limited
<div class="compact-list">
  <shared-product-format format="HC" formatDetail="Hardcover" /> <!-- Too verbose -->
</div>
```

### 2. Consistent Format Codes

Use consistent format codes across your application:

```typescript
// Good - Consistent codes
const FORMATS = {
  HARDCOVER: 'HC',
  PAPERBACK: 'PB',
  EBOOK: 'EB'
} as const;

// Avoid - Inconsistent codes
format: 'hardcover'  // Wrong - should be 'HC'
format: 'hc'         // Works but inconsistent with uppercase convention
```

### 3. Provide Meaningful Format Details

Use clear, user-friendly format descriptions:

```typescript
// Good - Clear descriptions
<shared-product-format format="HC" formatDetail="Hardcover" />
<shared-product-format format="AB" formatDetail="Audiobook (MP3)" />

// Avoid - Technical or unclear descriptions
<shared-product-format format="HC" formatDetail="HC Format" /> <!-- Redundant -->
<shared-product-format format="AB" formatDetail="Audio" /> <!-- Too vague -->
```

### 4. Include E2E Attributes

Add test attributes to parent containers for context:

```typescript
<div class="product-info" data-what="product-format-section">
  <shared-product-format
    [format]="product.format"
    [formatDetail]="product.formatDetail"
  />
</div>
```

### 5. Handle Missing Format Gracefully

Provide fallbacks for unknown formats:

```typescript
@Component({
  template: `
    @if (hasValidFormat()) {
      <shared-product-format
        [format]="product().format"
        [formatDetail]="product().formatDetail"
      />
    } @else {
      <span class="unknown-format">Format not available</span>
    }
  `
})
export class ProductComponent {
  product = input.required<Product>();

  hasValidFormat(): boolean {
    const format = this.product().format;
    return !!format && this.product().formatDetail !== undefined;
  }
}
```

## Performance Considerations

### OnPush Change Detection

The component uses OnPush strategy for optimal performance:
- Only re-renders when inputs change
- Minimal change detection overhead
- Works seamlessly with signals

### Icon Loading

Icons are loaded from ProductFormatIconGroup:
- Tree-shakeable - unused icons not included in bundle
- Lazy-loaded via ng-icons
- Cached after first render

### Signal-Based Reactivity

Uses computed signals for efficient updates:
- Only recalculates when format input changes
- No manual subscription management
- Automatic cleanup

## License

Internal ISA Frontend library - not for external distribution.
