import { FluentComponent } from '../core/fluent-component'
import { createUniversalComponent } from '../core/component-factory'
class FluentAvatarInternal extends FluentComponent {
static tag = 'fluent-avatar'
static override get observedAttributes() {
return ['src', 'alt', 'size', 'shape', 'ring', 'ring-color', 'status', 'initials', 'srcset', 'sizes', 'loading', 'decoding']
}
override connectedCallback() {
super.connectedCallback()
this.attachImageEvents()
}
render() {
const src = this.getAttribute('src') || ''
const alt = this.getAttribute('alt') || ''
const initials = this.getAttribute('initials') || ''
const srcset = this.getAttribute('srcset') || ''
const sizes = this.getAttribute('sizes') || ''
const loading = this.getAttribute('loading') || ''
const decoding = this.getAttribute('decoding') || ''
this.shadowRootRef.innerHTML = `
${initials}
`
}
constructor() {
super()
this.recipe = {
base: { display: 'inline-block', position: 'relative', '--avatar-size': '40px' },
selectors: {
'.avatar': { position: 'relative', width: 'var(--avatar-size)', height: 'var(--avatar-size)', 'border-radius': '50%', overflow: 'hidden', display: 'inline-flex', 'align-items': 'center', 'justify-content': 'center', 'background': 'var(--fluent-color-neutral-200)' },
'.image': { width: '100%', height: '100%', 'object-fit': 'cover', display: 'none' },
'.initials': { display: 'inline-flex', 'align-items': 'center', 'justify-content': 'center', 'font-weight': '700', 'letter-spacing': '0.03em', color: 'light-dark(var(--fluent-color-neutral-900), var(--fluent-color-neutral-50))' },
':host([data-image-ok]) .image': { display: 'block' },
':host([data-image-ok]) .initials': { display: 'none' },
':host([size="sm"])': { '--avatar-size': '24px' },
':host([size="md"])': { '--avatar-size': '40px' },
':host([size="lg"])': { '--avatar-size': '56px' },
':host([size="xl"])': { '--avatar-size': '80px' },
':host([shape="circle"]) .avatar': { 'border-radius': '50%' },
':host([shape="square"]) .avatar': { 'border-radius': '0' },
':host([shape="rounded"]) .avatar': { 'border-radius': '0.75rem' },
':host([ring]) .avatar': { 'box-shadow': '0 0 0 2px var(--fluent-avatar-ring-color, var(--fluent-color-primary-500))' },
'.status-dot': { position: 'absolute', bottom: '2px', right: '2px', width: '10px', height: '10px', 'border-radius': '50%', 'box-shadow': '0 0 0 2px light-dark(white, var(--fluent-color-neutral-900))', display: 'none' },
':host([status]) .status-dot': { display: 'block' },
':host([status="online"]) .status-dot': { background: 'var(--fluent-color-success-500)' },
':host([status="offline"]) .status-dot': { background: 'var(--fluent-color-neutral-500)' },
':host([status="busy"]) .status-dot': { background: 'var(--fluent-color-danger-500)' },
':host([status="away"]) .status-dot': { background: 'var(--fluent-color-warning-500)' }
}
}
}
override attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
super.attributeChangedCallback(name, oldValue, newValue)
const ringColor = this.getAttribute('ring-color')
if (ringColor) this.style.setProperty('--fluent-avatar-ring-color', ringColor)
else this.style.removeProperty('--fluent-avatar-ring-color')
this.render()
this.attachImageEvents()
}
private attachImageEvents() {
const img = this.shadowRootRef.querySelector('.image') as HTMLImageElement | null
if (!img) return
img.onload = () => this.setAttribute('data-image-ok', '')
img.onerror = () => this.removeAttribute('data-image-ok')
}
}
export const FluentAvatar = createUniversalComponent({
tag: 'fluent-avatar',
class: FluentAvatarInternal,
props: { size: 'md' }
})
if (typeof window !== 'undefined') {
if (!customElements.get(FluentAvatarInternal.tag)) {
customElements.define(FluentAvatarInternal.tag, FluentAvatarInternal)
}
}