accordion_rs/yew.rs
1use crate::common::{Align, Size};
2use yew::prelude::*;
3
4/// Properties for the Accordion component.
5#[derive(Properties, Clone, PartialEq)]
6pub struct AccordionProps {
7 /// A state handle that manages the expansion state of the accordion.
8 ///
9 /// This property determines whether the accordion is initially expanded or collapsed.
10 /// It is a required prop and should be used to toggle the accordion's expanded state.
11 pub expand: UseStateHandle<bool>,
12
13 /// The content to be displayed when the accordion is expanded.
14 ///
15 /// Defines the HTML content shown when the accordion is expanded. Defaults to an empty string.
16 #[prop_or_default]
17 pub expanded: Html,
18
19 /// The content to be displayed when the accordion is collapsed.
20 ///
21 /// Defines the HTML content shown when the accordion is collapsed. Defaults to an empty string.
22 #[prop_or_default]
23 pub collapsed: Html,
24
25 /// The child elements within the accordion.
26 ///
27 /// Any child HTML content that will be displayed within the accordion container. Defaults to an empty string.
28 #[prop_or_default]
29 pub children: ChildrenWithProps<List>,
30
31 /// Size of the accordion.
32 ///
33 /// Defines the size of the accordion component, such as "small", "medium", or "large". Defaults to `Size::XXLarge`.
34 #[prop_or_default]
35 pub size: Size,
36
37 /// ARIA controls attribute for accessibility.
38 ///
39 /// Provides an accessibility feature for screen readers, linking the accordion with other elements. Defaults to an empty string.
40 #[prop_or_default]
41 pub aria_controls: &'static str,
42
43 /// Custom inline styles for the container.
44 ///
45 /// Allows for custom styling of the accordion container. Defaults to an empty string.
46 #[prop_or_default]
47 pub style: &'static str,
48
49 /// Custom inline styles for the expanded element.
50 ///
51 /// Allows for custom styling of the expanded element. Defaults to an empty string.
52 #[prop_or_default]
53 pub expanded_style: &'static str,
54
55 /// Custom inline styles for the collapsed element.
56 ///
57 /// Allows for custom styling of the collapsed element. Defaults to an empty string.
58 #[prop_or_default]
59 pub collapsed_style: &'static str,
60
61 /// Custom inline styles for the content container.
62 ///
63 /// Allows for custom styling of the content section. Defaults to an empty string.
64 #[prop_or_default]
65 pub content_style: &'static str,
66
67 /// Custom class for the container.
68 ///
69 /// Applies a custom CSS class to the accordion container. Defaults to an empty string.
70 #[prop_or_default]
71 pub class: &'static str,
72
73 /// Custom class for the expanded element.
74 ///
75 /// Applies a custom CSS class to the expanded content. Defaults to an empty string.
76 #[prop_or_default]
77 pub expanded_class: &'static str,
78
79 /// Custom class for the collapsed element.
80 ///
81 /// Applies a custom CSS class to the collapsed content. Defaults to an empty string.
82 #[prop_or_default]
83 pub collapsed_class: &'static str,
84
85 /// Custom class for the content container.
86 ///
87 /// Applies a custom CSS class to the content section. Defaults to an empty string.
88 #[prop_or_default]
89 pub content_class: &'static str,
90
91 /// Whether ARIA attributes should be added to the HTML structure.
92 ///
93 /// If `true`, ARIA attributes will be included for better accessibility. Defaults to `true`.
94 #[prop_or(true)]
95 pub aria_enabled: bool,
96
97 /// Duration of the animation in milliseconds.
98 ///
99 /// Defines the animation speed for expanding or collapsing the accordion. Defaults to `600` milliseconds.
100 #[prop_or(600)]
101 pub duration: u64,
102
103 /// Callback executed before the accordion item is opened.
104 ///
105 /// This callback is triggered before the accordion expands. Defaults to no-op.
106 #[prop_or_default]
107 pub will_open: Callback<()>,
108
109 /// Callback executed when the accordion item is opened.
110 ///
111 /// This callback is triggered after the accordion has expanded. Defaults to no-op.
112 #[prop_or_default]
113 pub did_open: Callback<()>,
114
115 /// Callback executed before the accordion item is closed.
116 ///
117 /// This callback is triggered before the accordion collapses. Defaults to no-op.
118 #[prop_or_default]
119 pub will_close: Callback<()>,
120
121 /// Callback executed when the accordion item is closed.
122 ///
123 /// This callback is triggered after the accordion has collapsed. Defaults to no-op.
124 #[prop_or_default]
125 pub did_close: Callback<()>,
126}
127
128/// Accordion Component
129///
130/// A Yew component for displaying an accordion-style UI element that can be expanded or collapsed.
131/// This `Accordion` component supports customizing its appearance, animations, and behavior during
132/// expansion or collapse. The component can hold content within a collapsible section, which can be
133/// expanded or collapsed by the user. It also supports various customization options, such as custom
134/// styles, classes, and accessibility features like ARIA attributes.
135///
136/// # Properties
137/// The component uses the `AccordionProps` struct for its properties. Key properties include:
138///
139/// - **expand**: A state handle that manages the expansion state of the accordion (`UseStateHandle<bool>`).
140/// - **expanded**: The content to display when the accordion is expanded (`Html`). Default: `""`.
141/// - **collapsed**: The content to display when the accordion is collapsed (`Html`). Default: `""`.
142/// - **children**: The child elements inside the accordion (`Html`). Default: `""`.
143/// - **size**: Defines the size of the accordion (`Size`). Default: `Size::XXLarge`.
144/// - **aria_controls**: The ARIA controls attribute for accessibility (`&'static str`). Default: `""`.
145/// - **style**: Custom inline styles for the accordion container (`String`). Default: `""`.
146/// - **expanded_style**: Custom inline styles for the expanded accordion section (`String`). Default: `""`.
147/// - **collapsed_style**: Custom inline styles for the collapsed accordion section (`String`). Default: `""`.
148/// - **content_style**: Custom inline styles for the content container (`String`). Default: `""`.
149/// - **class**: Custom CSS class for the accordion container (`String`). Default: `""`.
150/// - **expanded_class**: Custom CSS class for the expanded accordion section (`String`). Default: `""`.
151/// - **collapsed_class**: Custom CSS class for the collapsed accordion section (`String`). Default: `""`.
152/// - **content_class**: Custom CSS class for the content container (`String`). Default: `""`.
153/// - **aria_enabled**: Whether ARIA attributes should be added for accessibility (`bool`). Default: `true`.
154/// - **duration**: Duration of the animation when expanding or collapsing (`u64`). Default: `600`.
155/// - **will_open**: Callback triggered before the accordion expands (`Callback<()>`). Default: no-op.
156/// - **did_open**: Callback triggered after the accordion expands (`Callback<()>`). Default: no-op.
157/// - **will_close**: Callback triggered before the accordion collapses (`Callback<()>`). Default: no-op.
158/// - **did_close**: Callback triggered after the accordion collapses (`Callback<()>`). Default: no-op.
159///
160/// # Features
161/// - Customizable expanded and collapsed content.
162/// - Animation duration for smooth transitions between states.
163/// - ARIA accessibility features for improved screen reader support.
164/// - Callbacks for tracking expansion and collapse events.
165///
166/// # Examples
167///
168/// ## Basic Usage
169/// ```rust
170/// use yew::prelude::*;
171/// use accordion_rs::yew::Accordion;
172///
173/// #[function_component(App)]
174/// pub fn app() -> Html {
175/// let expand = use_state(|| false);
176///
177/// html! {
178/// <Accordion
179/// expand={expand}
180/// expanded="This is expanded content"
181/// collapsed="This is collapsed content"
182/// />
183/// }
184/// }
185/// ```
186///
187/// ## Accordion with Custom Styles
188/// ```rust
189/// use yew::prelude::*;
190/// use accordion_rs::yew::Accordion;
191///
192/// #[function_component(App)]
193/// pub fn app() -> Html {
194/// let expand = use_state(|| false);
195///
196/// html! {
197/// <Accordion
198/// expand={expand}
199/// expanded="This is expanded content"
200/// collapsed="This is collapsed content"
201/// style="background-color: lightblue; padding: 10px"
202/// expanded_style="background-color: lightgreen"
203/// collapsed_style="background-color: lightcoral"
204/// />
205/// }
206/// }
207/// ```
208///
209/// ## Accordion with Callbacks
210/// ```rust
211/// use yew::prelude::*;
212/// use accordion_rs::yew::Accordion;
213///
214/// #[function_component(App)]
215/// pub fn app() -> Html {
216/// let expand = use_state(|| false);
217///
218/// let will_open = Callback::from(|_| log::info!("Accordion is about to open"));
219/// let did_open = Callback::from(|_| log::info!("Accordion has opened"));
220/// let will_close = Callback::from(|_| log::info!("Accordion is about to close"));
221/// let did_close = Callback::from(|_| log::info!("Accordion has closed"));
222///
223/// html! {
224/// <Accordion
225/// expand={expand}
226/// expanded="This is expanded content"
227/// collapsed="This is collapsed content"
228/// will_open={will_open}
229/// did_open={did_open}
230/// will_close={will_close}
231/// did_close={did_close}
232/// />
233/// }
234/// }
235/// ```
236///
237/// # Behavior
238/// - The component uses a state to track whether the accordion is expanded or collapsed.
239/// - Clicking the accordion header toggles between expanded and collapsed states, with smooth animation transitions.
240/// - It emits callbacks when the accordion is about to open or close, and after those actions have completed.
241/// - ARIA attributes are dynamically added for accessibility when `aria_enabled` is set to `true`.
242///
243/// # Notes
244/// - The `aria_enabled` property can be set to `false` to disable ARIA attributes for cases where they are not needed.
245/// - The `will_open`, `did_open`, `will_close`, and `did_close` callbacks can be used to hook into the accordion's state changes for custom behavior.
246/// - The `size` property allows customization of the accordion's size (e.g., `Size::Small`, `Size::Medium`, `Size::Large`).
247#[function_component]
248pub fn Accordion(props: &AccordionProps) -> Html {
249 let is_expanded = &props.expand;
250 let is_expanded_value = **is_expanded;
251
252 let toggle_expansion = {
253 let is_expanded = is_expanded.clone();
254 let props = props.clone();
255
256 move |e: MouseEvent| {
257 e.prevent_default();
258
259 if is_expanded_value {
260 props.will_close.emit(());
261 is_expanded.set(false);
262 props.did_close.emit(());
263 } else {
264 props.will_open.emit(());
265 is_expanded.set(true);
266 props.did_open.emit(());
267 }
268 }
269 };
270
271 html! {
272 <div
273 style={format!(
274 "{} {}",
275 props.size.to_style(),
276 props.style
277 )}
278 class={props.class}
279 >
280 <div
281 aria-expanded={if props.aria_enabled { Some(is_expanded_value.to_string()) } else { None }}
282 aria-controls={if props.aria_enabled { Some(props.aria_controls) } else { None }}
283 onclick={toggle_expansion.clone()}
284 class={if is_expanded_value {
285 props.expanded_class
286 } else {
287 props.collapsed_class
288 }}
289 style={format!(
290 "cursor: pointer; transition: all {}ms; {}",
291 props.duration,
292 if is_expanded_value {
293 props.expanded_style
294 } else {
295 props.collapsed_style
296 }
297 )}
298 >
299 { if is_expanded_value { props.expanded.clone() } else { props.collapsed.clone() } }
300 </div>
301 { if is_expanded_value {
302 html! {
303 <div
304 id={props.aria_controls}
305 class={props.content_class}
306 style={format!(
307 "overflow: hidden; transition: all {}ms; {}",
308 props.duration,
309 props.content_style
310 )}
311 >
312 { for props.children.iter() }
313 </div>
314 }
315 } else {
316 html! {}
317 } }
318 </div>
319 }
320}
321
322/// Properties for the Item component.
323#[derive(Clone, PartialEq, Properties)]
324pub struct ItemProps {
325 /// The content of the Item.
326 ///
327 /// Defines the HTML content that will be displayed inside the accordion item. Defaults to an empty string.
328 #[prop_or_default]
329 pub children: Html,
330
331 /// Additional inline styles for the Item.
332 ///
333 /// Allows for custom styling of the accordion item. Defaults to an empty string.
334 #[prop_or_default]
335 pub style: &'static str,
336
337 /// Additional class for the Item.
338 ///
339 /// Applies a custom CSS class to the accordion item. Defaults to an empty string.
340 #[prop_or_default]
341 pub class: &'static str,
342
343 /// Alignment of the content inside the Item.
344 ///
345 /// Defines the alignment of content within the accordion item, such as left, right, or center. Defaults to `Align::Left`.
346 #[prop_or_default]
347 pub align: Align,
348
349 /// The title of the Item.
350 ///
351 /// Provides the title or heading for the accordion item. Defaults to `None`.
352 #[prop_or_default]
353 pub title: &'static str,
354
355 /// The icon for the Item.
356 ///
357 /// Specifies an optional icon to be displayed alongside the title. Defaults to `None`.
358 #[prop_or_default]
359 pub icon: &'static str,
360}
361
362/// Item component.
363#[function_component]
364pub fn Item(props: &ItemProps) -> Html {
365 html! {
366 <li
367 class={props.class}
368 style={format!(
369 "{} {}",
370 props.align.to_style(),
371 props.style
372 )}
373 >
374 { if !props.icon.is_empty() {
375 html! { <span class="mr-2">{ props.icon }</span> }
376 } else {
377 html! {}
378 } }
379 { if !props.title.is_empty() {
380 html! { <strong>{ props.title }</strong> }
381 } else {
382 html! {}
383 } }
384 { props.children.clone() }
385 </li>
386 }
387}
388
389/// Properties for the Button component.
390#[derive(Clone, PartialEq, Properties)]
391pub struct ButtonProps {
392 /// The content of the Button.
393 ///
394 /// Defines the text or HTML content displayed on the button. Defaults to an empty string.
395 #[prop_or_default]
396 pub children: Html,
397
398 /// Additional inline styles for the Button.
399 ///
400 /// Allows for custom styling of the button. Defaults to an empty string.
401 #[prop_or_default]
402 pub style: &'static str,
403
404 /// Additional inline styles for the Button.
405 ///
406 /// Allows for custom CSS class styling for the button. Defaults to an empty string.
407 #[prop_or_default]
408 pub class: &'static str,
409}
410
411/// Button component.
412#[function_component]
413pub fn Button(props: &ButtonProps) -> Html {
414 html! {
415 <button class={props.class} style={props.style}>
416 { props.children.clone() }
417 </button>
418 }
419}
420
421/// Properties for the List component.
422#[derive(Clone, PartialEq, Properties)]
423pub struct ListProps {
424 /// The List items.
425 ///
426 /// Defines the child elements (list items) that will be displayed inside the list. Defaults to an empty string.
427 #[prop_or_default]
428 pub children: Children,
429
430 /// Additional inline styles for the List.
431 ///
432 /// Allows for custom styling of the list container. Defaults to an empty string.
433 #[prop_or_default]
434 pub style: &'static str,
435
436 /// Additional inline styles for the List.
437 ///
438 /// Allows for custom CSS class styling for the list container. Defaults to an empty string.
439 #[prop_or_default]
440 pub class: &'static str,
441}
442
443/// List component.
444#[function_component]
445pub fn List(props: &ListProps) -> Html {
446 html! {
447 <ul class={props.class} style={props.style}>
448 { for props.children.iter() }
449 </ul>
450 }
451}