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