Skip to content

Commit 90586e0

Browse files
committed
feat(Avatar): add icon prop as fallback
1 parent 48c13ab commit 90586e0

File tree

3 files changed

+73
-24
lines changed

3 files changed

+73
-24
lines changed

docs/content/2.elements/3.avatar.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,25 @@ baseProps:
5151

5252
### Placeholder
5353

54-
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`.
54+
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`.
5555

56-
If there's an `alt` prop initials will be displayed on top of the background, customizable in `ui.avatar.placeholder`.
56+
#### Icon
57+
58+
You can use the `icon` prop to display an icon on top of the background, customizable in `ui.avatar.icon`.
59+
60+
::component-card
61+
---
62+
props:
63+
icon: 'i-heroicons-photo'
64+
size: 'sm'
65+
excludedProps:
66+
- icon
67+
---
68+
::
69+
70+
#### Alt
71+
72+
Otherwise, a placeholder will be displayed with the initials of the `alt` prop, customizable in `ui.avatar.placeholder`.
5773

5874
::component-card
5975
---

src/runtime/app.config.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,18 @@ const avatar = {
6363
wrapper: 'relative inline-flex items-center justify-center',
6464
background: 'bg-gray-100 dark:bg-gray-800',
6565
rounded: 'rounded-full',
66-
placeholder: 'font-medium leading-none text-gray-900 dark:text-white truncate',
66+
text: 'font-medium leading-none text-gray-900 dark:text-white truncate',
67+
placeholder: 'font-medium leading-none text-gray-500 dark:text-gray-400 truncate',
6768
size: {
6869
'3xs': 'h-4 w-4 text-[8px]',
6970
'2xs': 'h-5 w-5 text-[10px]',
70-
xs: 'h-6 w-6 text-[11px]',
71-
sm: 'h-8 w-8 text-xs',
72-
md: 'h-10 w-10 text-sm',
73-
lg: 'h-12 w-12 text-base',
74-
xl: 'h-14 w-14 text-lg',
75-
'2xl': 'h-16 w-16 text-xl',
76-
'3xl': 'h-20 w-20 text-2xl'
71+
xs: 'h-6 w-6 text-xs',
72+
sm: 'h-8 w-8 text-sm',
73+
md: 'h-10 w-10 text-base',
74+
lg: 'h-12 w-12 text-lg',
75+
xl: 'h-14 w-14 text-xl',
76+
'2xl': 'h-16 w-16 text-2xl',
77+
'3xl': 'h-20 w-20 text-3xl'
7778
},
7879
chip: {
7980
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 = {
9697
'3xl': 'h-5 min-w-[1.25rem] text-[14px] p-1'
9798
}
9899
},
100+
icon: {
101+
base: 'text-gray-500 dark:text-gray-400 flex-shrink-0',
102+
size: {
103+
'3xs': 'h-2 w-2',
104+
'2xs': 'h-2.5 w-2.5',
105+
xs: 'h-3 w-3',
106+
sm: 'h-4 w-4',
107+
md: 'h-5 w-5',
108+
lg: 'h-6 w-6',
109+
xl: 'h-7 w-7',
110+
'2xl': 'h-8 w-8',
111+
'3xl': 'h-10 w-10'
112+
}
113+
},
99114
default: {
100115
size: 'sm',
101116
chipColor: null,

src/runtime/components/elements/Avatar.vue

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
:alt="alt"
77
:src="url"
88
v-bind="$attrs"
9-
:onerror="() => onError()"
9+
@error="onError"
1010
>
11-
<span v-else-if="text || placeholder" :class="ui.placeholder">{{ text || placeholder }}</span>
11+
<span v-else-if="text" :class="ui.text">{{ text }}</span>
12+
<UIcon v-else-if="icon" :name="icon" :class="iconClass" />
13+
<span v-else-if="placeholder" :class="ui.placeholder">{{ placeholder }}</span>
1214

1315
<span v-if="chipColor" :class="chipClass">
1416
{{ chipText }}
@@ -21,6 +23,7 @@
2123
import { defineComponent, ref, computed, watch } from 'vue'
2224
import type { PropType } from 'vue'
2325
import { defu } from 'defu'
26+
import UIcon from '../elements/Icon.vue'
2427
import { classNames } from '../../utils'
2528
import { useAppConfig } from '#imports'
2629
// TODO: Remove
@@ -30,6 +33,9 @@ import appConfig from '#build/app.config'
3033
// const appConfig = useAppConfig()
3134
3235
export default defineComponent({
36+
components: {
37+
UIcon
38+
},
3339
inheritAttrs: false,
3440
props: {
3541
src: {
@@ -44,6 +50,10 @@ export default defineComponent({
4450
type: String,
4551
default: null
4652
},
53+
icon: {
54+
type: String,
55+
default: null
56+
},
4757
size: {
4858
type: String,
4959
default: () => appConfig.ui.avatar.default.size,
@@ -80,10 +90,21 @@ export default defineComponent({
8090
8191
const ui = computed<Partial<typeof appConfig.ui.avatar>>(() => defu({}, props.ui, appConfig.ui.avatar))
8292
93+
const url = computed(() => {
94+
if (typeof props.src === 'boolean') {
95+
return null
96+
}
97+
return props.src
98+
})
99+
100+
const placeholder = computed(() => {
101+
return (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2)
102+
})
103+
83104
const wrapperClass = computed(() => {
84105
return classNames(
85106
ui.value.wrapper,
86-
ui.value.background,
107+
(error.value || !url.value) && ui.value.background,
87108
ui.value.rounded,
88109
ui.value.size[props.size]
89110
)
@@ -96,6 +117,13 @@ export default defineComponent({
96117
)
97118
})
98119
120+
const iconClass = computed(() => {
121+
return classNames(
122+
ui.value.icon.base,
123+
ui.value.icon.size[props.size]
124+
)
125+
})
126+
99127
const chipClass = computed(() => {
100128
return classNames(
101129
ui.value.chip.base,
@@ -105,17 +133,6 @@ export default defineComponent({
105133
)
106134
})
107135
108-
const url = computed(() => {
109-
if (typeof props.src === 'boolean') {
110-
return null
111-
}
112-
return props.src
113-
})
114-
115-
const placeholder = computed(() => {
116-
return (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2)
117-
})
118-
119136
const error = ref(false)
120137
121138
watch(() => props.src, () => {
@@ -131,6 +148,7 @@ export default defineComponent({
131148
return {
132149
wrapperClass,
133150
avatarClass,
151+
iconClass,
134152
chipClass,
135153
url,
136154
placeholder,

0 commit comments

Comments
 (0)