import {
ChangeDetectionStrategy,
Component,
computed,
input,
signal,
} from '@angular/core';
/** Control size for {@link AvatarComponent}. */
export type AvatarSize = 'sm' | 'md' | 'lg';
/**
* Circular avatar with image or initials fallback.
*
* @example
* ```html
*
*
* ```
*/
@Component({
selector: 'africanies-avatar',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'inline-flex shrink-0',
},
template: `
@if (src() && !imageFailed()) {
} @else {
{{ initials() }}
}
`,
})
/**
* Circular avatar with image or initials fallback.
*/
export class AvatarComponent {
/** Display name — used for initials when no image loads. */
readonly name = input('');
/** Optional image URL. */
readonly src = input(null);
/** Image alt text; falls back to {@link name}. */
readonly alt = input('');
readonly size = input('md');
protected readonly imageFailed = signal(false);
protected readonly initials = computed(() => initialsFromName(this.name()));
protected readonly shellClass = computed(() => {
const base =
'inline-flex items-center justify-center overflow-hidden rounded-full ' +
'bg-background-welcome font-semibold text-ink ring-1 ring-border ' +
'dark:bg-white/10 dark:text-white dark:ring-white/15';
switch (this.size()) {
case 'sm':
return `${base} size-7 text-caption`;
case 'lg':
return `${base} size-10 text-body-sm`;
default:
return `${base} size-8 text-caption`;
}
});
protected readonly initialsClass = computed(() => 'uppercase tracking-wide');
}
function initialsFromName(name: string): string {
const parts = name.trim().split(/\s+/).filter(Boolean);
if (parts.length === 0) {
return '?';
}
const first = parts[0] ?? '';
if (parts.length === 1) {
return first.slice(0, 2).toUpperCase();
}
const last = parts[parts.length - 1] ?? '';
return `${first[0] ?? ''}${last[0] ?? ''}`.toUpperCase();
}