Skip to main content

dioxus_tw_components/components/
sidepanel.rs

1use crate::components::icon::*;
2use crate::dioxus_core::IntoAttributeValue;
3use dioxus::prelude::*;
4use dioxus_core::AttributeValue;
5
6#[derive(Clone, Copy)]
7pub struct SidePanelState {
8    is_active: bool,
9}
10
11impl SidePanelState {
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    pub fn open(&mut self) {
21        self.is_active = true;
22    }
23
24    pub fn close(&mut self) {
25        self.is_active = false;
26    }
27}
28
29impl IntoAttributeValue for SidePanelState {
30    fn into_value(self) -> AttributeValue {
31        match self.is_active {
32            true => AttributeValue::Text("active".to_string()),
33            false => AttributeValue::Text("inactive".to_string()),
34        }
35    }
36}
37
38#[derive(Clone, PartialEq, Props)]
39pub struct SidePanelProps {
40    #[props(default = false)]
41    is_active: bool,
42
43    children: Element,
44}
45
46#[component]
47pub fn SidePanel(props: SidePanelProps) -> Element {
48    use_context_provider(|| Signal::new(SidePanelState::new(props.is_active)));
49
50    rsx! {
51        {props.children}
52    }
53}
54
55#[derive(Clone, PartialEq, Props)]
56pub struct SidePanelTriggerProps {
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 SidePanelTrigger(mut props: SidePanelTriggerProps) -> Element {
68    let mut state = use_context::<Signal<SidePanelState>>();
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        state.write().open();
75        props.onclick.call(event)
76    };
77
78    rsx! {
79        button { onclick, ..props.attributes, {props.children} }
80    }
81}
82
83#[derive(Clone, PartialEq, Props)]
84pub struct SidePanelCloseProps {
85    #[props(extends = div, extends = GlobalAttributes)]
86    attributes: Vec<Attribute>,
87
88    #[props(default)]
89    children: Element,
90}
91
92impl std::default::Default for SidePanelCloseProps {
93    fn default() -> Self {
94        Self {
95            attributes: Vec::<Attribute>::default(),
96            children: Ok(VNode::default()), // Default this way to be able to check the children in SidePanelClose
97        }
98    }
99}
100
101/// Div to close the content side panel, by default it is a cross located at the top left corner of the side panel
102/// If you provide a children, it will be used instead of the default cross and no internal styling will be provided
103#[component]
104pub fn SidePanelClose(mut props: SidePanelCloseProps) -> Element {
105    let mut state = use_context::<Signal<SidePanelState>>();
106
107    let has_children = props.children != Ok(VNode::default());
108
109    if !has_children {
110        let default_classes = "sidepanel-close";
111        crate::setup_class_attribute(&mut props.attributes, default_classes);
112    }
113
114    let onclick = move |event: Event<MouseData>| {
115        event.stop_propagation();
116        state.write().close();
117    };
118
119    rsx! {
120        div { onclick, ..props.attributes,
121            if !has_children {
122                Icon { icon: Icons::Close }
123            } else {
124                {props.children}
125            }
126        }
127    }
128}
129
130#[derive(Clone, PartialEq, Props)]
131pub struct SidePanelContentProps {
132    #[props(extends = div, extends = GlobalAttributes)]
133    attributes: Vec<Attribute>,
134
135    children: Element,
136}
137
138#[component]
139pub fn SidePanelContent(mut props: SidePanelContentProps) -> Element {
140    let mut state = use_context::<Signal<SidePanelState>>();
141
142    let default_classes = "sidepanel-content";
143    crate::setup_class_attribute(&mut props.attributes, default_classes);
144
145    let onkeydown = move |event: Event<KeyboardData>| {
146        if event.key() == Key::Escape {
147            state.write().close();
148        }
149    };
150
151    let mut panel_ref: Signal<Option<MountedEvent>> = use_signal(|| None);
152    let onmounted = move |event: MountedEvent| {
153        panel_ref.set(Some(event));
154    };
155
156    // Auto-focus on open
157    use_effect(move || {
158        if state.read().is_active
159            && let Some(ref el) = *panel_ref.read()
160        {
161            let el = el.clone();
162            spawn(async move {
163                let _ = document::eval("await new Promise(r => setTimeout(r, 100))").await;
164                let _ = el.set_focus(true).await;
165            });
166        }
167    });
168
169    rsx! {
170        div {
171            tabindex: "0",
172            onkeydown,
173            onmounted,
174            "data-state": state.read().into_value(),
175            ..props.attributes,
176            {props.children}
177        }
178    }
179}
180
181#[derive(Clone, PartialEq, Props)]
182pub struct SidePanelBackgroundProps {
183    #[props(optional, default = true)]
184    interactive: bool,
185
186    #[props(extends = div, extends = GlobalAttributes)]
187    attributes: Vec<Attribute>,
188
189    #[props(optional, default)]
190    onclick: EventHandler<MouseEvent>,
191
192    children: Element,
193}
194
195#[component]
196pub fn SidePanelBackground(mut props: SidePanelBackgroundProps) -> Element {
197    let mut state = use_context::<Signal<SidePanelState>>();
198
199    let default_classes = "sidepanel-background";
200    crate::setup_class_attribute(&mut props.attributes, default_classes);
201
202    let onclick = move |event: Event<MouseData>| {
203        event.stop_propagation();
204        if props.interactive {
205            state.write().close();
206            props.onclick.call(event)
207        }
208    };
209
210    rsx! {
211        div {
212            "data-state": state.read().into_value(),
213            onclick,
214            ..props.attributes,
215            {props.children}
216        }
217    }
218}