fc_dioxus/components/
chat.rs1use crate::{
3 core::{
4 assets,
5 attrs::{
6 availability_attributes, component_attributes, full_text_title, truncated_attribute,
7 FcData, Kind,
8 },
9 },
10 prelude::*,
11 theme::use_stylesheets,
12};
13const CHAT: Bem = Bem::new("fc-chat");
14const MSG: Bem = Bem::new("fc-msg");
15const ACTIONS: Bem = Bem::new("fc-msg__actions");
16const PROSE: Bem = Bem::new("fc-prose");
17fc_modifier! { enum ActionsVisibility for ACTIONS { Hover => None, Persistent => "persistent" } }
18
19#[component]
21pub fn Chat(
22 label: AccessibleName,
23 #[props(default)] class: Option<String>,
24 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
25 children: Element,
26) -> Element {
27 let attributes = component_attributes(
28 attributes,
29 ClassList::new(CHAT.block()).extend(class.as_deref()),
30 &["role", "aria-label"],
31 );
32 rsx! {
33 if use_stylesheets() {
34 document::Stylesheet { href: assets::COMPONENTS_CHAT_CSS }
35 document::Stylesheet { href: assets::COMPONENTS_EDITOR_CSS }
37 }
38 div { role: "log", aria_label: label.to_string(), ..attributes, {children} }
39 }
40}
41
42#[component]
44pub fn ChatGroup(children: Element) -> Element {
45 rsx! { div { class: CHAT.element("group"), {children} } }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct SentAt {
51 pub at: Timestamp,
53 pub display: String,
55}
56
57#[component]
59pub fn Message(
60 role: MessageRole,
61 state: Lifecycle,
62 author: Element,
63 #[props(default)] sent: Option<SentAt>,
64 #[props(default)]
66 actions: Option<Element>,
67 #[props(default)]
69 persistent_actions: bool,
70 #[props(default)]
72 clipped: Option<Clipped>,
73 #[props(default)] record: Record,
74 #[props(default)] class: Option<String>,
75 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
76 children: Element,
77) -> Element {
78 let mut attributes = component_attributes(
79 attributes,
80 ClassList::new(MSG.block()).extend(class.as_deref()),
81 &[],
82 );
83 attributes.extend(
84 FcData {
85 kind: Some(Kind::Message),
86 id: record.id.clone(),
87 seq: record.seq,
88 role: Some(role),
89 state: Some(state),
90 ts: record.ts.clone(),
91 }
92 .attributes(),
93 );
94 let title = clipped.as_ref().map(full_text_title).unwrap_or_default();
96 attributes.extend(
97 clipped
98 .as_ref()
99 .map(truncated_attribute)
100 .unwrap_or_default(),
101 );
102 let actions_class = ClassList::new(ACTIONS.block())
103 .with_modifier(
104 ACTIONS,
105 if persistent_actions {
106 ActionsVisibility::Persistent
107 } else {
108 ActionsVisibility::Hover
109 },
110 )
111 .to_string();
112 let body_class = ClassList::new(&MSG.element("body"))
113 .with(PROSE.block())
114 .to_string();
115 rsx! {
116 div { ..attributes,
117 div { class: MSG.element("meta"),
118 span { class: MSG.element("author"), {author} }
119 if let Some(sent) = sent { time { class: MSG.element("time"), datetime: "{sent.at}", "{sent.display}" } }
120 }
121 div { class: body_class, ..title, {children} }
122 if let Some(actions) = actions {
123 div { class: actions_class, role: "toolbar", aria_label: "Message actions", {actions} }
124 }
125 }
126 }
127}
128
129#[component]
132pub fn MessageAction(
133 #[props(default)] icon_label: Option<AccessibleName>,
134 #[props(default)] pressed: Option<bool>,
135 #[props(default)] availability: Availability,
136 #[props(default)] onclick: Option<EventHandler<MouseEvent>>,
137 children: Element,
138) -> Element {
139 let onclick = availability.guard(onclick);
140 let state = availability_attributes(&availability);
141 rsx! {
142 button { r#type: "button", class: MSG.element("action"),
143 aria_label: icon_label.map(|l| l.to_string()),
144 aria_pressed: pressed.map(|p| p.to_string()),
145 onclick: move |event| if let Some(onclick) = onclick { onclick.call(event); },
146 ..state, {children}
147 }
148 }
149}
150
151#[component]
153pub fn MessagePart(children: Element) -> Element {
154 rsx! { div { class: MSG.element("part"), {children} } }
155}