yew_accordion/
lib.rs

1#![doc(
2    html_logo_url = "https://github.com/next-rs/yew-accordion/assets/62179149/48fa7fe4-90a1-4314-801b-49389cebf33e",
3    html_favicon_url = "https://github.com/next-rs/yew-accordion/assets/62179149/05482d9e-3bb3-49c2-9d3e-26aa9c12d936"
4)]
5
6//! # Yew Accordion - Documentation
7//!
8//! Welcome to the official Yew Accordion documentation. This library
9//! provides a customizable accordion component for your Yew applications.
10//!
11//! ## Usage
12//!
13//! To use the Yew Accordion library, add the following dependency to your `Cargo.toml` file:
14//!
15//! ```sh
16//! cargo add yew-accordion
17//! ```
18//!
19//! To integrate the library into your Yew application, you can use the `Accordion`, `AccordionItem`,
20//! and `AccordionButton` components. Here's a simple example of how to use them:
21//!
22//! ```rust
23//! use yew::prelude::*;
24//! use yew_accordion::{Accordion, AccordionItem, AccordionButton};
25//!
26//! // Your Yew component structure here...
27//!
28//! #[function_component]
29//! pub fn MyAccordionComponent() -> Html {
30//!     // Your component logic here...
31//!
32//!     html! {
33//!         <Accordion
34//!             expanded_element={html! {<AccordionButton class={"bg-blue-500 text-white p-2 rounded"}>{ "Hide -" }</AccordionButton>}}
35//!             collapsed_element={html! {<AccordionButton class={"bg-green-500 text-white p-2 rounded"}>{ "Show +" }</AccordionButton>}}
36//!             size="sm"
37//!             aria_controls="example-accordion"
38//!             container_class="my-custom-class bg-gray-800 p-4 rounded border border-gray-700"
39//!             expanded_element_class="my-expanded-class bg-gradient-to-r from-blue-700 to-blue-500 text-white p-2 rounded"
40//!             collapsed_element_class="my-collapsed-class bg-gradient-to-r from-green-700 to-green-500 text-white p-2 rounded"
41//!             content_container_class="my-content-class bg-gray-900 p-4 rounded border-t border-gray-700"
42//!         >
43//!             <ul>
44//!                 <AccordionItem
45//!                     item_class="my-list-item-class border-b p-2 hover:bg-gray-700 transition duration-300 ease-in-out"
46//!                 >{ "Item 1" }</AccordionItem>
47//!                 <AccordionItem
48//!                     item_class="my-list-item-class border-b p-2 hover:bg-gray-700 transition duration-300 ease-in-out"
49//!                 >{ "Item 2" }</AccordionItem>
50//!                 <AccordionItem
51//!                     item_class="my-list-item-class p-2 hover:bg-gray-700 transition duration-300 ease-in-out"
52//!                 >{ "Item 3" }</AccordionItem>
53//!             </ul>
54//!         </Accordion>
55//!     }
56//! }
57//! ```
58//!
59//! For more detailed information, check the [examples] provided in the library.
60//!
61//! [examples]: https://github.com/next-rs/yew-accordion/tree/main/examples
62//!
63//! ## Configuration
64//!
65//! Yew Accordion allows you to customize various aspects of the accordion component through the
66//! `AccordionProps`, `AccordionItemProps`, and `AccordionButtonProps` structures. You can adjust
67//! properties such as size, ARIA controls, and custom classes. Refer to the respective documentation
68//! for detailed configuration options.
69//!
70//! ## Contribution
71//!
72//! If you encounter any issues or have suggestions for improvements, feel free to contribute
73//! to the [GitHub repository](https://github.com/next-rs/yew-accordion). We appreciate your feedback
74//! and involvement in making Yew Accordion better!
75//!
76//! ## Acknowledgments
77//!
78//! Special thanks to the Yew community and contributors for such an amazing framework.
79//!
80
81use yew::prelude::*;
82
83/// Properties for the Accordion component.
84#[derive(Properties, Clone, PartialEq)]
85pub struct AccordionProps {
86    #[prop_or_default]
87    /// The content to be displayed when the accordion is expanded.
88    pub expanded_element: Html,
89    
90    #[prop_or_default]
91    /// The content to be displayed when the accordion is collapsed.
92    pub collapsed_element: Html,
93    
94    #[prop_or_default]
95    /// The child elements within the accordion.
96    pub children: Html,
97    
98    #[prop_or_default]
99    /// Size of the accordion. Possible values: "sm", "md", "lg".
100    pub size: &'static str,
101    
102    #[prop_or_default]
103    /// ARIA controls attribute for accessibility.
104    pub aria_controls: &'static str,
105    
106    #[prop_or_default]
107    /// Class for the container element.
108    pub container_class: &'static str,
109    
110    #[prop_or_default]
111    /// Class for the expanded element.
112    pub expanded_element_class: &'static str,
113    
114    #[prop_or_default]
115    /// Class for the collapsed element.
116    pub collapsed_element_class: &'static str,
117    
118    #[prop_or_default]
119    /// Class for the content container.
120    pub content_container_class: &'static str,
121}
122
123/// Accordion component.
124#[function_component]
125pub fn Accordion(props: &AccordionProps) -> Html {
126    // State to track whether the accordion is expanded or collapsed.
127    let is_expanded = use_state(|| false);
128    let props = props.clone();
129    let is_expanded_value = *is_expanded;
130
131    // Callback function to handle accordion click events.
132    let onclick = move |e: MouseEvent| {
133        e.prevent_default();
134        is_expanded.set(!is_expanded_value);
135    };
136
137    html! {
138        <div class={get_accordion_container_style(&props.size, &props.container_class)}>
139            <div
140                aria-expanded={is_expanded_value.to_string()}
141                aria-controls={props.aria_controls}
142                onclick={onclick}
143                class={get_toggle_element_style(&is_expanded_value, &props.expanded_element_class, &props.collapsed_element_class)}
144            >
145                { if is_expanded_value {props.expanded_element.clone()} else {props.collapsed_element.clone()} }
146            </div>
147            { if is_expanded_value {
148                html! { <div id={props.aria_controls} class={props.content_container_class}>{props.children.clone()}</div> }
149            } else {
150                html! {}
151            } }
152        </div>
153    }
154}
155
156/// Get the CSS class for the accordion container based on size and custom class.
157fn get_accordion_container_style(size: &str, custom_class: &str) -> String {
158    let base_class = match size {
159        "sm" => "w-28",
160        "md" => "w-40",
161        "lg" => "w-80",
162        _ => "",
163    };
164    format!("{} {}", base_class, custom_class)
165}
166
167/// Get the CSS class for the toggle element based on its expanded state.
168fn get_toggle_element_style(
169    is_expanded: &bool,
170    expanded_class: &'static str,
171    collapsed_class: &'static str,
172) -> &'static str {
173    if *is_expanded {
174        expanded_class
175    } else {
176        collapsed_class
177    }
178}
179
180/// Properties for the AccordionItem component.
181#[derive(Clone, PartialEq, Properties)]
182pub struct AccordionItemProps {
183    #[prop_or_default]
184    /// The content of the AccordionItem.
185    pub children: Html,
186    
187    #[prop_or_default]
188    /// Additional class for the AccordionItem.
189    pub item_class: &'static str,
190}
191
192/// AccordionItem component.
193#[function_component]
194pub fn AccordionItem(props: &AccordionItemProps) -> Html {
195    html! { <li class={props.item_class}>{ props.children.clone() }</li> }
196}
197
198/// Properties for the AccordionButton component.
199#[derive(Clone, PartialEq, Properties)]
200pub struct AccordionButtonProps {
201    #[prop_or_default]
202    /// The content of the AccordionButton.
203    pub children: Html,
204    
205    #[prop_or_default]
206    /// Additional class for the AccordionButton.
207    pub class: &'static str,
208}
209
210/// AccordionButton component.
211#[function_component]
212pub fn AccordionButton(props: &AccordionButtonProps) -> Html {
213    html! { <button class={props.class}>{ props.children.clone() }</button> }
214}