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 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}