freya_components/
accordion.rs1use dioxus::prelude::*;
2use freya_core::platform::CursorIcon;
3use freya_elements::{
4 self as dioxus_elements,
5 events::MouseEvent,
6};
7use freya_hooks::{
8 use_animation,
9 use_applied_theme,
10 use_platform,
11 AccordionTheme,
12 AccordionThemeWith,
13 AnimNum,
14 Ease,
15 Function,
16};
17
18#[derive(Debug, Default, PartialEq, Clone, Copy)]
20pub enum AccordionStatus {
21 #[default]
23 Idle,
24 Hovering,
26}
27
28#[derive(Props, Clone, PartialEq)]
30pub struct AccordionProps {
31 pub theme: Option<AccordionThemeWith>,
33 pub children: Element,
35 pub summary: Element,
37 #[props(default = false)]
39 pub initial_open: bool,
40}
41
42#[allow(non_snake_case)]
47pub fn Accordion(props: AccordionProps) -> Element {
48 let theme = use_applied_theme!(&props.theme, accordion);
49 let mut open = use_signal(|| props.initial_open);
50 let animation = use_animation(move |_conf| {
51 AnimNum::new(0., 100.)
52 .time(300)
53 .function(Function::Expo)
54 .ease(Ease::Out)
55 });
56 let mut status = use_signal(AccordionStatus::default);
57 let platform = use_platform();
58
59 let animation_value = animation.get().read().read();
60 let AccordionTheme {
61 background,
62 color,
63 border_fill,
64 } = theme;
65
66 let onclick = move |_: MouseEvent| {
67 open.toggle();
68 if *open.read() {
69 animation.start();
70 } else {
71 animation.reverse();
72 }
73 };
74
75 use_drop(move || {
76 if *status.read() == AccordionStatus::Hovering {
77 platform.set_cursor(CursorIcon::default());
78 }
79 });
80
81 let onmouseenter = move |_| {
82 platform.set_cursor(CursorIcon::Pointer);
83 status.set(AccordionStatus::Hovering);
84 };
85
86 let onmouseleave = move |_| {
87 platform.set_cursor(CursorIcon::default());
88 status.set(AccordionStatus::default());
89 };
90
91 rsx!(
92 rect {
93 onmouseenter,
94 onmouseleave,
95 overflow: "clip",
96 color: "{color}",
97 padding: "10",
98 margin: "2 4",
99 corner_radius: "6",
100 width: "100%",
101 height: "auto",
102 background: "{background}",
103 onclick,
104 border: "1 inner {border_fill}",
105 {&props.summary}
106 rect {
107 overflow: "clip",
108 width: "100%",
109 visible_height: "{animation_value}%",
110 {&props.children}
111 }
112 }
113 )
114}
115
116#[derive(Props, Clone, PartialEq)]
118pub struct AccordionSummaryProps {
119 children: Element,
121}
122
123#[allow(non_snake_case)]
125pub fn AccordionSummary(props: AccordionSummaryProps) -> Element {
126 rsx!({ props.children })
127}
128
129#[derive(Props, Clone, PartialEq)]
131pub struct AccordionBodyProps {
132 children: Element,
134}
135
136#[allow(non_snake_case)]
138pub fn AccordionBody(props: AccordionBodyProps) -> Element {
139 rsx!(rect {
140 width: "100%",
141 padding: "15 0 0 0",
142 {props.children}
143 })
144}
145
146#[cfg(test)]
147mod test {
148 use std::time::Duration;
149
150 use freya::prelude::*;
151 use freya_testing::prelude::*;
152 use tokio::time::sleep;
153
154 #[tokio::test]
155 pub async fn accordion() {
156 fn accordion_app() -> Element {
157 rsx!(
158 Accordion {
159 summary: rsx!(AccordionSummary {
160 label {
161 "Accordion Summary"
162 }
163 }),
164 AccordionBody {
165 label {
166 "Accordion Body"
167 }
168 }
169 }
170 )
171 }
172
173 let mut utils = launch_test(accordion_app);
174
175 let root = utils.root();
176 let content = root.get(0).get(1).get(0);
177 let label = content.get(0);
178 utils.wait_for_update().await;
179
180 assert!(!label.is_visible());
182
183 utils.click_cursor((5., 5.)).await;
185
186 sleep(Duration::from_millis(70)).await;
188 utils.wait_for_update().await;
189
190 assert!(label.is_visible());
192 }
193}