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