bootstrap-rs/src/props.rs

162 lines
4.2 KiB
Rust

use crate::prelude::*;
use std::collections::HashMap;
#[cfg(feature = "validate")]
use validator::ValidationErrors;
use yew::{prelude::*, virtual_dom::VNode};
pub(crate) fn collect_props<'a, T>(t: &'a Option<T>, ts: &'a [T]) -> Vec<&'a T> {
if let Some(t) = t.as_ref() {
let mut r = vec![t];
r.append(&mut ts.iter().collect());
r
} else {
ts.iter().collect()
}
}
#[derive(Properties, Clone, PartialEq, Default)]
pub struct Props {
// html specific
#[prop_or_default]
pub id: Option<String>,
#[prop_or_default]
pub aria_label: Option<String>,
#[prop_or_default]
pub role: Option<String>,
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
pub style: Option<String>,
#[prop_or_default]
pub children: Children,
// optional validation support
#[cfg(feature = "validate")]
#[prop_or_default]
pub error_code: Option<String>,
#[cfg(feature = "validate")]
#[prop_or_default]
pub errors: Option<ValidationErrors>,
// bootstrap specific
#[prop_or_default]
pub border: Option<Border>,
#[prop_or_default]
pub borders: Vec<Border>,
#[prop_or_default]
pub margin: Option<Margin>,
#[prop_or_default]
pub margins: Vec<Margin>,
#[prop_or_default]
pub padding: Option<Padding>,
#[prop_or_default]
pub paddings: Vec<Padding>,
}
impl<'a> From<&'a Props> for BootstrapProps<'a> {
fn from(props: &Props) -> BootstrapProps {
let class = &props.class;
let borders = collect_props(&props.border, &props.borders);
let margins = collect_props(&props.margin, &props.margins);
let paddings = collect_props(&props.padding, &props.paddings);
let mut attributes = HashMap::new();
add_opt_attr(&mut attributes, "id", &props.id);
add_opt_attr(&mut attributes, "aria-label", &props.aria_label);
add_opt_attr(&mut attributes, "role", &props.role);
add_opt_attr(&mut attributes, "style", &props.style);
BootstrapProps {
class,
borders,
margins,
paddings,
attributes,
}
}
}
pub struct BootstrapProps<'a> {
pub class: &'a Classes,
pub borders: Vec<&'a Border>,
pub margins: Vec<&'a Margin>,
pub paddings: Vec<&'a Padding>,
pub attributes: HashMap<&'static str, &'a String>,
}
impl<'a> BootstrapProps<'a> {
pub fn add_attributes(&self, html: &mut VNode) {
if let VNode::VTag(tag) = html {
for key in self.attributes.keys() {
if let Some(value) = &self.attributes.get(key) {
let value = **value;
tag.add_attribute(key, value.clone());
}
}
}
}
pub fn calculate_classes<C: Into<Classes>>(&self, prefix: C) -> Classes {
let BootstrapProps {
class,
borders,
margins,
paddings,
..
} = self;
let mut classes: Classes = prefix.into();
classes.push((*class).to_owned());
classes.extend(borders);
classes.extend(margins);
classes.extend(paddings);
classes
}
}
pub(crate) fn add_opt_attr<'a>(
attrs: &mut HashMap<&str, &'a String>,
name: &'static str,
opt: &'a Option<String>,
) {
if let Some(val) = opt.as_ref() {
attrs.insert(name, val);
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_aria_label_override() {
let props = Props {
aria_label: Some("override".into()),
..Props::default()
};
let expected = html! {
<div class="" aria-label="override" />
};
crate::test::assert_attrs_eq(
expected,
crate::render::render_with_prefix(&props, "", html! { <div aria-label="test" /> }),
);
}
#[test]
fn test_opt_attr() {
let props = Props {
aria_label: Some("some-label".into()),
..Props::default()
};
let expected = html! {
<div class="" aria-label="some-label" />
};
crate::test::assert_attrs_eq(
expected,
crate::render::render_with_prefix(&props, "", html! { <div /> }),
);
}
}