Skip to main content

dioxus_tw_components/components/
modal.rs

1use crate::components::icon::*;
2use crate::dioxus_core::IntoAttributeValue;
3use dioxus::prelude::*;
4use dioxus_core::AttributeValue;
5
6#[derive(Clone, Copy)]
7pub struct ModalState {
8    is_active: bool,
9}
10
11impl ModalState {
12    fn new(is_active: bool) -> Self {
13        Self { is_active }
14    }
15
16    pub fn toggle(&mut self) {
17        self.is_active = !self.is_active;
18    }
19}
20
21impl IntoAttributeValue for ModalState {
22    fn into_value(self) -> AttributeValue {
23        match self.is_active {
24            true => AttributeValue::Text("active".to_string()),
25            false => AttributeValue::Text("inactive".to_string()),
26        }
27    }
28}
29
30#[derive(Clone, PartialEq, Props)]
31pub struct ModalProps {
32    #[props(default = false)]
33    is_active: bool,
34
35    children: Element,
36}
37
38#[component]
39pub fn Modal(props: ModalProps) -> Element {
40    let mut state = use_context_provider(|| Signal::new(ModalState::new(props.is_active)));
41
42    rsx! {
43        div {
44            tabindex: 0,
45            onkeydown: move |e: KeyboardEvent| {
46                if e.key() == Key::Escape {
47                    state.write().toggle();
48                }
49            },
50            {props.children}
51        }
52    }
53}
54
55#[derive(Clone, PartialEq, Props)]
56pub struct ModalTriggerProps {
57    #[props(extends = div, extends = GlobalAttributes)]
58    attributes: Vec<Attribute>,
59
60    #[props(optional, default)]
61    onclick: EventHandler<MouseEvent>,
62
63    children: Element,
64}
65
66#[component]
67pub fn ModalTrigger(mut props: ModalTriggerProps) -> Element {
68    let mut state = use_context::<Signal<ModalState>>();
69
70    let default_classes = "button";
71    crate::setup_class_attribute(&mut props.attributes, default_classes);
72
73    let onclick = move |event: Event<MouseData>| {
74        event.stop_propagation();
75        state.write().toggle();
76        props.onclick.call(event)
77    };
78
79    rsx! {
80        button { onclick, ..props.attributes, {props.children} }
81    }
82}
83
84#[derive(Clone, PartialEq, Props)]
85pub struct ModalCloseProps {
86    #[props(extends = div, extends = GlobalAttributes)]
87    attributes: Vec<Attribute>,
88
89    #[props(default)]
90    children: Element,
91}
92
93impl std::default::Default for ModalCloseProps {
94    fn default() -> Self {
95        Self {
96            attributes: Vec::<Attribute>::default(),
97            children: Ok(VNode::default()), // Default this way to be able to check the children in SidePanelClose
98        }
99    }
100}
101
102/// Div to close the content modal, by default it is a cross located at the top left corner of the modal
103/// If you provide a children, it will be used instead of the default cross and no internal styling will be provided
104#[component]
105pub fn ModalClose(mut props: ModalCloseProps) -> Element {
106    let mut state = use_context::<Signal<ModalState>>();
107
108    let has_children = props.children != Ok(VNode::default());
109
110    if !has_children {
111        let default_classes = "modal-close";
112        crate::setup_class_attribute(&mut props.attributes, default_classes);
113    }
114
115    let onclick = move |event: Event<MouseData>| {
116        event.stop_propagation();
117        state.write().toggle();
118    };
119
120    rsx! {
121        div {
122            "data-state": state.read().into_value(),
123            onclick,
124            ..props.attributes,
125            if !has_children {
126                Icon { icon: Icons::Close }
127            } else {
128                {props.children}
129            }
130        }
131    }
132}
133
134#[derive(Clone, PartialEq, Props)]
135pub struct ModalContentProps {
136    #[props(extends = div, extends = GlobalAttributes)]
137    attributes: Vec<Attribute>,
138
139    children: Element,
140}
141
142#[component]
143pub fn ModalContent(mut props: ModalContentProps) -> Element {
144    let state = use_context::<Signal<ModalState>>();
145
146    let default_classes = "modal-content";
147    crate::setup_class_attribute(&mut props.attributes, default_classes);
148
149    rsx! {
150        div {
151            "data-state": state.read().into_value(),
152            ..props.attributes,
153            {props.children}
154        }
155    }
156}
157
158#[derive(Clone, PartialEq, Props)]
159pub struct ModalBackgroundProps {
160    #[props(optional, default = true)]
161    interactive: bool,
162
163    #[props(extends = div, extends = GlobalAttributes)]
164    attributes: Vec<Attribute>,
165
166    #[props(optional, default)]
167    onclick: EventHandler<MouseEvent>,
168
169    children: Element,
170}
171
172#[component]
173pub fn ModalBackground(mut props: ModalBackgroundProps) -> Element {
174    let mut state = use_context::<Signal<ModalState>>();
175
176    let default_classes = "modal-background";
177    crate::setup_class_attribute(&mut props.attributes, default_classes);
178
179    let onclick = move |event: Event<MouseData>| {
180        event.stop_propagation();
181        if props.interactive {
182            state.write().toggle();
183            props.onclick.call(event)
184        }
185    };
186
187    rsx! {
188        div {
189            "data-state": state.read().into_value(),
190            onclick,
191            ..props.attributes,
192            {props.children}
193        }
194    }
195}