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