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 get_theme,
15 theming::component_themes::AccordionThemePartial,
16};
17
18#[cfg_attr(feature = "docs",
53 doc = embed_doc_image::embed_image!("accordion", "images/gallery_accordion.png")
54)]
55#[derive(Clone, PartialEq, Default)]
56pub struct Accordion {
57 pub(crate) theme: Option<AccordionThemePartial>,
58 header: Option<Element>,
59 children: Vec<Element>,
60 key: DiffKey,
61}
62
63impl KeyExt for Accordion {
64 fn write_key(&mut self) -> &mut DiffKey {
65 &mut self.key
66 }
67}
68
69impl Accordion {
70 pub fn new() -> Self {
71 Self::default()
72 }
73
74 pub fn header<C: Into<Element>>(mut self, header: C) -> Self {
75 self.header = Some(header.into());
76 self
77 }
78}
79
80impl ChildrenExt for Accordion {
81 fn get_children(&mut self) -> &mut Vec<Element> {
82 &mut self.children
83 }
84}
85
86impl Render for Accordion {
87 fn render(self: &Accordion) -> impl IntoElement {
88 let header = use_focus();
89 let accordion_theme = get_theme!(&self.theme, accordion);
90 let mut open = use_state(|| false);
91 let mut animation = use_animation(move |_conf| {
92 AnimNum::new(0., 100.)
93 .time(300)
94 .function(Function::Expo)
95 .ease(Ease::Out)
96 });
97
98 let clip_percent = animation.get().value();
99
100 rect()
101 .a11y_id(header.a11y_id())
102 .a11y_role(AccessibilityRole::Header)
103 .a11y_focusable(true)
104 .corner_radius(CornerRadius::new_all(8.))
105 .padding(Gaps::new_all(8.))
106 .color(accordion_theme.color)
107 .background(accordion_theme.background)
108 .border(
109 Border::new()
110 .fill(accordion_theme.border_fill)
111 .width(1.)
112 .alignment(BorderAlignment::Inner),
113 )
114 .on_pointer_enter(move |_| {
115 Cursor::set(CursorIcon::Pointer);
116 })
117 .on_pointer_leave(move |_| {
118 Cursor::set(CursorIcon::default());
119 })
120 .on_press(move |_| {
121 if open.toggled() {
122 animation.start();
123 } else {
124 animation.reverse();
125 }
126 })
127 .maybe_child(self.header.clone())
128 .child(
129 rect()
130 .a11y_role(AccessibilityRole::Region)
131 .a11y_builder(|b| {
132 b.set_labelled_by([header.a11y_id()]);
133 if !open() {
134 b.set_hidden();
135 }
136 })
137 .overflow(Overflow::Clip)
138 .visible_height(VisibleSize::inner_percent(clip_percent))
139 .children(self.children.clone()),
140 )
141 }
142
143 fn render_key(&self) -> DiffKey {
144 self.key.clone().or(self.default_key())
145 }
146}