1use crate::component_prelude::*;
2use crate::prelude::*;
3use gpui::{AnyElement, StyleRefinement};
4use smallvec::SmallVec;
5
6use super::Avatar;
7
8/// An element that displays a collection of (usually) faces stacked
9/// horizontally, with the left-most face on top, visually descending
10/// from left to right.
11///
12/// Facepiles are used to display a group of people or things,
13/// such as a list of participants in a collaboration session.
14///
15/// # Examples
16///
17/// ## Default
18///
19/// A default, horizontal facepile.
20///
21/// ```
22/// use gpui::IntoElement;
23/// use ui::{Avatar, Facepile, EXAMPLE_FACES};
24///
25/// let facepile = Facepile::new(
26/// EXAMPLE_FACES.iter().take(3).map(|&url|
27/// Avatar::new(url).into_any_element()).collect()
28/// );
29/// ```
30#[derive(IntoElement, Documented, RegisterComponent)]
31pub struct Facepile {
32 base: Div,
33 faces: SmallVec<[AnyElement; 2]>,
34}
35
36impl Facepile {
37 /// Creates a new empty facepile.
38 pub fn empty() -> Self {
39 Self::new(SmallVec::new())
40 }
41
42 /// Creates a new facepile with the given faces.
43 pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
44 Self { base: div(), faces }
45 }
46}
47
48impl ParentElement for Facepile {
49 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
50 self.faces.extend(elements);
51 }
52}
53
54// Style methods.
55impl Facepile {
56 fn style(&mut self) -> &mut StyleRefinement {
57 self.base.style()
58 }
59
60 gpui::padding_style_methods!({
61 visibility: pub
62 });
63}
64
65impl RenderOnce for Facepile {
66 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
67 // Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
68 self.base
69 .flex()
70 .flex_row_reverse()
71 .items_center()
72 .justify_start()
73 .children(
74 self.faces
75 .into_iter()
76 .enumerate()
77 .rev()
78 .map(|(ix, player)| div().when(ix > 0, |div| div.ml_neg_1()).child(player)),
79 )
80 }
81}
82
83pub const EXAMPLE_FACES: [&str; 6] = [
84 "https://avatars.githubusercontent.com/u/326587?s=60&v=4",
85 "https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
86 "https://avatars.githubusercontent.com/u/1789?s=60&v=4",
87 "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
88 "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
89 "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
90];
91
92impl Component for Facepile {
93 fn scope() -> ComponentScope {
94 ComponentScope::Collaboration
95 }
96
97 fn description() -> Option<&'static str> {
98 Some(
99 "Displays a collection of avatars or initials in a compact format. Often used to represent active collaborators or a subset of contributors.",
100 )
101 }
102
103 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
104 Some(
105 v_flex()
106 .gap_6()
107 .children(vec![example_group_with_title(
108 "Facepile Examples",
109 vec![
110 single_example(
111 "Default",
112 Facepile::new(
113 EXAMPLE_FACES
114 .iter()
115 .map(|&url| Avatar::new(url).into_any_element())
116 .collect(),
117 )
118 .into_any_element(),
119 ),
120 single_example(
121 "Custom Size",
122 Facepile::new(
123 EXAMPLE_FACES
124 .iter()
125 .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
126 .collect(),
127 )
128 .into_any_element(),
129 ),
130 ],
131 )])
132 .into_any_element(),
133 )
134 }
135}