🌐 AI搜索 & 代理 主页
Skip to content

Commit 35f124e

Browse files
authored
Revert "feat(b-avatar): if img src fails to load, show icon or text or fallback icon (#5064)" (#5078)
This reverts commit 5fc5771.
1 parent 6298953 commit 35f124e

File tree

4 files changed

+40
-157
lines changed

4 files changed

+40
-157
lines changed

src/components/avatar/README.md

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,27 @@ components.
5252

5353
## Avatar types
5454

55-
The avatar content can be either a an image, an icon, or short text string. Avatar content defaults
55+
The avatar content can be either a short text string, an image, or an icon. Avatar content defaults
5656
to the [`'person-fill'` icon](/docs/icons) when no other content is specified.
5757

58-
You can also supply custom content via the default slot, although you may need to apply additional
59-
styling on the content.
58+
### Text content
59+
60+
You can specify a short string as the content of an avatar via the `text` prop. The string should be
61+
short (1 to 3 characters), and will be transformed via CSS to be all uppercase. The font size will
62+
be scaled relative to the [`size` prop setting](#sizing).
63+
64+
```html
65+
<template>
66+
<div class="mb-2">
67+
<b-avatar text="BV"></b-avatar>
68+
<b-avatar text="a"></b-avatar>
69+
<b-avatar text="Foo"></b-avatar>
70+
<b-avatar text="BV" size="4rem"></b-avatar>
71+
</div>
72+
</template>
73+
74+
<!-- b-avatar-text.vue -->
75+
```
6076

6177
### Image content
6278

@@ -80,11 +96,7 @@ and will be sized to show the avatar's [variant background](#variants) around th
8096

8197
- When using a module bundler and project relative image URLs, please refer to the
8298
[Component img src resolving](/docs/reference/images) reference section for additional details.
83-
- The `src` prop takes precedence over the `icon` and `text` props.
84-
- <span class="badge badge-secondary">2.11.0+</span> If the image fails to load, the avatar will
85-
fallback to the value of the `icon` or `text` props. If neither the `icon` or `text` props are
86-
provided, then the default avatar icon will be shown. Also, when the image fails to load, the
87-
`img-error` event will be emitted.
99+
- The `src` prop takes precedence over the `text` prop.
88100

89101
### Icon content
90102

@@ -109,29 +121,10 @@ prop should be set to a valid icon name. Icons will scale respective to the [`si
109121
- When providing a BootstrapVue icon name, you _must_ ensure that you have registered the
110122
corresponding icon component (either locally to your component/page, or globally), if not using
111123
the full [`BootstrapVueIcons` plugin](/docs/icons).
112-
- The `icon` prop takes precedence over the `text` prop.
124+
- The `icon` prop takes precedence over the `text` and `src` props.
113125
- If the `text`, `src`, or `icon` props are not provided _and_ the [default slot](#custom-content)
114126
has no content, then the `person-fill` icon will be used.
115127

116-
### Text content
117-
118-
You can specify a short string as the content of an avatar via the `text` prop. The string should be
119-
short (1 to 3 characters), and will be transformed via CSS to be all uppercase. The font size will
120-
be scaled relative to the [`size` prop setting](#sizing).
121-
122-
```html
123-
<template>
124-
<div class="mb-2">
125-
<b-avatar text="BV"></b-avatar>
126-
<b-avatar text="a"></b-avatar>
127-
<b-avatar text="Foo"></b-avatar>
128-
<b-avatar text="BV" size="4rem"></b-avatar>
129-
</div>
130-
</template>
131-
132-
<!-- b-avatar-text.vue -->
133-
```
134-
135128
### Custom content
136129

137130
Use the `default` slot to render custom content in the avatar, for finer grained control of its

src/components/avatar/avatar.js

Lines changed: 16 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { mergeData } from 'vue-functional-data-merge'
12
import Vue from '../../utils/vue'
23
import pluckProps from '../../utils/pluck-props'
34
import { getComponentConfig } from '../../utils/config'
@@ -7,7 +8,6 @@ import { BButton } from '../button/button'
78
import { BLink } from '../link/link'
89
import { BIcon } from '../../icons/icon'
910
import { BIconPersonFill } from '../../icons/icons'
10-
import normalizeSlotMixin from '../../mixins/normalize-slot'
1111

1212
// --- Constants ---
1313
const NAME = 'BAvatar'
@@ -135,69 +135,30 @@ const computeSize = value => {
135135
// @vue/component
136136
export const BAvatar = /*#__PURE__*/ Vue.extend({
137137
name: NAME,
138-
mixins: [normalizeSlotMixin],
138+
functional: true,
139139
props,
140-
data() {
141-
return {
142-
localSrc: this.src || null
143-
}
144-
},
145-
computed: {
146-
computedSize() {
147-
return computeSize(this.size)
148-
},
149-
fontSize() {
150-
const size = this.computedSize
151-
return size ? `calc(${size} * ${FONT_SIZE_SCALE})` : null
152-
}
153-
},
154-
watch: {
155-
src(newSrc, oldSrc) {
156-
if (newSrc !== oldSrc) {
157-
this.localSrc = newSrc || null
158-
}
159-
}
160-
},
161-
methods: {
162-
onImgError() {
163-
this.localSrc = null
164-
this.$emit('img-error')
165-
},
166-
onClick(evt) {
167-
this.$emit('click', evt)
168-
}
169-
},
170-
render(h) {
171-
const {
172-
variant,
173-
disabled,
174-
square,
175-
icon,
176-
localSrc: src,
177-
text,
178-
fontSize,
179-
computedSize: size,
180-
button: isButton,
181-
buttonType: type
182-
} = this
183-
const isBLink = !isButton && (this.href || this.to)
140+
render(h, { props, data, children }) {
141+
const { variant, disabled, square, icon, src, text, button: isButton, buttonType: type } = props
142+
const isBLink = !isButton && (props.href || props.to)
184143
const tag = isButton ? BButton : isBLink ? BLink : 'span'
185-
const rounded = square ? false : this.rounded === '' ? true : this.rounded || 'circle'
186-
const alt = this.alt || null
187-
const ariaLabel = this.ariaLabel || null
144+
const rounded = square ? false : props.rounded === '' ? true : props.rounded || 'circle'
145+
const size = computeSize(props.size)
146+
const alt = props.alt || null
147+
const ariaLabel = props.ariaLabel || null
188148

189149
let $content = null
190-
if (this.hasNormalizedSlot('default')) {
150+
if (children) {
191151
// Default slot overrides props
192-
$content = this.normalizeSlot('default')
193-
} else if (src) {
194-
$content = h('img', { attrs: { src, alt }, on: { error: this.onImgError } })
152+
$content = children
195153
} else if (icon) {
196154
$content = h(BIcon, {
197155
props: { icon },
198156
attrs: { 'aria-hidden': 'true', alt }
199157
})
158+
} else if (src) {
159+
$content = h('img', { attrs: { src, alt } })
200160
} else if (text) {
161+
const fontSize = size ? `calc(${size} * ${FONT_SIZE_SCALE})` : null
201162
$content = h('span', { style: { fontSize } }, text)
202163
} else {
203164
// Fallback default avatar content
@@ -218,10 +179,9 @@ export const BAvatar = /*#__PURE__*/ Vue.extend({
218179
},
219180
style: { width: size, height: size },
220181
attrs: { 'aria-label': ariaLabel },
221-
props: isButton ? { variant, disabled, type } : isBLink ? pluckProps(linkProps, this) : {},
222-
on: isBLink || isButton ? { click: this.onClick } : {}
182+
props: isButton ? { variant, disabled, type } : isBLink ? pluckProps(linkProps, props) : {}
223183
}
224184

225-
return h(tag, componentData, [$content])
185+
return h(tag, mergeData(data, componentData), [$content])
226186
}
227187
})
Lines changed: 2 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils'
22
import { BIconPerson } from '../../icons/icons'
33
import { BAvatar } from './avatar'
4-
import { waitNT } from '../../../tests/utils'
54

65
describe('avatar', () => {
76
it('should have expected default structure', async () => {
87
const wrapper = mount(BAvatar)
9-
expect(wrapper.isVueInstance()).toBe(true)
108
expect(wrapper.is('span')).toBe(true)
119
expect(wrapper.classes()).toContain('b-avatar')
1210
expect(wrapper.classes()).toContain('badge-secondary')
1311
expect(wrapper.classes()).not.toContain('disabled')
1412
expect(wrapper.attributes('href')).not.toBeDefined()
1513
expect(wrapper.attributes('type')).not.toBeDefined()
16-
wrapper.destroy()
1714
})
1815

1916
it('should have expected structure when prop `button` set', async () => {
@@ -22,7 +19,6 @@ describe('avatar', () => {
2219
button: true
2320
}
2421
})
25-
expect(wrapper.isVueInstance()).toBe(true)
2622
expect(wrapper.is('button')).toBe(true)
2723
expect(wrapper.classes()).toContain('b-avatar')
2824
expect(wrapper.classes()).toContain('btn-secondary')
@@ -33,17 +29,6 @@ describe('avatar', () => {
3329
expect(wrapper.text()).toEqual('')
3430
expect(wrapper.find('.b-icon').exists()).toBe(true)
3531
expect(wrapper.find('img').exists()).toBe(false)
36-
37-
expect(wrapper.emitted('click')).toBeUndefined()
38-
39-
wrapper.trigger('click')
40-
await waitNT(wrapper.vm)
41-
42-
expect(wrapper.emitted('click')).not.toBeUndefined()
43-
expect(wrapper.emitted('click').length).toBe(1)
44-
expect(wrapper.emitted('click')[0][0]).toBeInstanceOf(Event)
45-
46-
wrapper.destroy()
4732
})
4833

4934
it('should have expected structure when prop `href` set', async () => {
@@ -52,7 +37,6 @@ describe('avatar', () => {
5237
href: '#foo'
5338
}
5439
})
55-
expect(wrapper.isVueInstance()).toBe(true)
5640
expect(wrapper.is('a')).toBe(true)
5741
expect(wrapper.classes()).toContain('b-avatar')
5842
expect(wrapper.classes()).toContain('badge-secondary')
@@ -64,17 +48,6 @@ describe('avatar', () => {
6448
expect(wrapper.text()).toEqual('')
6549
expect(wrapper.find('.b-icon').exists()).toBe(true)
6650
expect(wrapper.find('img').exists()).toBe(false)
67-
68-
expect(wrapper.emitted('click')).toBeUndefined()
69-
70-
wrapper.trigger('click')
71-
await waitNT(wrapper.vm)
72-
73-
expect(wrapper.emitted('click')).not.toBeUndefined()
74-
expect(wrapper.emitted('click').length).toBe(1)
75-
expect(wrapper.emitted('click')[0][0]).toBeInstanceOf(Event)
76-
77-
wrapper.destroy()
7851
})
7952

8053
it('should have expected structure when prop `text` set', async () => {
@@ -83,7 +56,6 @@ describe('avatar', () => {
8356
text: 'BV'
8457
}
8558
})
86-
expect(wrapper.isVueInstance()).toBe(true)
8759
expect(wrapper.is('span')).toBe(true)
8860
expect(wrapper.classes()).toContain('b-avatar')
8961
expect(wrapper.classes()).toContain('badge-secondary')
@@ -93,7 +65,6 @@ describe('avatar', () => {
9365
expect(wrapper.text()).toContain('BV')
9466
expect(wrapper.find('.b-icon').exists()).toBe(false)
9567
expect(wrapper.find('img').exists()).toBe(false)
96-
wrapper.destroy()
9768
})
9869

9970
it('should have expected structure when default slot used', async () => {
@@ -105,7 +76,6 @@ describe('avatar', () => {
10576
default: 'BAR'
10677
}
10778
})
108-
expect(wrapper.isVueInstance()).toBe(true)
10979
expect(wrapper.is('span')).toBe(true)
11080
expect(wrapper.classes()).toContain('b-avatar')
11181
expect(wrapper.classes()).toContain('badge-secondary')
@@ -116,17 +86,14 @@ describe('avatar', () => {
11686
expect(wrapper.text()).not.toContain('FOO')
11787
expect(wrapper.find('.b-icon').exists()).toBe(false)
11888
expect(wrapper.find('img').exists()).toBe(false)
119-
wrapper.destroy()
12089
})
12190

12291
it('should have expected structure when prop `src` set', async () => {
12392
const wrapper = mount(BAvatar, {
12493
propsData: {
125-
src: '/foo/bar',
126-
text: 'BV'
94+
src: '/foo/bar'
12795
}
12896
})
129-
expect(wrapper.isVueInstance()).toBe(true)
13097
expect(wrapper.is('span')).toBe(true)
13198
expect(wrapper.classes()).toContain('b-avatar')
13299
expect(wrapper.classes()).toContain('badge-secondary')
@@ -137,31 +104,9 @@ describe('avatar', () => {
137104
expect(wrapper.find('.b-icon').exists()).toBe(false)
138105
expect(wrapper.find('img').exists()).toBe(true)
139106
expect(wrapper.find('img').attributes('src')).toEqual('/foo/bar')
140-
expect(wrapper.text()).not.toContain('BV')
141-
142-
wrapper.setProps({
143-
src: '/foo/baz'
144-
})
145-
await waitNT(wrapper.vm)
146-
147-
expect(wrapper.find('img').exists()).toBe(true)
148-
expect(wrapper.find('img').attributes('src')).toEqual('/foo/baz')
149-
expect(wrapper.text()).not.toContain('BV')
150-
expect(wrapper.emitted('img-error')).not.toBeDefined()
151-
expect(wrapper.text()).not.toContain('BV')
152-
153-
// Fake an image error
154-
wrapper.find('img').trigger('error')
155-
await waitNT(wrapper.vm)
156-
expect(wrapper.emitted('img-error')).toBeDefined()
157-
expect(wrapper.emitted('img-error').length).toBe(1)
158-
expect(wrapper.find('img').exists()).toBe(false)
159-
expect(wrapper.text()).toContain('BV')
160-
161-
wrapper.destroy()
162107
})
163108

164-
it('should have expected structure when prop `icon` set', async () => {
109+
it('should have expected structure when prop `src` set', async () => {
165110
const localVue = new CreateLocalVue()
166111
localVue.component('BIconPerson', BIconPerson)
167112
const wrapper = mount(BAvatar, {
@@ -170,7 +115,6 @@ describe('avatar', () => {
170115
icon: 'person'
171116
}
172117
})
173-
expect(wrapper.isVueInstance()).toBe(true)
174118
expect(wrapper.is('span')).toBe(true)
175119
expect(wrapper.classes()).toContain('b-avatar')
176120
expect(wrapper.classes()).toContain('badge-secondary')
@@ -181,40 +125,31 @@ describe('avatar', () => {
181125
const $icon = wrapper.find('.b-icon')
182126
expect($icon.exists()).toBe(true)
183127
expect($icon.classes()).toContain('bi-person')
184-
wrapper.destroy()
185128
})
186129

187130
it('`size` prop should work as expected', async () => {
188131
const wrapper1 = mount(BAvatar)
189132
expect(wrapper1.attributes('style')).toEqual('width: 2.5em; height: 2.5em;')
190-
wrapper1.destroy()
191133

192134
const wrapper2 = mount(BAvatar, { propsData: { size: 'sm' } })
193135
expect(wrapper2.attributes('style')).toEqual('width: 1.5em; height: 1.5em;')
194-
wrapper2.destroy()
195136

196137
const wrapper3 = mount(BAvatar, { propsData: { size: 'md' } })
197138
expect(wrapper3.attributes('style')).toEqual('width: 2.5em; height: 2.5em;')
198-
wrapper3.destroy()
199139

200140
const wrapper4 = mount(BAvatar, { propsData: { size: 'lg' } })
201141
expect(wrapper4.attributes('style')).toEqual('width: 3.5em; height: 3.5em;')
202-
wrapper4.destroy()
203142

204143
const wrapper5 = mount(BAvatar, { propsData: { size: 20 } })
205144
expect(wrapper5.attributes('style')).toEqual('width: 20px; height: 20px;')
206-
wrapper5.destroy()
207145

208146
const wrapper6 = mount(BAvatar, { propsData: { size: '24.5' } })
209147
expect(wrapper6.attributes('style')).toEqual('width: 24.5px; height: 24.5px;')
210-
wrapper6.destroy()
211148

212149
const wrapper7 = mount(BAvatar, { propsData: { size: '5em' } })
213150
expect(wrapper7.attributes('style')).toEqual('width: 5em; height: 5em;')
214-
wrapper7.destroy()
215151

216152
const wrapper8 = mount(BAvatar, { propsData: { size: '36px' } })
217153
expect(wrapper8.attributes('style')).toEqual('width: 36px; height: 36px;')
218-
wrapper8.destroy()
219154
})
220155
})

0 commit comments

Comments
 (0)