fc_dioxus/components/
dialog.rs1use crate::{
3 core::{
4 assets,
5 attrs::{component_attributes, FcData, Kind},
6 platform::{DomBridge, DomError},
7 },
8 prelude::*,
9 theme::use_stylesheets,
10};
11const BEM: Bem = Bem::new("fc-dialog");
12fc_modifier! {
13 pub enum DialogSize for BEM {
15 Md => None,
17 Sm => "sm",
19 Lg => "lg",
21 Fullscreen => "fullscreen",
23 }
24}
25fc_modifier! {
26 pub enum DialogPlacement for BEM {
28 Center => None,
30 Sheet => "sheet",
32 Bottom => "bottom",
34 }
35}
36
37#[component]
45pub fn Dialog(
46 open: bool,
47 onclose: EventHandler<()>,
48 title: Element,
49 #[props(default = HeadingLevel::H2)] heading: HeadingLevel,
50 #[props(default)] description: Option<Element>,
51 #[props(default)] size: DialogSize,
52 #[props(default)] placement: DialogPlacement,
53 #[props(default = true)] modal: bool,
54 #[props(default)]
56 close_label: Option<AccessibleName>,
57 #[props(default)] footer: Option<Element>,
58 #[props(default)]
60 handle: bool,
61 #[props(default)]
63 scrollable: bool,
64 #[props(default)]
66 fc_id: Option<Id>,
67 #[props(default)] class: Option<String>,
68 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
69 children: Element,
70) -> Element {
71 let id = use_id();
72 let title_id = id.suffixed("title");
73 let description_id = description.is_some().then(|| id.suffixed("description"));
74 let mut inline = use_signal(|| !modal);
75 let effect_id = id.clone();
76 use_effect(use_reactive!(|open, modal| {
77 let id = effect_id.clone();
78 spawn(async move {
79 if !modal {
80 inline.set(true);
81 return;
82 }
83 let result = if open {
84 DomBridge.show_modal(&id).await
85 } else {
86 DomBridge.close_dialog(&id).await
87 };
88 inline.set(matches!(result, Err(DomError::Unsupported)));
90 });
91 }));
92 let mut attributes = component_attributes(
93 attributes,
94 ClassList::new(BEM.block())
95 .with_modifier(BEM, size)
96 .with_modifier(BEM, placement)
97 .extend(class.as_deref()),
98 &["id", "open", "aria-labelledby", "aria-describedby"],
99 );
100 if let Some(fc_id) = fc_id {
101 attributes.extend(
102 FcData {
103 kind: Some(Kind::Dialog),
104 id: Some(fc_id),
105 ..FcData::default()
106 }
107 .attributes(),
108 );
109 }
110 let close_label = close_label.map_or_else(|| "Close".to_owned(), |l| l.to_string());
111 rsx! {
112 if use_stylesheets() {
113 document::Stylesheet { href: assets::COMPONENTS_BUTTON_CSS }
114 document::Stylesheet { href: assets::COMPONENTS_DIALOG_CSS }
115 }
116 dialog { id: "{id}", open: open && inline(), aria_labelledby: "{title_id}",
117 aria_describedby: description_id.as_ref().map(ToString::to_string),
118 oncancel: move |event| { event.prevent_default(); onclose.call(()); },
119 ..attributes,
120 if handle { div { class: BEM.element("handle"), aria_hidden: "true" } }
121 header { class: BEM.element("header"),
122 {super::heading_with_id(heading, BEM.element("title"), &title_id, title)}
123 button { r#type: "button", class: BEM.element("close"), aria_label: close_label, onclick: move |_| onclose.call(()), "✕" }
124 }
125 div { class: BEM.element("body"), tabindex: scrollable.then_some("0"),
126 if let (Some(description), Some(id)) = (description, description_id.clone()) {
127 p { class: BEM.element("description"), id: "{id}", {description} }
128 }
129 {children}
130 }
131 if let Some(footer) = footer { footer { class: BEM.element("footer"), {footer} } }
132 }
133 }
134}