100 lines
2.1 KiB
Vue
100 lines
2.1 KiB
Vue
<script setup lang="ts">
|
|
const props = defineProps<{
|
|
size?: number | string
|
|
photoURL?: string
|
|
firstName?: string
|
|
lastName?: string
|
|
}>()
|
|
|
|
const imageError = ref(false)
|
|
|
|
const avatarSize = computed(() => {
|
|
const s = props.size || 32
|
|
if (typeof s === 'number') return `${s}px`
|
|
// If it's a string consisting only of digits, append px
|
|
if (/^\d+$/.test(s)) return `${s}px`
|
|
return s
|
|
})
|
|
|
|
const initials = computed(() => {
|
|
const getFirstChar = (name?: string) => {
|
|
if (!name) return ''
|
|
// For Thai names, if the first char is a leading vowel (เ แ โ ใ ไ), skip it to get the consonant
|
|
const leadingVowels = ['เ', 'แ', 'โ', 'ใ', 'ไ']
|
|
if (leadingVowels.includes(name.charAt(0)) && name.length > 1) {
|
|
return name.charAt(1)
|
|
}
|
|
return name.charAt(0)
|
|
}
|
|
|
|
const f = getFirstChar(props.firstName)
|
|
const l = getFirstChar(props.lastName)
|
|
return (f + l).toUpperCase()
|
|
})
|
|
|
|
const handleImageError = () => {
|
|
imageError.value = true
|
|
}
|
|
|
|
// Watch for photoURL changes to reset error state
|
|
watch(() => props.photoURL, () => {
|
|
imageError.value = false
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="user-avatar"
|
|
:style="{ width: avatarSize, height: avatarSize }"
|
|
>
|
|
<img
|
|
v-if="photoURL && !imageError"
|
|
:src="photoURL"
|
|
class="avatar-img"
|
|
@error="handleImageError"
|
|
>
|
|
<div v-else class="avatar-initials">
|
|
{{ initials }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.user-avatar {
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
background-color: var(--neutral-200);
|
|
color: var(--neutral-600);
|
|
font-weight: 700;
|
|
font-size: 13px;
|
|
flex-shrink: 0;
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
.avatar-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.avatar-initials {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: #3b82f6; /* Blue-500 to match theme */
|
|
color: #ffffff;
|
|
user-select: none;
|
|
font-size: 13px;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
/* Specific styling for AppHeader integration */
|
|
:deep(.avatar-initials) {
|
|
text-transform: uppercase;
|
|
}
|
|
</style>
|