guise/data/
avatargroup.rs1use gpui::prelude::*;
4use gpui::{div, px, App, FontWeight, IntoElement, SharedString, Window};
5
6use crate::devtools::Probed;
7use crate::theme::{theme, ColorName, Size};
8
9const PALETTE: [ColorName; 6] = [
10 ColorName::Blue,
11 ColorName::Teal,
12 ColorName::Grape,
13 ColorName::Orange,
14 ColorName::Pink,
15 ColorName::Lime,
16];
17
18#[derive(IntoElement)]
20pub struct AvatarGroup {
21 items: Vec<SharedString>,
22 size: Size,
23 limit: Option<usize>,
24}
25
26impl AvatarGroup {
27 pub fn new() -> Self {
28 AvatarGroup {
29 items: Vec::new(),
30 size: Size::Md,
31 limit: None,
32 }
33 }
34
35 pub fn avatar(mut self, initials: impl Into<SharedString>) -> Self {
36 self.items.push(initials.into());
37 self
38 }
39
40 pub fn avatars<I, S>(mut self, items: I) -> Self
41 where
42 I: IntoIterator<Item = S>,
43 S: Into<SharedString>,
44 {
45 self.items.extend(items.into_iter().map(Into::into));
46 self
47 }
48
49 pub fn size(mut self, size: Size) -> Self {
50 self.size = size;
51 self
52 }
53
54 pub fn limit(mut self, limit: usize) -> Self {
56 self.limit = Some(limit);
57 self
58 }
59}
60
61impl Default for AvatarGroup {
62 fn default() -> Self {
63 AvatarGroup::new()
64 }
65}
66
67impl RenderOnce for AvatarGroup {
68 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
69 let t = theme(cx);
70 let dim = super::avatar::avatar_size(self.size);
71 let ring = t.body().hsla();
72 let overflow_bg = t
73 .color(ColorName::Gray, if t.scheme.is_dark() { 6 } else { 3 })
74 .hsla();
75 let overflow_fg = t.text().hsla();
76 let dark = t.scheme.is_dark();
77
78 let total = self.items.len();
79 let shown = self.limit.unwrap_or(total).min(total);
80 let overflow = total - shown;
81
82 let bubble = |bg, fg, content: SharedString, first: bool| {
84 let mut b = div()
85 .w(px(dim))
86 .h(px(dim))
87 .flex()
88 .items_center()
89 .justify_center()
90 .rounded(px(dim))
91 .border_2()
92 .border_color(ring)
93 .bg(bg)
94 .text_color(fg)
95 .text_size(px(dim * 0.38))
96 .font_weight(FontWeight::SEMIBOLD)
97 .child(content);
98 if !first {
99 b = b.ml(px(-(dim * 0.3)));
100 }
101 b
102 };
103
104 let mut row = div().flex().items_center();
105 for (i, initials) in self.items.into_iter().take(shown).enumerate() {
106 let name = PALETTE[i % PALETTE.len()];
107 let bg = t.color(name, if dark { 8 } else { 1 }).hsla();
108 let fg = t.color(name, if dark { 2 } else { 8 }).hsla();
109 row = row.child(bubble(bg, fg, initials, i == 0));
110 }
111 if overflow > 0 {
112 row = row.child(bubble(
113 overflow_bg,
114 overflow_fg,
115 SharedString::from(format!("+{overflow}")),
116 shown == 0,
117 ));
118 }
119 row.probe("AvatarGroup")
120 }
121}