facepile.rs

  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}