diff --git a/docs/content/2.elements/3.avatar.md b/docs/content/2.elements/3.avatar.md
index 44fa42b5e9..028dd51b94 100644
--- a/docs/content/2.elements/3.avatar.md
+++ b/docs/content/2.elements/3.avatar.md
@@ -51,9 +51,25 @@ baseProps:
### Placeholder
-If there is an error loading the `src` of the avatar or `src` is null a background placeholder will be displayed, customizable in `ui.avatar.background`.
+If there is an error loading the `src` of the avatar or `src` is null / false a background placeholder will be displayed, customizable in `ui.avatar.background`.
-If there's an `alt` prop initials will be displayed on top of the background, customizable in `ui.avatar.placeholder`.
+#### Icon
+
+You can use the `icon` prop to display an icon on top of the background, customizable in `ui.avatar.icon`.
+
+::component-card
+---
+props:
+ icon: 'i-heroicons-photo'
+ size: 'sm'
+excludedProps:
+ - icon
+---
+::
+
+#### Alt
+
+Otherwise, a placeholder will be displayed with the initials of the `alt` prop, customizable in `ui.avatar.placeholder`.
::component-card
---
diff --git a/src/runtime/app.config.ts b/src/runtime/app.config.ts
index 8b434f92f6..17e7cd3655 100644
--- a/src/runtime/app.config.ts
+++ b/src/runtime/app.config.ts
@@ -63,17 +63,18 @@ const avatar = {
wrapper: 'relative inline-flex items-center justify-center',
background: 'bg-gray-100 dark:bg-gray-800',
rounded: 'rounded-full',
- placeholder: 'font-medium leading-none text-gray-900 dark:text-white truncate',
+ text: 'font-medium leading-none text-gray-900 dark:text-white truncate',
+ placeholder: 'font-medium leading-none text-gray-500 dark:text-gray-400 truncate',
size: {
'3xs': 'h-4 w-4 text-[8px]',
'2xs': 'h-5 w-5 text-[10px]',
- xs: 'h-6 w-6 text-[11px]',
- sm: 'h-8 w-8 text-xs',
- md: 'h-10 w-10 text-sm',
- lg: 'h-12 w-12 text-base',
- xl: 'h-14 w-14 text-lg',
- '2xl': 'h-16 w-16 text-xl',
- '3xl': 'h-20 w-20 text-2xl'
+ xs: 'h-6 w-6 text-xs',
+ sm: 'h-8 w-8 text-sm',
+ md: 'h-10 w-10 text-base',
+ lg: 'h-12 w-12 text-lg',
+ xl: 'h-14 w-14 text-xl',
+ '2xl': 'h-16 w-16 text-2xl',
+ '3xl': 'h-20 w-20 text-3xl'
},
chip: {
base: 'absolute rounded-full ring-1 ring-white dark:ring-gray-900 flex items-center justify-center text-white dark:text-gray-900 font-medium',
@@ -96,6 +97,20 @@ const avatar = {
'3xl': 'h-5 min-w-[1.25rem] text-[14px] p-1'
}
},
+ icon: {
+ base: 'text-gray-500 dark:text-gray-400 flex-shrink-0',
+ size: {
+ '3xs': 'h-2 w-2',
+ '2xs': 'h-2.5 w-2.5',
+ xs: 'h-3 w-3',
+ sm: 'h-4 w-4',
+ md: 'h-5 w-5',
+ lg: 'h-6 w-6',
+ xl: 'h-7 w-7',
+ '2xl': 'h-8 w-8',
+ '3xl': 'h-10 w-10'
+ }
+ },
default: {
size: 'sm',
chipColor: null,
diff --git a/src/runtime/components/elements/Avatar.vue b/src/runtime/components/elements/Avatar.vue
index 384eb44ca7..ebb0370ec4 100644
--- a/src/runtime/components/elements/Avatar.vue
+++ b/src/runtime/components/elements/Avatar.vue
@@ -6,9 +6,11 @@
:alt="alt"
:src="url"
v-bind="$attrs"
- :onerror="() => onError()"
+ @error="onError"
>
- {{ text || placeholder }}
+ {{ text }}
+
+ {{ placeholder }}
{{ chipText }}
@@ -21,6 +23,7 @@
import { defineComponent, ref, computed, watch } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
+import UIcon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -30,6 +33,9 @@ import appConfig from '#build/app.config'
// const appConfig = useAppConfig()
export default defineComponent({
+ components: {
+ UIcon
+ },
inheritAttrs: false,
props: {
src: {
@@ -44,6 +50,10 @@ export default defineComponent({
type: String,
default: null
},
+ icon: {
+ type: String,
+ default: null
+ },
size: {
type: String,
default: () => appConfig.ui.avatar.default.size,
@@ -80,10 +90,21 @@ export default defineComponent({
const ui = computed>(() => defu({}, props.ui, appConfig.ui.avatar))
+ const url = computed(() => {
+ if (typeof props.src === 'boolean') {
+ return null
+ }
+ return props.src
+ })
+
+ const placeholder = computed(() => {
+ return (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2)
+ })
+
const wrapperClass = computed(() => {
return classNames(
ui.value.wrapper,
- ui.value.background,
+ (error.value || !url.value) && ui.value.background,
ui.value.rounded,
ui.value.size[props.size]
)
@@ -96,6 +117,13 @@ export default defineComponent({
)
})
+ const iconClass = computed(() => {
+ return classNames(
+ ui.value.icon.base,
+ ui.value.icon.size[props.size]
+ )
+ })
+
const chipClass = computed(() => {
return classNames(
ui.value.chip.base,
@@ -105,17 +133,6 @@ export default defineComponent({
)
})
- const url = computed(() => {
- if (typeof props.src === 'boolean') {
- return null
- }
- return props.src
- })
-
- const placeholder = computed(() => {
- return (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2)
- })
-
const error = ref(false)
watch(() => props.src, () => {
@@ -131,6 +148,7 @@ export default defineComponent({
return {
wrapperClass,
avatarClass,
+ iconClass,
chipClass,
url,
placeholder,