1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#![doc(
    html_logo_url = "https://github.com/next-rs/yew-accordion/assets/62179149/48fa7fe4-90a1-4314-801b-49389cebf33e",
    html_favicon_url = "https://github.com/next-rs/yew-accordion/assets/62179149/05482d9e-3bb3-49c2-9d3e-26aa9c12d936"
)]

//! # Yew Accordion - Documentation
//!
//! Welcome to the official Yew Accordion documentation. This library
//! provides a customizable accordion component for your Yew applications.
//!
//! ## Usage
//!
//! To use the Yew Accordion library, add the following dependency to your `Cargo.toml` file:
//!
//! ```sh
//! cargo add yew-accordion
//! ```
//!
//! To integrate the library into your Yew application, you can use the `Accordion`, `AccordionItem`,
//! and `AccordionButton` components. Here's a simple example of how to use them:
//!
//! ```rust
//! use yew::prelude::*;
//! use yew_accordion::{Accordion, AccordionItem, AccordionButton};
//!
//! // Your Yew component structure here...
//!
//! #[function_component]
//! pub fn MyAccordionComponent() -> Html {
//!     // Your component logic here...
//!
//!     html! {
//!         <Accordion
//!             expanded_element={html! {<AccordionButton class={"bg-blue-500 text-white p-2 rounded"}>{ "Hide -" }</AccordionButton>}}
//!             collapsed_element={html! {<AccordionButton class={"bg-green-500 text-white p-2 rounded"}>{ "Show +" }</AccordionButton>}}
//!             size="sm"
//!             aria_controls="example-accordion"
//!             container_class="my-custom-class bg-gray-800 p-4 rounded border border-gray-700"
//!             expanded_element_class="my-expanded-class bg-gradient-to-r from-blue-700 to-blue-500 text-white p-2 rounded"
//!             collapsed_element_class="my-collapsed-class bg-gradient-to-r from-green-700 to-green-500 text-white p-2 rounded"
//!             content_container_class="my-content-class bg-gray-900 p-4 rounded border-t border-gray-700"
//!         >
//!             <ul>
//!                 <AccordionItem
//!                     item_class="my-list-item-class border-b p-2 hover:bg-gray-700 transition duration-300 ease-in-out"
//!                 >{ "Item 1" }</AccordionItem>
//!                 <AccordionItem
//!                     item_class="my-list-item-class border-b p-2 hover:bg-gray-700 transition duration-300 ease-in-out"
//!                 >{ "Item 2" }</AccordionItem>
//!                 <AccordionItem
//!                     item_class="my-list-item-class p-2 hover:bg-gray-700 transition duration-300 ease-in-out"
//!                 >{ "Item 3" }</AccordionItem>
//!             </ul>
//!         </Accordion>
//!     }
//! }
//! ```
//!
//! For more detailed information, check the [examples] provided in the library.
//!
//! [examples]: https://github.com/next-rs/yew-accordion/tree/main/examples
//!
//! ## Configuration
//!
//! Yew Accordion allows you to customize various aspects of the accordion component through the
//! `AccordionProps`, `AccordionItemProps`, and `AccordionButtonProps` structures. You can adjust
//! properties such as size, ARIA controls, and custom classes. Refer to the respective documentation
//! for detailed configuration options.
//!
//! ## Contribution
//!
//! If you encounter any issues or have suggestions for improvements, feel free to contribute
//! to the [GitHub repository](https://github.com/next-rs/yew-accordion). We appreciate your feedback
//! and involvement in making Yew Accordion better!
//!
//! ## Acknowledgments
//!
//! Special thanks to the Yew community and contributors for such an amazing framework.
//!

use yew::prelude::*;

/// Properties for the Accordion component.
#[derive(Properties, Clone, PartialEq)]
pub struct AccordionProps {
    #[prop_or_default]
    /// The content to be displayed when the accordion is expanded.
    pub expanded_element: Html,
    
    #[prop_or_default]
    /// The content to be displayed when the accordion is collapsed.
    pub collapsed_element: Html,
    
    #[prop_or_default]
    /// The child elements within the accordion.
    pub children: Html,
    
    #[prop_or_default]
    /// Size of the accordion. Possible values: "sm", "md", "lg".
    pub size: &'static str,
    
    #[prop_or_default]
    /// ARIA controls attribute for accessibility.
    pub aria_controls: &'static str,
    
    #[prop_or_default]
    /// Class for the container element.
    pub container_class: &'static str,
    
    #[prop_or_default]
    /// Class for the expanded element.
    pub expanded_element_class: &'static str,
    
    #[prop_or_default]
    /// Class for the collapsed element.
    pub collapsed_element_class: &'static str,
    
    #[prop_or_default]
    /// Class for the content container.
    pub content_container_class: &'static str,
}

/// Accordion component.
#[function_component]
pub fn Accordion(props: &AccordionProps) -> Html {
    // State to track whether the accordion is expanded or collapsed.
    let is_expanded = use_state(|| false);
    let props = props.clone();
    let is_expanded_value = *is_expanded;

    // Callback function to handle accordion click events.
    let onclick = move |e: MouseEvent| {
        e.prevent_default();
        is_expanded.set(!is_expanded_value);
    };

    html! {
        <div class={get_accordion_container_style(&props.size, &props.container_class)}>
            <div
                aria-expanded={is_expanded_value.to_string()}
                aria-controls={props.aria_controls}
                onclick={onclick}
                class={get_toggle_element_style(&is_expanded_value, &props.expanded_element_class, &props.collapsed_element_class)}
            >
                { if is_expanded_value {props.expanded_element.clone()} else {props.collapsed_element.clone()} }
            </div>
            { if is_expanded_value {
                html! { <div id={props.aria_controls} class={props.content_container_class}>{props.children.clone()}</div> }
            } else {
                html! {}
            } }
        </div>
    }
}

/// Get the CSS class for the accordion container based on size and custom class.
fn get_accordion_container_style(size: &str, custom_class: &str) -> String {
    let base_class = match size {
        "sm" => "w-28",
        "md" => "w-40",
        "lg" => "w-80",
        _ => "",
    };
    format!("{} {}", base_class, custom_class)
}

/// Get the CSS class for the toggle element based on its expanded state.
fn get_toggle_element_style(
    is_expanded: &bool,
    expanded_class: &'static str,
    collapsed_class: &'static str,
) -> &'static str {
    if *is_expanded {
        expanded_class
    } else {
        collapsed_class
    }
}

/// Properties for the AccordionItem component.
#[derive(Clone, PartialEq, Properties)]
pub struct AccordionItemProps {
    #[prop_or_default]
    /// The content of the AccordionItem.
    pub children: Html,
    
    #[prop_or_default]
    /// Additional class for the AccordionItem.
    pub item_class: &'static str,
}

/// AccordionItem component.
#[function_component]
pub fn AccordionItem(props: &AccordionItemProps) -> Html {
    html! { <li class={props.item_class}>{ props.children.clone() }</li> }
}

/// Properties for the AccordionButton component.
#[derive(Clone, PartialEq, Properties)]
pub struct AccordionButtonProps {
    #[prop_or_default]
    /// The content of the AccordionButton.
    pub children: Html,
    
    #[prop_or_default]
    /// Additional class for the AccordionButton.
    pub class: &'static str,
}

/// AccordionButton component.
#[function_component]
pub fn AccordionButton(props: &AccordionButtonProps) -> Html {
    html! { <button class={props.class}>{ props.children.clone() }</button> }
}