Skip to content

Commit c84faae

Browse files
feat(b-avatar-group): new helper component b-avatar-group (#5272)
* feat(b-avatar-group): new helper component b-avatar-group * Update _avatar.scss * Update _avatar.scss * Update _avatar.scss * Update _avatar.scss * Create avatar-group.js * Update index.js * Update package.json * Create avatar-group.spec.js * Update avatar-group.spec.js * Update avatar.spec.js * Update avatar.js * Update avatar-group.js * Update avatar.js * Update avatar.js * Update avatar-group.js * Update avatar.spec.js * Update avatar.spec.js * Update avatar.spec.js * Update avatar.spec.js * Update avatar-group.js * Update avatar.js * Update README.md * Update package.json * Update index.d.ts * Update _avatar.scss * Update avatar.js * Update _avatar.scss * Update avatar.js * Update _avatar.scss * Update avatar.js * Update avatar-group.js * Update _avatar.scss * Update README.md * Update _avatar.scss * Update avatar.js * Update avatar-group.js * Update avatar-group.js * Update avatar.js * Update avatar-group.js * Update avatar-group.js * Update package.json * Update README.md * Update avatar.js * Update README.md * Update avatar-group.js * Update _avatar.scss * Update avatar-group.js * Update README.md * lint * Update avatar.js * Update avatar.spec.js * Update avatar.spec.js * Update README.md * Update _avatar.scss * Update avatar.js * Update _avatar.scss * Update _avatar.scss * Update _avatar.scss * Update README.md * Update _avatar.scss * Update avatar-group.js * Update avatar-group.js * Update avatar-group.js * Update avatar.js * Update package.json * Update index.js * Update _avatar.scss * Update README.md * Update avatar-group.js * Update README.md * Update README.md * Prettify * Fix typos * Update README.md * Update README.md * Update package.json * Update avatar-group.js * Update avatar.spec.js * Update avatar-group.spec.js Co-authored-by: Jacob Müller <jacob.mueller.elz@gmail.com>
1 parent 755164f commit c84faae

File tree

10 files changed

+464
-35
lines changed

10 files changed

+464
-35
lines changed

src/components/avatar/README.md

Lines changed: 149 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ styling on the content.
6161

6262
Use the `src` prop to specify a URL of an image to use as the avatar content. The image should have
6363
an aspect ratio of `1:1` (meaning the width and height should be equal), otherwise image aspect
64-
distortion will occur. The image will be scaled up or down to fit within the avatar's bounding box,
65-
and will be sized to show the avatar's [variant background](#variants) around the edge.
64+
distortion will occur. The image will be scaled up or down to fit within the avatar's bounding box.
6665

6766
```html
6867
<template>
@@ -84,8 +83,9 @@ and will be sized to show the avatar's [variant background](#variants) around th
8483
fallback to the value of the `icon` or `text` props. If neither the `icon` or `text` props are
8584
provided, then the default avatar icon will be shown. Also, when the image fails to load, the
8685
`img-error` event will be emitted.
87-
- <span class="badge badge-secondary">2.12.0+</span> Setting the [variant prop](#variants) to an
88-
empty string will remove the visible background border around the image.
86+
- [Variant colors](#variants) when using images not normally visible, unless the image fails load.
87+
The variant will affect the focus styling when the image avatar is also an
88+
[actionalble avatar](#actionalble-avatars).
8989

9090
### Icon content
9191

@@ -270,6 +270,8 @@ Easily create avatars that respond to clicks, or avatars that change the URL/rou
270270
Actionable avatars will appear in the document tab sequence, and are accessible for both screen
271271
reader and keyboard-only users.
272272

273+
Image avatars, when actionalble, employ a basic scale transform on the image when hovered.
274+
273275
### Button
274276

275277
Want to trigger the opening of a modal or trigger an action? Set the `button` prop to instruct
@@ -278,10 +280,20 @@ the `click` event whenever clicked.
278280

279281
```html
280282
<template>
281-
<div>
282-
<b-avatar button @click="onClick" variant="primary" text="FF" class="align-baseline"></b-avatar>
283-
Button Avatar
284-
</div>
283+
<b-list-group>
284+
<b-list-group-item>
285+
<b-avatar button @click="onClick" variant="primary" text="FF" class="align-baseline"></b-avatar>
286+
Button Text Avatar
287+
</b-list-group-item>
288+
<b-list-group-item>
289+
<b-avatar button @click="onClick" src="https://placekitten.com/300/300"></b-avatar>
290+
Button Image Avatar
291+
</b-list-group-item>
292+
<b-list-group-item>
293+
<b-avatar button @click="onClick" icon="star-fill" class="align-center"></b-avatar>
294+
Button Icon Avatar
295+
</b-list-group-item>
296+
</b-list-group>
285297
</template>
286298

287299
<script>
@@ -315,10 +327,20 @@ The `to` prop can either be a string path, or a `Location` object. The `to` prop
315327

316328
```html
317329
<template>
318-
<div>
319-
<b-avatar href="#foobar" variant="info" src="https://placekitten.com/300/300"></b-avatar>
320-
Link Avatar
321-
</div>
330+
<b-list-group>
331+
<b-list-group-item>
332+
<b-avatar href="#foo" variant="primary" text="FF" class="align-baseline"></b-avatar>
333+
Link Text Avatar
334+
</b-list-group-item>
335+
<b-list-group-item>
336+
<b-avatar href="#bar" src="https://placekitten.com/300/300"></b-avatar>
337+
Link Image Avatar
338+
</b-list-group-item>
339+
<b-list-group-item>
340+
<b-avatar href="#baz" icon="star-fill" class="align-center"></b-avatar>
341+
Link Icon Avatar
342+
</b-list-group-item>
343+
</b-list-group>
322344
</template>
323345

324346
<!-- b-avatar-href.vue -->
@@ -411,6 +433,121 @@ inward, while negative values will move the badge outward.
411433
<!-- b-avatar-badge-offset.vue -->
412434
```
413435

436+
## Avatar groups
437+
438+
<span class="badge badge-info small">v2.14.0+</span>
439+
440+
Group multiple avatars together by wrapping them in a `<b-avatar-group>` component:
441+
442+
```html
443+
<template>
444+
<div>
445+
<b-avatar-group size="60px">
446+
<b-avatar></b-avatar>
447+
<b-avatar text="BV" variant="primary"></b-avatar>
448+
<b-avatar src="https://placekitten.com/300/300" variant="info"></b-avatar>
449+
<b-avatar text="OK" variant="danger"></b-avatar>
450+
<b-avatar variant="warning"></b-avatar>
451+
<b-avatar src="https://placekitten.com/320/320" variant="dark"></b-avatar>
452+
<b-avatar icon="music-note" variant="success"></b-avatar>
453+
</b-avatar-group>
454+
</div>
455+
</template>
456+
457+
<!-- b-avatar-group.vue -->
458+
```
459+
460+
**Notes:**
461+
462+
- The `variant`, `square` and `rounded` props on `<b-avatar-group>` will take precedence over the
463+
respective props on individual avatars.
464+
465+
### Group size
466+
467+
To size the avatars, use the prop `size` on `<b-avatar-group>`. The `size` prop accepts the same
468+
type of values as the `size` prop on `<b-avatar>`. Note that the `size` prop will be ignored on
469+
individual avatars when they are placed inside a `<b-avatar-group>`.
470+
471+
```html
472+
<template>
473+
<div>
474+
<b-avatar-group size="5rem">
475+
<b-avatar></b-avatar>
476+
<b-avatar></b-avatar>
477+
<b-avatar></b-avatar>
478+
<b-avatar></b-avatar>
479+
<b-avatar></b-avatar>
480+
</b-avatar-group>
481+
</div>
482+
</template>
483+
484+
<!-- b-avatar-group-size.vue -->
485+
```
486+
487+
### Group variant
488+
489+
Use the `variant` prop to color all child avatars in the `<b-avatar-group>`. Note that the `variant`
490+
prop, when set, will override the the `variant` specified on individual avatars.
491+
492+
```html
493+
<template>
494+
<div>
495+
<b-avatar-group variant="success">
496+
<b-avatar></b-avatar>
497+
<b-avatar variant="info"></b-avatar>
498+
<b-avatar></b-avatar>
499+
<b-avatar></b-avatar>
500+
<b-avatar></b-avatar>
501+
</b-avatar-group>
502+
</div>
503+
</template>
504+
505+
<!-- b-avatar-group-variant.vue -->
506+
```
507+
508+
### Group rounding
509+
510+
Similar to the `variant` prop, the `<b-avatar-group>` props `square` and `rounded` take precedence
511+
over the respective props on individual child avatars.
512+
513+
```html
514+
<template>
515+
<div>
516+
<b-avatar-group rounded="lg">
517+
<b-avatar></b-avatar>
518+
<b-avatar></b-avatar>
519+
<b-avatar></b-avatar>
520+
<b-avatar></b-avatar>
521+
<b-avatar></b-avatar>
522+
</b-avatar-group>
523+
</div>
524+
</template>
525+
526+
<!-- b-avatar-group-rounded.vue -->
527+
```
528+
529+
### Group overlap
530+
531+
By default child avatars inside a `<b-avatar-group>` will overlap by a factor of `0.3` (relative to
532+
the size of the avatar). You can control the overlap amount by setting the `overlap` prop to a value
533+
between `0` and `1`, where `0` means no overlap and `1` means 100% overlap.
534+
535+
```html
536+
<template>
537+
<div>
538+
<b-avatar-group overlap="0.65">
539+
<b-avatar></b-avatar>
540+
<b-avatar></b-avatar>
541+
<b-avatar></b-avatar>
542+
<b-avatar></b-avatar>
543+
<b-avatar></b-avatar>
544+
</b-avatar-group>
545+
</div>
546+
</template>
547+
548+
<!-- b-avatar-group-overlap.vue -->
549+
```
550+
414551
## Accessibility
415552

416553
Use the `aria-label` prop to provide an accessible, screen reader friendly, label for your avatar.

src/components/avatar/_avatar.scss

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,23 @@
2020
outline: 0;
2121
}
2222

23-
&a,
24-
&button,
25-
&.btn {
23+
&.btn,
24+
&[href] {
2625
padding: 0;
2726
border: 0;
2827

28+
.b-avatar-img img {
29+
transition: transform 0.15s ease-in-out;
30+
}
31+
2932
&:not(:disabled):not(.disabled) {
3033
cursor: if($enable-pointer-cursor-for-buttons, pointer, null);
34+
35+
&:hover {
36+
.b-avatar-img img {
37+
transform: scale(1.15);
38+
}
39+
}
3140
}
3241
}
3342

@@ -39,7 +48,8 @@
3948
}
4049

4150
.b-avatar-custom,
42-
.b-avatar-text {
51+
.b-avatar-text,
52+
.b-avatar-img {
4353
border-radius: inherit;
4454
width: 100%;
4555
height: 100%;
@@ -54,16 +64,19 @@
5464
white-space: nowrap;
5565
}
5666

67+
&[href] {
68+
text-decoration: none;
69+
}
70+
5771
> .b-icon {
5872
width: 60%;
5973
height: auto;
6074
max-width: 100%;
6175
}
6276

63-
img {
64-
width: 90%;
65-
height: 90%;
66-
max-width: 100%;
77+
.b-avatar-img img {
78+
width: 100%;
79+
height: 100%;
6780
max-height: auto;
6881
border-radius: inherit;
6982
}
@@ -81,3 +94,21 @@
8194
z-index: 5;
8295
}
8396
}
97+
98+
.b-avatar-group {
99+
.b-avatar-group-inner {
100+
display: flex;
101+
flex-wrap: wrap;
102+
}
103+
104+
.b-avatar {
105+
border: $border-width solid $border-color;
106+
}
107+
108+
a,
109+
.btn {
110+
&.b-avatar:hover:not(.disabled):not(disabled) {
111+
z-index: 3;
112+
}
113+
}
114+
}

src/components/avatar/avatar-group.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import Vue from '../../utils/vue'
2+
import normalizeSlotMixin from '../../mixins/normalize-slot'
3+
import { mathMax, mathMin } from '../../utils/math'
4+
import { toFloat } from '../../utils/number'
5+
import { computeSize } from './avatar'
6+
7+
// --- Constants ---
8+
const NAME = 'BAvatarGroup'
9+
10+
// --- Main component ---
11+
// @vue/component
12+
export const BAvatarGroup = /*#__PURE__*/ Vue.extend({
13+
name: NAME,
14+
mixins: [normalizeSlotMixin],
15+
provide() {
16+
return { bvAvatarGroup: this }
17+
},
18+
props: {
19+
variant: {
20+
// Child avatars will prefer this variant over their own
21+
type: String,
22+
default: null
23+
},
24+
size: {
25+
// Child avatars will always use this over their own size
26+
type: String,
27+
default: null
28+
},
29+
overlap: {
30+
type: [Number, String],
31+
default: 0.3
32+
},
33+
square: {
34+
// Child avatars will prefer this prop (if set) over their own
35+
type: Boolean,
36+
default: false
37+
},
38+
rounded: {
39+
// Child avatars will prefer this prop (if set) over their own
40+
type: [Boolean, String],
41+
default: false
42+
},
43+
tag: {
44+
type: String,
45+
default: 'div'
46+
}
47+
},
48+
computed: {
49+
computedSize() {
50+
return computeSize(this.size)
51+
},
52+
overlapScale() {
53+
return mathMin(mathMax(toFloat(this.overlap, 0), 0), 1) / 2
54+
},
55+
paddingStyle() {
56+
let value = this.computedSize
57+
value = value ? `calc(${value} * ${this.overlapScale})` : null
58+
return value ? { paddingLeft: value, paddingRight: value } : {}
59+
}
60+
},
61+
render(h) {
62+
const $inner = h('div', { staticClass: 'b-avatar-group-inner', style: this.paddingStyle }, [
63+
this.normalizeSlot('default')
64+
])
65+
66+
return h(this.tag, { staticClass: 'b-avatar-group', attrs: { role: 'group' } }, [$inner])
67+
}
68+
})

0 commit comments

Comments
 (0)