1use std::marker::PhantomData;
2
3use chrono::NaiveDateTime;
4
5use crate::prelude::*;
6use crate::{Icon, IconButton, Input, Label, LabelColor};
7
8#[derive(Element)]
9pub struct ChatPanel<S: 'static + Send + Sync + Clone> {
10 scroll_state: ScrollState,
11 messages: Vec<ChatMessage<S>>,
12}
13
14impl<S: 'static + Send + Sync + Clone> ChatPanel<S> {
15 pub fn new(scroll_state: ScrollState) -> Self {
16 Self {
17 scroll_state,
18 messages: Vec::new(),
19 }
20 }
21
22 pub fn messages(mut self, messages: Vec<ChatMessage<S>>) -> Self {
23 self.messages = messages;
24 self
25 }
26
27 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
28 div()
29 .flex()
30 .flex_col()
31 .justify_between()
32 .h_full()
33 .px_2()
34 .gap_2()
35 // Header
36 .child(
37 div()
38 .flex()
39 .justify_between()
40 .py_2()
41 .child(div().flex().child(Label::new("#design")))
42 .child(
43 div()
44 .flex()
45 .items_center()
46 .gap_px()
47 .child(IconButton::new(Icon::File))
48 .child(IconButton::new(Icon::AudioOn)),
49 ),
50 )
51 .child(
52 div()
53 .flex()
54 .flex_col()
55 // Chat Body
56 .child(
57 div()
58 .w_full()
59 .flex()
60 .flex_col()
61 .gap_3()
62 .overflow_y_scroll(self.scroll_state.clone())
63 .children(self.messages.clone()),
64 )
65 // Composer
66 .child(div().flex().my_2().child(Input::new("Message #design"))),
67 )
68 }
69}
70
71#[derive(Element, Clone)]
72pub struct ChatMessage<S: 'static + Send + Sync + Clone> {
73 state_type: PhantomData<S>,
74 author: String,
75 text: String,
76 sent_at: NaiveDateTime,
77}
78
79impl<S: 'static + Send + Sync + Clone> ChatMessage<S> {
80 pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
81 Self {
82 state_type: PhantomData,
83 author,
84 text,
85 sent_at,
86 }
87 }
88
89 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
90 div()
91 .flex()
92 .flex_col()
93 .child(
94 div()
95 .flex()
96 .gap_2()
97 .child(Label::new(self.author.clone()))
98 .child(
99 Label::new(self.sent_at.format("%m/%d/%Y").to_string())
100 .color(LabelColor::Muted),
101 ),
102 )
103 .child(div().child(Label::new(self.text.clone())))
104 }
105}
106
107#[cfg(feature = "stories")]
108pub use stories::*;
109
110#[cfg(feature = "stories")]
111mod stories {
112 use chrono::DateTime;
113
114 use crate::{Panel, Story};
115
116 use super::*;
117
118 #[derive(Element)]
119 pub struct ChatPanelStory<S: 'static + Send + Sync + Clone> {
120 state_type: PhantomData<S>,
121 }
122
123 impl<S: 'static + Send + Sync + Clone> ChatPanelStory<S> {
124 pub fn new() -> Self {
125 Self {
126 state_type: PhantomData,
127 }
128 }
129
130 fn render(
131 &mut self,
132 _view: &mut S,
133 cx: &mut ViewContext<S>,
134 ) -> impl Element<ViewState = S> {
135 Story::container(cx)
136 .child(Story::title_for::<_, ChatPanel<S>>(cx))
137 .child(Story::label(cx, "Default"))
138 .child(Panel::new(cx).child(ChatPanel::new(ScrollState::default())))
139 .child(Story::label(cx, "With Mesages"))
140 .child(
141 Panel::new(cx).child(ChatPanel::new(ScrollState::default()).messages(vec![
142 ChatMessage::new(
143 "osiewicz".to_string(),
144 "is this thing on?".to_string(),
145 DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
146 .unwrap()
147 .naive_local(),
148 ),
149 ChatMessage::new(
150 "maxdeviant".to_string(),
151 "Reading you loud and clear!".to_string(),
152 DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
153 .unwrap()
154 .naive_local(),
155 ),
156 ])),
157 )
158 }
159 }
160}