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}