freya_components/
accordion.rs1use freya_animation::prelude::{
2 AnimNum,
3 Ease,
4 Function,
5 use_animation,
6};
7use freya_core::prelude::*;
8use torin::{
9 gaps::Gaps,
10 prelude::VisibleSize,
11};
12
13use crate::{
14 define_theme,
15 get_theme,
16};
17
18define_theme! {
19 %[component]
20 pub Accordion {
21 %[fields]
22 color: Color,
23 background: Color,
24 border_fill: Color,
25 }
26}
27
28#[cfg_attr(feature = "docs",
63 doc = embed_doc_image::embed_image!("accordion", "images/gallery_accordion.png")
64)]
65#[derive(Clone, PartialEq, Default)]
66pub struct Accordion {
67 pub(crate) theme: Option<AccordionThemePartial>,
68 header: Option<Element>,
69 children: Vec<Element>,
70 cursor_icon: CursorIcon,
71 key: DiffKey,
72}
73
74impl KeyExt for Accordion {
75 fn write_key(&mut self) -> &mut DiffKey {
76 &mut self.key
77 }
78}
79
80impl Accordion {
81 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn header<C: Into<Element>>(mut self, header: C) -> Self {
86 self.header = Some(header.into());
87 self
88 }
89
90 pub fn cursor_icon(mut self, cursor_icon: impl Into<CursorIcon>) -> Self {
92 self.cursor_icon = cursor_icon.into();
93 self
94 }
95}
96
97impl ChildrenExt for Accordion {
98 fn get_children(&mut self) -> &mut Vec<Element> {
99 &mut self.children
100 }
101}
102
103impl Component for Accordion {
104 fn render(self: &Accordion) -> impl IntoElement {
105 let header_a11y_id = use_a11y();
106 let accordion_theme = get_theme!(&self.theme, AccordionThemePreference, "accordion");
107 let cursor_icon = self.cursor_icon;
108 let mut open = use_state(|| false);
109 let mut animation = use_animation(move |_conf| {
110 AnimNum::new(0., 100.)
111 .time(300)
112 .function(Function::Expo)
113 .ease(Ease::Out)
114 });
115
116 let clip_percent = animation.get().value();
117
118 rect()
119 .a11y_id(header_a11y_id)
120 .a11y_role(AccessibilityRole::Header)
121 .a11y_focusable(true)
122 .corner_radius(CornerRadius::new_all(8.))
123 .padding(Gaps::new_all(8.))
124 .color(accordion_theme.color)
125 .background(accordion_theme.background)
126 .border(
127 Border::new()
128 .fill(accordion_theme.border_fill)
129 .width(1.)
130 .alignment(BorderAlignment::Inner),
131 )
132 .on_pointer_enter(move |_| {
133 Cursor::set(cursor_icon);
134 })
135 .on_pointer_leave(move |_| {
136 Cursor::set(CursorIcon::default());
137 })
138 .on_press(move |_| {
139 if open.toggled() {
140 animation.start();
141 } else {
142 animation.reverse();
143 }
144 })
145 .maybe_child(self.header.clone())
146 .child(
147 rect()
148 .a11y_role(AccessibilityRole::Region)
149 .a11y_builder(|b| {
150 b.set_labelled_by([header_a11y_id]);
151 if !open() {
152 b.set_hidden();
153 }
154 })
155 .overflow(Overflow::Clip)
156 .visible_height(VisibleSize::inner_percent(clip_percent))
157 .children(self.children.clone()),
158 )
159 }
160
161 fn render_key(&self) -> DiffKey {
162 self.key.clone().or(self.default_key())
163 }
164}