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
215
216
use crate::prelude::*;
use yew::Html;

/// Properties for Paper component
#[derive(Properties, PartialEq)]
pub struct CardProps {
    #[prop_or_default]
    pub width: u16,
    #[prop_or_default]
    pub elevation: u8,
    #[prop_or_default]
    pub children: Children,
    #[prop_or_default]
    pub class: String,
    #[prop_or_default]
    pub style: String,
    #[prop_or_default]
    pub avatar: Option<String>,
    #[prop_or_default]
    pub link: Option<String>,
    #[prop_or_default]
    pub link_title: String,
    #[prop_or_default]
    pub title: Option<String>,
    #[prop_or_default]
    pub header: Option<fn() -> Html>,
    #[prop_or_default]
    pub footer: Option<fn() -> Html>,
    #[prop_or(Theme::Title)]
    pub theme: Theme,
}

/// Card component
///
/// Display a group of content with a header, body, and optional footer content.
/// Header content generally consists of an Avatar (icon or image), a title, and a link or button for some context related action.
///
/// Note: avatar string is expected to start with "fa-" if using an icon, otherwise string is treated as an image source value.
///
/// Basic example
/// ```rust
/// use webui::*;
///
/// fn page() -> Html {
///     html! {
///         <Card title="Hello World" avatar="fa-solid fa-acorn">{"Your card body content here"}</Card>
///     }
/// }
/// ```
///
/// Apply theme to card header
/// ```rust
/// use webui::*;
///
/// fn page() -> Html {
///     html! {
///         <Card title="Hello World" theme={Theme::Primary}>{"Your card body content here"}</Card>
///     }
/// }
/// ```
///
/// Add classes - applied to outer Paper component
/// ```rust
/// use webui::*;
///
/// fn page() -> Html {
///     html! {
///         <Card class="d-flex flex-column">{"Your card body content here"}</Card>
///     }
/// }
/// ```
///
/// Apply elevetation
///
/// Elevation applies a box shadow to the Card component.
/// Valid ranges range from 0 ro 25.
/// ```rust
/// use webui::*;
///
/// fn page() -> Html {
///     html! {
///         <Card elevation={10}>{"Your card body content here"}</Card>
///     }
/// }
/// ```
#[function_component(Card)]
pub fn card(props: &CardProps) -> Html {
    let classes = &mut Classes::new();
    classes.push("card");

    if props.elevation > 0 {
        classes.push(format!("elevation-{}", props.elevation));
    }

    if !props.class.is_empty() {
        classes.push(&props.class);
    }

    let header_classes = &mut Classes::new();
    header_classes.push("card-header d-flex flex-row flex-gap align-center");
    header_classes.push(props.theme.to_string());

    let mut styles: Vec<String> = Vec::new();
    if props.width > 0 {
        styles.push(format!(
            "max-width:{}px;min-width:{}px;",
            props.width,
            props.width / 2
        ));
    }
    if !props.style.is_empty() {
        styles.push(props.style.to_string());
    }

    html! {
        <Paper class={classes.to_string()} style={styles.join(" ")}>
            <Paper class={header_classes.to_string()}>
                {match props.avatar.to_owned() {
                    Some(avatar) => {
                        if avatar.is_empty() {
                            html!()
                        } else if avatar.starts_with("fa-") {
                            html! {
                                <Avatar class="f3 ml-1 pa-1" icon={avatar} />
                            }
                        } else {
                            html! {
                                <Avatar class="ml-1 pa-1" image={avatar} />
                            }
                        }
                    },
                    None => html! {}
                }}
                <Paper class="card-title d-flex flex-column flex-grow flex-gap">
                    {match props.title.to_owned() {
                        Some(title) => {
                            html! {
                                <h2 class="f3 pa-1 d-flex flex-wrap flex-row elevation-0">{title}</h2>
                            }
                        },
                        None => html! {}
                    }}
                    {match props.header.to_owned() {
                        Some(header) => {
                            html! {
                                {header()}
                            }
                        },
                        None => html! {}
                    }}
                </Paper>
                {match props.link.to_owned() {
                    Some(link) => {
                        if !link.is_empty() {
                            html!(
                                <Link href={link} class="f3 pr-3" title={props.link_title.to_owned()}>
                                    <i class="fa-solid fa-external-link" />
                                </Link>
                            )
                        } else {
                            html!()
                        }
                    },
                    None => html!{}
                }}
            </Paper>
            <Paper class="card-body d-flex flex-column flex-gap pa-1">
                { for props.children.iter() }
            </Paper>
            {match props.footer.to_owned() {
                Some(footer) => {
                    html! {
                        <Paper class="card-footer">
                            {footer()}
                        </Paper>
                    }
                },
                None => html! {}
            }}
        </Paper>
    }
}

/// Properties for Paper component
#[derive(Properties, PartialEq)]
pub struct CardsProps {
    #[prop_or_default]
    pub elevation: u8,
    #[prop_or_default]
    pub children: Children,
    #[prop_or_default]
    pub class: String,
    #[prop_or_default]
    pub style: String,
}

#[function_component(Cards)]
pub fn cards(props: &CardsProps) -> Html {
    let classes = &mut Classes::new();
    classes.push("cards page-segment-cards");

    if props.elevation > 0 {
        classes.push(format!("elevation-{}", props.elevation));
    }

    if !props.class.is_empty() {
        classes.push(&props.class);
    }
    let style = props.style.to_owned();

    html! {
        <Paper class={classes.to_string()} {style}>
            { for props.children.iter() }
        </Paper>
    }
}