Files
Lorenz Hilpert 2b5da00249 feat(checkout): add reward order confirmation feature with schema migrations
- Add new reward-order-confirmation feature library with components and store
- Implement checkout completion orchestrator service for order finalization
- Migrate checkout/oms/crm models to Zod schemas for better type safety
- Add order creation facade and display order schemas
- Update shopping cart facade with order completion flow
- Add comprehensive tests for shopping cart facade
- Update routing to include order confirmation page
2025-10-21 14:28:52 +02:00
..

@isa/ui/toolbar

A flexible toolbar container component for Angular applications with configurable sizing and content projection.

Overview

The Toolbar library provides a single component (ui-toolbar) that serves as a styled container for toolbar content. It supports two size variants (small and medium) and uses content projection to allow flexible composition of toolbar items such as buttons, inputs, and action controls.

Table of Contents

Features

  • Two size variants - Small and medium toolbar heights
  • Content projection - Full flexibility for toolbar content composition
  • ViewEncapsulation.None - Allows external styling
  • OnPush change detection - Optimized performance
  • Signal-based sizing - Reactive size computation with computed()
  • Host class bindings - Dynamic CSS class application
  • Standalone component - Modern Angular architecture

Quick Start

1. Import Component

import { Component } from '@angular/core';
import { ToolbarComponent } from '@isa/ui/toolbar';

@Component({
  selector: 'app-page-header',
  imports: [ToolbarComponent],
  template: `...`
})
export class PageHeaderComponent {}

2. Basic Usage

<ui-toolbar>
  <h1>Page Title</h1>
  <button>Action</button>
</ui-toolbar>

3. Size Variants

<!-- Medium toolbar (default) -->
<ui-toolbar>
  <span>Default medium toolbar</span>
</ui-toolbar>

<!-- Small toolbar -->
<ui-toolbar [size]="'small'">
  <span>Compact toolbar</span>
</ui-toolbar>

Component API

ToolbarComponent

Container component for toolbar content with size variants.

Selector

'ui-toolbar'

Inputs

Input Type Default Description
size ToolbarSize 'medium' Size variant of the toolbar

Size Options

export const ToolbarSize = {
  Small: 'small',
  Medium: 'medium',
} as const;

export type ToolbarSize = (typeof ToolbarSize)[keyof typeof ToolbarSize];

Computed Properties

Property Type Description
sizeClass() string Computed CSS class based on size (ui-toolbar__small or ui-toolbar__medium)

Host Bindings

  • class: ['ui-toolbar', sizeClass()] - Base class and size-specific class

Template

<ng-content></ng-content>

Encapsulation

  • Uses ViewEncapsulation.None to allow external styling of toolbar content

Usage Examples

Page Header Toolbar

import { Component } from '@angular/core';
import { ToolbarComponent } from '@isa/ui/toolbar';
import { ButtonComponent } from '@isa/ui/buttons';

@Component({
  selector: 'app-page-header',
  imports: [ToolbarComponent, ButtonComponent],
  template: `
    <ui-toolbar>
      <h1 class="page-title">Dashboard</h1>
      <div class="toolbar-actions">
        <ui-button (click)="refresh()">Refresh</ui-button>
        <ui-button appearance="accent" (click)="create()">Create</ui-button>
      </div>
    </ui-toolbar>
  `,
  styles: [`
    .page-title {
      flex: 1;
      margin: 0;
    }
    .toolbar-actions {
      display: flex;
      gap: 0.5rem;
    }
  `]
})
export class PageHeaderComponent {
  refresh() { /* ... */ }
  create() { /* ... */ }
}

Search Toolbar

import { Component } from '@angular/core';
import { ToolbarComponent, ToolbarSize } from '@isa/ui/toolbar';
import { TextFieldComponent, InputControlDirective } from '@isa/ui/input-controls';

@Component({
  selector: 'app-search-toolbar',
  imports: [ToolbarComponent, TextFieldComponent, InputControlDirective],
  template: `
    <ui-toolbar [size]="ToolbarSize.Small">
      <ui-text-field>
        <input uiInputControl type="text" placeholder="Search..." />
      </ui-text-field>
      <button (click)="search()">Search</button>
    </ui-toolbar>
  `
})
export class SearchToolbarComponent {
  readonly ToolbarSize = ToolbarSize;

  search() { /* ... */ }
}

Filter Toolbar with Multiple Controls

import { Component, signal } from '@angular/core';
import { ToolbarComponent } from '@isa/ui/toolbar';
import { DropdownButtonComponent, DropdownOptionComponent } from '@isa/ui/input-controls';

@Component({
  selector: 'app-filter-toolbar',
  imports: [ToolbarComponent, DropdownButtonComponent, DropdownOptionComponent],
  template: `
    <ui-toolbar>
      <span class="filter-label">Filters:</span>

      <ui-dropdown [(value)]="selectedStatus" label="Status">
        <ui-dropdown-option [value]="'all'">All</ui-dropdown-option>
        <ui-dropdown-option [value]="'active'">Active</ui-dropdown-option>
        <ui-dropdown-option [value]="'inactive'">Inactive</ui-dropdown-option>
      </ui-dropdown>

      <ui-dropdown [(value)]="selectedCategory" label="Category">
        <ui-dropdown-option [value]="'all'">All</ui-dropdown-option>
        <ui-dropdown-option [value]="'electronics'">Electronics</ui-dropdown-option>
        <ui-dropdown-option [value]="'books'">Books</ui-dropdown-option>
      </ui-dropdown>

      <button (click)="resetFilters()">Reset</button>
    </ui-toolbar>
  `,
  styles: [`
    .filter-label {
      font-weight: 600;
      margin-right: 0.5rem;
    }
  `]
})
export class FilterToolbarComponent {
  selectedStatus = signal('all');
  selectedCategory = signal('all');

  resetFilters() {
    this.selectedStatus.set('all');
    this.selectedCategory.set('all');
  }
}

Dynamic Size Toolbar

import { Component, signal } from '@angular/core';
import { ToolbarComponent, ToolbarSize } from '@isa/ui/toolbar';

@Component({
  selector: 'app-dynamic-toolbar',
  imports: [ToolbarComponent],
  template: `
    <ui-toolbar [size]="currentSize()">
      <span>Toolbar Content</span>
      <button (click)="toggleSize()">Toggle Size</button>
    </ui-toolbar>
  `
})
export class DynamicToolbarComponent {
  currentSize = signal<ToolbarSize>(ToolbarSize.Medium);

  toggleSize() {
    const newSize = this.currentSize() === ToolbarSize.Medium
      ? ToolbarSize.Small
      : ToolbarSize.Medium;
    this.currentSize.set(newSize);
  }
}

Toolbar with Icons and Actions

import { Component } from '@angular/core';
import { ToolbarComponent } from '@isa/ui/toolbar';
import { NgIcon } from '@ng-icons/core';
import { ButtonComponent } from '@isa/ui/buttons';

@Component({
  selector: 'app-actions-toolbar',
  imports: [ToolbarComponent, ButtonComponent, NgIcon],
  template: `
    <ui-toolbar>
      <div class="toolbar-section">
        <ng-icon name="isaActionMenu" size="1.5rem"></ng-icon>
        <span class="toolbar-title">Orders</span>
      </div>

      <div class="toolbar-section">
        <ui-button (click)="export()">
          <ng-icon name="isaActionDownload"></ng-icon>
          Export
        </ui-button>
        <ui-button appearance="accent" (click)="createOrder()">
          <ng-icon name="isaActionPlus"></ng-icon>
          New Order
        </ui-button>
      </div>
    </ui-toolbar>
  `,
  styles: [`
    .toolbar-section {
      display: flex;
      align-items: center;
      gap: 0.75rem;
    }
    .toolbar-title {
      font-size: 1.25rem;
      font-weight: 600;
    }
  `]
})
export class ActionsToolbarComponent {
  export() { /* ... */ }
  createOrder() { /* ... */ }
}

Responsive Toolbar with Breakpoints

import { Component, computed } from '@angular/core';
import { ToolbarComponent, ToolbarSize } from '@isa/ui/toolbar';
import { breakpoint, Breakpoint } from '@isa/ui/layout';

@Component({
  selector: 'app-responsive-toolbar',
  imports: [ToolbarComponent],
  template: `
    <ui-toolbar [size]="toolbarSize()">
      @if (isDesktop()) {
        <h1>Full Desktop Title</h1>
        <div class="actions">
          <button>Action 1</button>
          <button>Action 2</button>
          <button>Action 3</button>
        </div>
      } @else {
        <h1>Mobile</h1>
        <button>Menu</button>
      }
    </ui-toolbar>
  `
})
export class ResponsiveToolbarComponent {
  isDesktop = breakpoint([Breakpoint.Desktop, Breakpoint.DekstopL, Breakpoint.DekstopXL]);

  toolbarSize = computed(() =>
    this.isDesktop() ? ToolbarSize.Medium : ToolbarSize.Small
  );
}

Styling and Customization

CSS Classes

The component exposes the following CSS classes for styling:

/* Base toolbar class */
.ui-toolbar {
  /* Base toolbar styles */
}

/* Medium toolbar (default) */
.ui-toolbar__medium {
  /* Medium size styles */
}

/* Small toolbar */
.ui-toolbar__small {
  /* Small size styles */
}

Example Styling

// Base toolbar styling
.ui-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1rem;
  background-color: var(--isa-surface-100);
  border-bottom: 1px solid var(--isa-border-200);

  // Medium toolbar (default height)
  &.ui-toolbar__medium {
    height: 4rem;
    padding: 0 1.5rem;
  }

  // Small toolbar (compact height)
  &.ui-toolbar__small {
    height: 3rem;
    padding: 0 1rem;
    font-size: 0.875rem;
  }
}

// Toolbar sections and layouts
.ui-toolbar {
  .toolbar-section {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  .toolbar-title {
    font-weight: 600;
    font-size: 1.125rem;
  }

  .toolbar-actions {
    display: flex;
    gap: 0.5rem;
    margin-left: auto;
  }
}

Dark Theme Example

// Dark mode toolbar
.dark-theme {
  .ui-toolbar {
    background-color: var(--isa-surface-900);
    border-bottom-color: var(--isa-border-700);
    color: var(--isa-text-inverse);
  }
}

Architecture Notes

Size Management

The component uses Angular signals for reactive size management:

size = input<ToolbarSize>('medium');

sizeClass = computed(() => `ui-toolbar__${this.size()}`);

Benefits:

  • Automatic updates - Size changes trigger class recomputation
  • Type safety - ToolbarSize type ensures valid values
  • Performance - OnPush detection with signals

ViewEncapsulation.None

The component uses ViewEncapsulation.None to allow external styling:

@Component({
  encapsulation: ViewEncapsulation.None
})

This enables:

  • Parent components can style toolbar content
  • Global styles can target toolbar elements
  • Flexible theme integration

Content Projection

The toolbar uses simple content projection for maximum flexibility:

<ng-content></ng-content>

This allows any content structure:

  • Buttons, inputs, dropdowns
  • Custom components
  • Icons and text
  • Complex layouts

Standalone Architecture

The component is fully standalone with no dependencies:

@Component({
  standalone: true,
  imports: []
})

Testing

The library uses Jest for testing.

Running Tests

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

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

# Run tests in watch mode
npx nx test ui-toolbar --watch

Test Coverage

The library should include tests covering:

  • Component rendering - Verifies toolbar renders correctly
  • Size variants - Tests both small and medium sizes
  • Content projection - Validates content appears correctly
  • Class bindings - Tests dynamic class application
  • Signal reactivity - Tests size changes update classes

Example Test

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ToolbarComponent, ToolbarSize } from './toolbar.component';

describe('ToolbarComponent', () => {
  let component: ToolbarComponent;
  let fixture: ComponentFixture<ToolbarComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ToolbarComponent]
    });
    fixture = TestBed.createComponent(ToolbarComponent);
    component = fixture.componentInstance;
  });

  it('should apply medium size by default', () => {
    fixture.detectChanges();
    expect(component.sizeClass()).toBe('ui-toolbar__medium');
  });

  it('should apply small size when specified', () => {
    component.size = ToolbarSize.Small;
    fixture.detectChanges();
    expect(component.sizeClass()).toBe('ui-toolbar__small');
  });

  it('should project content correctly', () => {
    const compiled = fixture.nativeElement;
    compiled.innerHTML = '<span>Test Content</span>';
    fixture.detectChanges();
    expect(compiled.textContent).toContain('Test Content');
  });
});

Dependencies

Required Libraries

  • @angular/core - Angular framework (v20.1.2)

Path Alias

Import from: @isa/ui/toolbar

No External Dependencies

This component has no external dependencies beyond Angular core, making it lightweight and easy to integrate.

Best Practices

  1. Consistent Sizing - Use the same toolbar size throughout similar contexts (e.g., all page headers use medium)
  2. Flexbox Layouts - Structure toolbar content using flexbox for responsive behavior
  3. Semantic HTML - Use appropriate HTML elements within the toolbar (headings, buttons, etc.)
  4. Accessibility - Ensure toolbar controls are keyboard accessible and have proper ARIA labels
  5. Content Organization - Group related toolbar items visually using spacing and dividers
  6. Responsive Design - Consider using the breakpoint service to adapt toolbar content for different screen sizes

Common Patterns

Left-Right Layout

<ui-toolbar>
  <div class="toolbar-left">
    <h1>Title</h1>
    <span>Subtitle</span>
  </div>
  <div class="toolbar-right">
    <button>Action 1</button>
    <button>Action 2</button>
  </div>
</ui-toolbar>

<style>
  .ui-toolbar {
    display: flex;
    justify-content: space-between;
  }
</style>

Three-Section Layout

<ui-toolbar>
  <div class="toolbar-left">Left content</div>
  <div class="toolbar-center">Center content</div>
  <div class="toolbar-right">Right content</div>
</ui-toolbar>

<style>
  .ui-toolbar {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 1rem;
  }
  .toolbar-center {
    justify-self: center;
  }
  .toolbar-right {
    justify-self: end;
  }
</style>

License

Internal ISA Frontend library - not for external distribution.