1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
use crate::component_prelude::*;
use crate::prelude::*;
use gpui::{AnyElement, StyleRefinement};
use smallvec::SmallVec;
use super::Avatar;
/// An element that displays a collection of (usually) faces stacked
/// horizontally, with the left-most face on top, visually descending
/// from left to right.
///
/// Facepiles are used to display a group of people or things,
/// such as a list of participants in a collaboration session.
///
/// # Examples
///
/// ## Default
///
/// A default, horizontal facepile.
///
/// ```
/// use gpui::IntoElement;
/// use ui::{Avatar, Facepile, EXAMPLE_FACES};
///
/// let facepile = Facepile::new(
/// EXAMPLE_FACES.iter().take(3).map(|&url|
/// Avatar::new(url).into_any_element()).collect()
/// );
/// ```
#[derive(IntoElement, Documented, RegisterComponent)]
pub struct Facepile {
base: Div,
faces: SmallVec<[AnyElement; 2]>,
max_visible: Option<usize>,
}
impl Facepile {
/// Creates a new empty facepile.
pub fn empty() -> Self {
Self::new(SmallVec::new())
}
/// Creates a new facepile with the given faces.
pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
Self {
base: div(),
faces,
max_visible: None,
}
}
/// Caps the number of visible faces, replacing the rest with a `+N`
/// overflow indicator.
pub fn max_visible(mut self, max: usize) -> Self {
self.max_visible = Some(max);
self
}
}
impl ParentElement for Facepile {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.faces.extend(elements);
}
}
// Style methods.
impl Facepile {
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
}
gpui::padding_style_methods!({
visibility: pub
});
}
impl RenderOnce for Facepile {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let total = self.faces.len();
let mut faces = self.faces;
// If capped, trim to `max_visible` and append a `+N` overflow badge
// (rendered on top, at the front of the stack).
if let Some(max) = self.max_visible {
if total > max {
let overflow = total - max;
faces.truncate(max);
faces.push(
div()
.size_6()
.flex()
.items_center()
.justify_center()
.rounded_full()
.border_2()
.border_color(semantic::surface(cx))
.bg(semantic::elevated_surface(cx))
.child(
Label::new(format!("+{overflow}"))
.size(LabelSize::XSmall)
.color(Color::Custom(semantic::text_muted(cx))),
)
.into_any_element(),
);
}
}
// Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
self.base
.flex()
.flex_row_reverse()
.items_center()
.justify_start()
.children(
faces
.into_iter()
.enumerate()
.rev()
.map(|(ix, player)| div().when(ix > 0, |div| div.ml_neg_1()).child(player)),
)
}
}
pub const EXAMPLE_FACES: [&str; 6] = [
"https://avatars.githubusercontent.com/u/326587?s=60&v=4",
"https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
"https://avatars.githubusercontent.com/u/1789?s=60&v=4",
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
];
impl Component for Facepile {
fn scope() -> ComponentScope {
ComponentScope::Collaboration
}
fn description() -> Option<&'static str> {
Some(
"Displays a collection of avatars or initials in a compact format. Often used to represent active collaborators or a subset of contributors.",
)
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
Some(
v_flex()
.gap_6()
.children(vec![example_group_with_title(
"Facepile Examples",
vec![
single_example(
"Default",
Facepile::new(
EXAMPLE_FACES
.iter()
.map(|&url| Avatar::new(url).into_any_element())
.collect(),
)
.into_any_element(),
),
single_example(
"Custom Size",
Facepile::new(
EXAMPLE_FACES
.iter()
.map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
.collect(),
)
.into_any_element(),
),
single_example(
"Overflow Count",
Facepile::new(
EXAMPLE_FACES
.iter()
.map(|&url| Avatar::new(url).into_any_element())
.collect(),
)
.max_visible(3)
.into_any_element(),
)
.description("Caps visible faces and shows a `+N` overflow indicator."),
],
)])
.into_any_element(),
)
}
}