1use crate::prelude::*;
2use crate::{
3 static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List,
4 ListHeader, ToggleState,
5};
6use gpui2::{img, svg, SharedString};
7use std::marker::PhantomData;
8
9#[derive(Element)]
10pub struct CollabPanel<S: 'static + Send + Sync> {
11 id: ElementId,
12 state_type: PhantomData<S>,
13}
14
15impl<S: 'static + Send + Sync> CollabPanel<S> {
16 pub fn new(id: impl Into<ElementId>) -> Self {
17 Self {
18 id: id.into(),
19 state_type: PhantomData,
20 }
21 }
22
23 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
24 let color = ThemeColor::new(cx);
25
26 v_stack()
27 .id(self.id.clone())
28 .h_full()
29 .bg(color.surface)
30 .child(
31 v_stack()
32 .id("crdb")
33 .w_full()
34 .overflow_y_scroll()
35 .child(
36 div().pb_1().border_color(color.border).border_b().child(
37 List::new(static_collab_panel_current_call())
38 .header(
39 ListHeader::new("CRDB")
40 .left_icon(Icon::Hash.into())
41 .toggle(ToggleState::Toggled),
42 )
43 .toggle(ToggleState::Toggled),
44 ),
45 )
46 .child(
47 v_stack().id("channels").py_1().child(
48 List::new(static_collab_panel_channels())
49 .header(ListHeader::new("CHANNELS").toggle(ToggleState::Toggled))
50 .empty_message("No channels yet. Add a channel to get started.")
51 .toggle(ToggleState::Toggled),
52 ),
53 )
54 .child(
55 v_stack().id("contacts-online").py_1().child(
56 List::new(static_collab_panel_current_call())
57 .header(
58 ListHeader::new("CONTACTS – ONLINE")
59 .toggle(ToggleState::Toggled),
60 )
61 .toggle(ToggleState::Toggled),
62 ),
63 )
64 .child(
65 v_stack().id("contacts-offline").py_1().child(
66 List::new(static_collab_panel_current_call())
67 .header(
68 ListHeader::new("CONTACTS – OFFLINE")
69 .toggle(ToggleState::NotToggled),
70 )
71 .toggle(ToggleState::NotToggled),
72 ),
73 ),
74 )
75 .child(
76 div()
77 .h_7()
78 .px_2()
79 .border_t()
80 .border_color(color.border)
81 .flex()
82 .items_center()
83 .child(
84 div()
85 .text_sm()
86 .text_color(color.text_placeholder)
87 .child("Find..."),
88 ),
89 )
90 }
91
92 fn list_section_header(
93 &self,
94 label: impl Into<SharedString>,
95 expanded: bool,
96 cx: &WindowContext,
97 ) -> impl Element<ViewState = S> {
98 let color = ThemeColor::new(cx);
99 div()
100 .h_7()
101 .px_2()
102 .flex()
103 .justify_between()
104 .items_center()
105 .child(div().flex().gap_1().text_sm().child(label.into()))
106 .child(
107 div().flex().h_full().gap_1().items_center().child(
108 svg()
109 .path(if expanded {
110 "icons/caret_down.svg"
111 } else {
112 "icons/caret_up.svg"
113 })
114 .w_3p5()
115 .h_3p5()
116 .text_color(color.icon_muted),
117 ),
118 )
119 }
120
121 fn list_item(
122 &self,
123 avatar_uri: impl Into<SharedString>,
124 label: impl Into<SharedString>,
125 cx: &WindowContext,
126 ) -> impl Element<ViewState = S> {
127 let color = ThemeColor::new(cx);
128
129 div()
130 .id("list_item")
131 .h_7()
132 .px_2()
133 .flex()
134 .items_center()
135 .hover(|style| style.bg(color.ghost_element_hover))
136 .active(|style| style.bg(color.ghost_element_active))
137 .child(
138 div()
139 .flex()
140 .items_center()
141 .gap_1()
142 .text_sm()
143 .child(
144 img()
145 .uri(avatar_uri)
146 .size_3p5()
147 .rounded_full()
148 .bg(color.image_fallback_background),
149 )
150 .child(label.into()),
151 )
152 }
153}
154
155#[cfg(feature = "stories")]
156pub use stories::*;
157
158#[cfg(feature = "stories")]
159mod stories {
160 use crate::Story;
161
162 use super::*;
163
164 #[derive(Element)]
165 pub struct CollabPanelStory<S: 'static + Send + Sync> {
166 state_type: PhantomData<S>,
167 }
168
169 impl<S: 'static + Send + Sync> CollabPanelStory<S> {
170 pub fn new() -> Self {
171 Self {
172 state_type: PhantomData,
173 }
174 }
175
176 fn render(
177 &mut self,
178 _view: &mut S,
179 cx: &mut ViewContext<S>,
180 ) -> impl Element<ViewState = S> {
181 Story::container(cx)
182 .child(Story::title_for::<_, CollabPanel<S>>(cx))
183 .child(Story::label(cx, "Default"))
184 .child(CollabPanel::new("collab-panel"))
185 }
186 }
187}