Widgets

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Widget Examples

This page provides comprehensive examples of widgets used throughout our monorepo applications. All examples are based on real implementations from our Space Map, Editor Web, and UX Test applications.

🧩 Multi-Select Widget

Real Implementation from Space Map

The multi-select component demonstrates advanced form interaction and data binding patterns.

// From apps/space-map/src/app/features/space-editor/components/create-page-dialog/
@Component({
  selector: 'app-create-page-dialog',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    UxMultiSelectComponent
  ],
  template: `
    <div class="form-group">
      <label for="galaxyId">Galaxies</label>
      <div class="galaxy-multiselect-container">
        <ux-multi-select
          [options]="galaxyOptions"
          [(ngModel)]="selectedGalaxies"
          name="selectedGalaxies"
          placeholder="-- Select Galaxies --"
          (selectionChange)="onGalaxySelectionChange($event)">
        </ux-multi-select>
        <small class="form-hint">A page can belong to multiple galaxies.</small>
      </div>
    </div>
  `
})
export class CreatePageDialogComponent {
  galaxyOptions: SelectOption[] = [];
  selectedGalaxies: string[] = [];
  
  onGalaxySelectionChange(values: string[]): void {
    this.selectedGalaxies = values;
    console.log('Selected galaxies:', values);
  }
}

Key Features Demonstrated

🎨 Theme Integration Widgets

Cosmic-Themed UI Components

Examples from Space Map showing theme system integration:

// Theme-aware widget styling
.space-card {
  background: var(--ux-surface);
  border: 1px solid var(--ux-outline);
  border-radius: var(--ux-border-radius);
  box-shadow: var(--ux-shadow-md);
  padding: var(--ux-spacing-lg);
}

.galaxy-multiselect-container {
  .form-hint {
    color: var(--ux-on-surface-variant);
    font-size: var(--ux-text-sm);
    margin-top: var(--ux-spacing-xs);
  }
}

Utility Class Usage

<!-- Using theme utility classes -->
<div class="ux-bg-primary ux-text-on-primary ux-p-lg ux-rounded-lg">
  <h3 class="ux-text-lg ux-font-semibold">Cosmic Header</h3>
  <p class="ux-text-sm ux-opacity-80">Theme-integrated content</p>
</div>

🗃️ Database Integration Widgets

Reactive Data Widgets

Examples showing database service integration with reactive patterns:

// Database-connected widget component
@Component({
  selector: 'app-page-list-widget',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="page-list-widget">
      <h3>Pages</h3>
      <div class="page-items">
        <div 
          *ngFor="let page of pages$ | async" 
          class="page-item ux-bg-surface ux-p-md ux-rounded ux-shadow-sm">
          <h4>{{ page.title }}</h4>
          <p>{{ page.description }}</p>
          <div class="page-meta">
            <span class="world">{{ page.worldId }}</span>
            <span class="galaxy">{{ page.galaxyId }}</span>
          </div>
        </div>
      </div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PageListWidget {
  pages$ = this.dbService.pages$;
  
  constructor(private dbService: SpaceMapDbService) {}
}

Key Database Patterns

📱 Responsive Widget Examples

Adaptive Layout Widgets

Widgets that respond to different screen sizes:

// Responsive widget with breakpoint awareness
@Component({
  selector: 'app-responsive-dashboard-widget',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="dashboard-widget" [class]="layoutClass">
      <div class="widget-header">
        <h3>{{ title }}</h3>
        <button class="collapse-btn" (click)="toggleCollapse()">
          {{ isCollapsed ? '▼' : '▲' }}
        </button>
      </div>
      <div class="widget-content" *ngIf="!isCollapsed">
        <ng-content></ng-content>
      </div>
    </div>
  `,
  styles: [`
    .dashboard-widget {
      border: 1px solid var(--ux-outline);
      border-radius: var(--ux-border-radius);
      background: var(--ux-surface);
      
      &.compact {
        .widget-header { padding: var(--ux-spacing-sm); }
        .widget-content { padding: var(--ux-spacing-sm); }
      }
      
      &.comfortable {
        .widget-header { padding: var(--ux-spacing-md); }
        .widget-content { padding: var(--ux-spacing-lg); }
      }
    }
    
    @media (max-width: 768px) {
      .dashboard-widget {
        margin-bottom: var(--ux-spacing-md);
      }
    }
  `]
})
export class ResponsiveDashboardWidget {
  @Input() title: string = '';
  @Input() layoutMode: 'compact' | 'comfortable' = 'comfortable';
  
  isCollapsed = false;
  
  get layoutClass(): string {
    return this.layoutMode;
  }
  
  toggleCollapse(): void {
    this.isCollapsed = !this.isCollapsed;
  }
}

🔧 Form Widgets

Advanced Form Controls

Complex form widgets with validation and state management:

// Advanced form widget with validation
@Component({
  selector: 'app-page-form-widget',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    UxMultiSelectComponent
  ],
  template: `
    <form [formGroup]="pageForm" (ngSubmit)="onSubmit()">
      <div class="form-grid">
        <div class="form-field">
          <label for="title">Title *</label>
          <input 
            type="text" 
            id="title" 
            formControlName="title"
            class="form-control"
            [class.error]="titleControl?.invalid && titleControl?.touched">
          <div class="field-error" *ngIf="titleControl?.invalid && titleControl?.touched">
            Title is required
          </div>
        </div>
        
        <div class="form-field">
          <label for="world">World</label>
          <select id="world" formControlName="worldId" class="form-control">
            <option value="">Select World</option>
            <option *ngFor="let world of worlds" [value]="world.id">
              {{ world.name }}
            </option>
          </select>
        </div>
        
        <div class="form-field full-width">
          <label>Galaxies</label>
          <ux-multi-select
            [options]="galaxyOptions"
            [value]="galaxiesControl?.value || []"
            (selectionChange)="onGalaxiesChange($event)">
          </ux-multi-select>
        </div>
      </div>
      
      <div class="form-actions">
        <button type="button" class="btn-secondary" (click)="onCancel()">
          Cancel
        </button>
        <button type="submit" class="btn-primary" [disabled]="pageForm.invalid">
          Save Page
        </button>
      </div>
    </form>
  `
})
export class PageFormWidget {
  @Input() worlds: WorldGroup[] = [];
  @Input() galaxyOptions: SelectOption[] = [];
  @Output() submitted = new EventEmitter<PageFormData>();
  @Output() cancelled = new EventEmitter<void>();
  
  pageForm: FormGroup;
  
  constructor(private fb: FormBuilder) {
    this.pageForm = this.createForm();
  }
  
  private createForm(): FormGroup {
    return this.fb.group({
      title: ['', [Validators.required, Validators.minLength(2)]],
      description: [''],
      worldId: [''],
      galaxyIds: [[]]
    });
  }
  
  get titleControl() { return this.pageForm.get('title'); }
  get galaxiesControl() { return this.pageForm.get('galaxyIds'); }
  
  onGalaxiesChange(galaxyIds: string[]): void {
    this.galaxiesControl?.setValue(galaxyIds);
  }
  
  onSubmit(): void {
    if (this.pageForm.valid) {
      this.submitted.emit(this.pageForm.value);
    }
  }
  
  onCancel(): void {
    this.cancelled.emit();
  }
}

📊 Data Visualization Widgets

Chart and Display Widgets

Examples of data presentation widgets:

// Simple metrics display widget
@Component({
  selector: 'app-metrics-widget',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="metrics-widget ux-bg-surface ux-p-lg ux-rounded-lg">
      <h3 class="widget-title ux-text-lg ux-font-semibold ux-mb-md">
        {{ title }}
      </h3>
      <div class="metrics-grid">
        <div 
          *ngFor="let metric of metrics" 
          class="metric-item ux-text-center ux-p-md ux-bg-primary-light ux-rounded">
          <div class="metric-value ux-text-2xl ux-font-bold ux-text-primary">
            {{ metric.value }}
          </div>
          <div class="metric-label ux-text-sm ux-text-on-surface-variant">
            {{ metric.label }}
          </div>
        </div>
      </div>
    </div>
  `,
  styles: [`
    .metrics-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
      gap: var(--ux-spacing-md);
    }
    
    .metric-item {
      transition: transform 0.2s ease;
      
      &:hover {
        transform: translateY(-2px);
      }
    }
  `]
})
export class MetricsWidget {
  @Input() title: string = 'Metrics';
  @Input() metrics: MetricData[] = [];
}

interface MetricData {
  label: string;
  value: string | number;
  trend?: 'up' | 'down' | 'neutral';
}

🔗 Widget Composition

Composable Widget System

Example of how widgets work together:

// Container component that composes multiple widgets
@Component({
  selector: 'app-dashboard-container',
  standalone: true,
  imports: [
    CommonModule,
    MetricsWidget,
    PageListWidget,
    ResponsiveDashboardWidget
  ],
  template: `
    <div class="dashboard-container">
      <div class="dashboard-header">
        <h2>Space Map Dashboard</h2>
      </div>
      
      <div class="dashboard-grid">
        <app-responsive-dashboard-widget 
          title="Statistics" 
          layoutMode="comfortable">
          <app-metrics-widget 
            title="Content Metrics"
            [metrics]="contentMetrics">
          </app-metrics-widget>
        </app-responsive-dashboard-widget>
        
        <app-responsive-dashboard-widget 
          title="Recent Pages" 
          layoutMode="comfortable">
          <app-page-list-widget></app-page-list-widget>
        </app-responsive-dashboard-widget>
        
        <app-responsive-dashboard-widget 
          title="Quick Actions" 
          layoutMode="compact">
          <div class="quick-actions">
            <button class="ux-btn ux-btn-primary" (click)="createPage()">
              New Page
            </button>
            <button class="ux-btn ux-btn-outline" (click)="createGalaxy()">
              New Galaxy
            </button>
          </div>
        </app-responsive-dashboard-widget>
      </div>
    </div>
  `
})
export class DashboardContainer {
  contentMetrics: MetricData[] = [
    { label: 'Total Pages', value: 42 },
    { label: 'Galaxies', value: 8 },
    { label: 'Worlds', value: 3 }
  ];
  
  createPage(): void {
    // Implementation
  }
  
  createGalaxy(): void {
    // Implementation
  }
}

🎯 Best Practices Demonstrated

Performance Optimization

Accessibility

Type Safety

🔗 Related Resources

These widget examples demonstrate production-ready patterns that can be adapted and extended for any application in our monorepo ecosystem.