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
- Type Safety: Uses
SelectOptionfrom@core-types - Two-way Data Binding:
[(ngModel)]integration - Event Handling: Selection change events
- Accessibility: Proper labeling and hints
🎨 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
- Reactive Streams: Using
Observablewith async pipe - OnPush Detection: Optimal performance pattern
- Service Injection: Proper dependency injection
- Type Safety: SafeAny patterns for flexible data
📱 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
- OnPush Change Detection: All widgets use OnPush strategy
- Reactive Patterns: Leverage observables with async pipe
- Lazy Loading: Components load only when needed
- Memory Management: Proper subscription handling
Accessibility
- Semantic HTML: Proper heading hierarchy and landmarks
- ARIA Labels: Screen reader support
- Keyboard Navigation: Focus management and keyboard shortcuts
- Color Contrast: Theme-compliant color usage
Type Safety
- SafeAny Usage: Safe alternatives to any type
- Interface Definitions: Clear component contracts
- Type Guards: Runtime type checking
- Generic Components: Reusable with type parameters
🔗 Related Resources
- UX Framework Documentation - Component implementation details
- Theme System - Styling and theming patterns
- Space Map Application - Real-world widget usage
- Core Types - Type definitions and SafeAny patterns
These widget examples demonstrate production-ready patterns that can be adapted and extended for any application in our monorepo ecosystem.