yew_bs/components/
modal.rs

1use yew::prelude::*;
2use web_sys::Element;
3use crate::interop::BsModal;
4#[derive(Properties, PartialEq)]
5pub struct ModalProps {
6    #[prop_or_default]
7    pub children: Children,
8    #[prop_or_default]
9    pub show: bool,
10    #[prop_or_default]
11    pub title: Option<AttrValue>,
12    #[prop_or_default]
13    pub on_hide: Option<Callback<()>>,
14    #[prop_or(true)]
15    pub close_button: bool,
16    #[prop_or_default]
17    pub centered: bool,
18    #[prop_or_default]
19    pub size: Option<String>,
20    #[prop_or_default]
21    pub fullscreen: bool,
22    #[prop_or_default]
23    pub scrollable: bool,
24    #[prop_or_default]
25    pub static_backdrop: bool,
26    #[prop_or_default]
27    pub class: Option<AttrValue>,
28    #[prop_or_default]
29    pub dialog_class: Option<AttrValue>,
30    #[prop_or_default]
31    pub node_ref: NodeRef,
32}
33#[function_component(Modal)]
34pub fn modal(props: &ModalProps) -> Html {
35    let modal_ref = use_node_ref();
36    let bs_modal = use_state(|| None::<BsModal>);
37    let prev_show = use_state(|| false);
38    {
39        let modal_ref = modal_ref.clone();
40        let bs_modal = bs_modal.clone();
41        let static_backdrop = props.static_backdrop;
42        use_effect(move || {
43            if let Some(element) = modal_ref.cast::<Element>() {
44                let options = js_sys::Object::new();
45                if static_backdrop {
46                    let _ = js_sys::Reflect::set(
47                        &options,
48                        &"backdrop".into(),
49                        &"static".into(),
50                    );
51                }
52                bs_modal.set(Some(BsModal::new(&element, Some(&options.into()))));
53            }
54            || {}
55        });
56    }
57    {
58        let bs_modal = bs_modal.clone();
59        let prev_show = prev_show.clone();
60        let on_hide = props.on_hide.clone();
61        use_effect_with(
62            props.show,
63            move |show| {
64                if let Some(modal) = (*bs_modal).as_ref() {
65                    if *show && !*prev_show {
66                        modal.show();
67                        prev_show.set(true);
68                    } else if !*show && *prev_show {
69                        modal.hide();
70                        prev_show.set(false);
71                        if let Some(on_hide) = on_hide {
72                            on_hide.emit(());
73                        }
74                    }
75                }
76                || {}
77            },
78        );
79    }
80    let mut modal_classes = Classes::new();
81    modal_classes.push("modal");
82    modal_classes.push("fade");
83    if let Some(class) = &props.class {
84        modal_classes.push(class.to_string());
85    }
86    let mut dialog_classes = Classes::new();
87    dialog_classes.push("modal-dialog");
88    if props.centered {
89        dialog_classes.push("modal-dialog-centered");
90    }
91    if props.scrollable {
92        dialog_classes.push("modal-dialog-scrollable");
93    }
94    if props.fullscreen {
95        dialog_classes.push("modal-fullscreen");
96    } else if let Some(size) = &props.size {
97        dialog_classes.push(format!("modal-{}", size));
98    }
99    if let Some(dialog_class) = &props.dialog_class {
100        dialog_classes.push(dialog_class.to_string());
101    }
102    html! {
103        < div class = { modal_classes } tabindex = "-1" aria - hidden = { (! props.show)
104        .to_string() } ref = { modal_ref } data - bs - backdrop = { if props
105        .static_backdrop { "static" } else { "true" } } data - bs - keyboard = "true" > <
106        div class = { dialog_classes } > < div class = "modal-content" > if props.title
107        .is_some() || props.close_button { < div class = "modal-header" > if let
108        Some(title) = & props.title { < h5 class = "modal-title" > { title.clone() } </
109        h5 > } if props.close_button { < button type = "button" class = "btn-close" data
110        - bs - dismiss = "modal" aria - label = "Close" /> } </ div > } < div class =
111        "modal-body" > { for props.children.iter() } </ div > </ div > </ div > </ div >
112    }
113}
114#[derive(Properties, PartialEq)]
115pub struct ModalBodyProps {
116    #[prop_or_default]
117    pub children: Children,
118    #[prop_or_default]
119    pub class: Option<AttrValue>,
120    #[prop_or_default]
121    pub node_ref: NodeRef,
122}
123#[function_component(ModalBody)]
124pub fn modal_body(props: &ModalBodyProps) -> Html {
125    let mut classes = Classes::new();
126    classes.push("modal-body");
127    if let Some(class) = &props.class {
128        classes.push(class.to_string());
129    }
130    html! {
131        < div class = { classes } ref = { props.node_ref.clone() } > { for props.children
132        .iter() } </ div >
133    }
134}
135#[derive(Properties, PartialEq)]
136pub struct ModalFooterProps {
137    #[prop_or_default]
138    pub children: Children,
139    #[prop_or_default]
140    pub class: Option<AttrValue>,
141    #[prop_or_default]
142    pub node_ref: NodeRef,
143}
144#[function_component(ModalFooter)]
145pub fn modal_footer(props: &ModalFooterProps) -> Html {
146    let mut classes = Classes::new();
147    classes.push("modal-footer");
148    if let Some(class) = &props.class {
149        classes.push(class.to_string());
150    }
151    html! {
152        < div class = { classes } ref = { props.node_ref.clone() } > { for props.children
153        .iter() } </ div >
154    }
155}
156#[derive(Properties, PartialEq)]
157pub struct ModalHeaderProps {
158    #[prop_or_default]
159    pub children: Children,
160    #[prop_or(true)]
161    pub close_button: bool,
162    #[prop_or_default]
163    pub class: Option<AttrValue>,
164    #[prop_or_default]
165    pub node_ref: NodeRef,
166}
167#[function_component(ModalHeader)]
168pub fn modal_header(props: &ModalHeaderProps) -> Html {
169    let mut classes = Classes::new();
170    classes.push("modal-header");
171    if let Some(class) = &props.class {
172        classes.push(class.to_string());
173    }
174    html! {
175        < div class = { classes } ref = { props.node_ref.clone() } > { for props.children
176        .iter() } if props.close_button { < button type = "button" class = "btn-close"
177        data - bs - dismiss = "modal" aria - label = "Close" /> } </ div >
178    }
179}
180#[derive(Properties, PartialEq)]
181pub struct ModalTitleProps {
182    #[prop_or_default]
183    pub children: Children,
184    #[prop_or_default]
185    pub class: Option<AttrValue>,
186    #[prop_or_default]
187    pub node_ref: NodeRef,
188}
189#[function_component(ModalTitle)]
190pub fn modal_title(props: &ModalTitleProps) -> Html {
191    let mut classes = Classes::new();
192    classes.push("modal-title");
193    if let Some(class) = &props.class {
194        classes.push(class.to_string());
195    }
196    html! {
197        < h5 class = { classes } ref = { props.node_ref.clone() } > { for props.children
198        .iter() } </ h5 >
199    }
200}