import { Component, EventEmitter, Input, Output } from '@angular/core'; export type AvatarStatus = 'online' | 'offline' | 'away' | 'busy'; const AVATAR_STATUS: Record = { online: 'var(--success)', offline: 'var(--text-faint)', away: 'var(--warning)', busy: 'var(--danger)', }; @Component({ selector: 'vsp-avatar', template: ` @if (src) { } @else { {{ initials }} } @if (status) { } `, }) export class VspAvatar { @Input() name = ''; @Input() hue = 0; @Input() size = 34; @Input() src?: string; @Input() alt?: string; @Input() status?: AvatarStatus; @Input() shape: 'circle' | 'square' = 'circle'; get initials(): string { return this.name .split(' ') .map((s) => s.charAt(0)) .slice(0, 2) .join('') .toUpperCase(); } get radius(): string { return this.shape === 'square' ? 'var(--r-sm)' : '50%'; } get dot(): number { return Math.max(8, Math.round(this.size * 0.28)); } get statusColor(): string { return this.status ? AVATAR_STATUS[this.status] : ''; } get bg(): string { return this.src ? 'var(--surface-3)' : `linear-gradient(140deg, oklch(0.62 0.16 ${this.hue}), oklch(0.55 0.17 ${(this.hue + 50) % 360}))`; } } export interface Person { name: string; hue?: number; src?: string; } @Component({ selector: 'vsp-avatar-group', imports: [VspAvatar], template: `
@for (p of shown; track $index) { } @if (extra > 0) { +{{ extra }} }
`, }) export class VspAvatarGroup { @Input() people: Person[] = []; @Input() max = 4; @Input() size = 32; get shown(): Person[] { return this.people.slice(0, this.max); } get extra(): number { return this.people.length - this.shown.length; } } @Component({ selector: 'vsp-segmented', template: `
@for (o of options; track o) { }
`, }) export class VspSegmented { @Input() value?: string; @Output() valueChange = new EventEmitter(); @Input() options: string[] = []; pick(o: string): void { this.value = o; this.valueChange.emit(o); } }