Browse Source

Re-factor props and attrs handling.

main
Thomas Gideon 2 years ago
parent
commit
6236cf6ae9
  1. 2
      Cargo.toml
  2. 17
      src/alert/convert.rs
  3. 48
      src/alert/mod.rs
  4. 55
      src/alert/props.rs
  5. 98
      src/breadcrumb/item.rs
  6. 18
      src/breadcrumb/mod.rs
  7. 22
      src/button/mod.rs
  8. 30
      src/card/body.rs
  9. 50
      src/card/header.rs
  10. 9
      src/card/mod.rs
  11. 38
      src/card/text.rs
  12. 8
      src/container.rs
  13. 30
      src/form/mod.rs
  14. 13
      src/input/group.rs
  15. 90
      src/input/mod.rs
  16. 73
      src/input/props.rs
  17. 70
      src/input/textarea.rs
  18. 10
      src/jumbotron.rs
  19. 2
      src/lib.rs
  20. 4
      src/prelude/border.rs
  21. 10
      src/prelude/color.rs
  22. 54
      src/prelude/edge.rs
  23. 4
      src/prelude/margin.rs
  24. 144
      src/prelude/mod.rs
  25. 4
      src/prelude/padding.rs
  26. 142
      src/props.rs
  27. 51
      src/render.rs

2
Cargo.toml

@ -1,6 +1,6 @@
[package]
name = "bootstrap-rs"
version = "0.2.6"
version = "0.2.7"
authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
edition = "2018"

17
src/alert/convert.rs

@ -1,17 +0,0 @@
use super::Props;
use crate::prelude::*;
impl<'a> From<&'a Props> for BootstrapProps<'a> {
fn from(props: &Props) -> BootstrapProps {
let class = &props.class;
let borders = collect_bs(&props.border, &props.borders);
let margins = collect_bs(&props.margin, &props.margins);
let paddings = collect_bs(&props.padding, &props.paddings);
BootstrapProps {
class,
borders,
margins,
paddings,
}
}
}

48
src/alert/mod.rs

@ -1,41 +1,14 @@
mod convert;
mod props;
use crate::prelude::*;
use yew::{html::Children, prelude::*};
use self::props::Props;
use crate::{prelude::*, render};
use yew::prelude::*;
pub struct Alert {
link: ComponentLink<Self>,
props: Props,
}
#[derive(Properties, Default, Clone, PartialEq)]
pub struct Props {
pub on_close: Callback<()>,
pub color: Color,
#[prop_or_default]
pub aria_label: String,
#[prop_or_default]
pub role: String,
#[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>,
#[prop_or_default]
pub class: String,
#[prop_or_default]
pub style: String,
#[prop_or_default]
pub children: Children,
}
impl Component for Alert {
type Properties = Props;
type Message = ();
@ -54,10 +27,8 @@ impl Component for Alert {
}
fn view(&self) -> Html {
let color_class = self.props.color.with_prefix("alert");
let class = calculate_classes(format!("alert {}", color_class), (&self.props).into());
html! {
<div class=class>
let html = html! {
<div>
{ self.props.children.render() }
<button
type="button"
@ -69,6 +40,11 @@ impl Component for Alert {
<span aria-hidden="true">{ "×" }</span>
</button>
</div>
}
};
render::render_with_prefix(
&self.props,
vec!["alert", &self.props.color.with_prefix("alert")],
html,
)
}
}

55
src/alert/props.rs

@ -0,0 +1,55 @@
use crate::{prelude::*, props::*};
use std::collections::HashMap;
use yew::prelude::*;
#[derive(Properties, Default, Clone, PartialEq)]
pub struct Props {
// component specific
pub on_close: Callback<()>,
pub color: Color,
// html specific
#[prop_or_default]
pub id: Option<String>,
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
pub style: String,
#[prop_or_default]
pub aria_label: Option<String>,
#[prop_or_default]
pub role: Option<String>,
#[prop_or_default]
pub children: Children,
// bootstrap
#[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 attributes = HashMap::new();
BootstrapProps {
class,
borders,
margins,
paddings,
attributes,
}
}
}

98
src/breadcrumb/item.rs

@ -1,13 +1,17 @@
use crate::prelude::*;
use crate::{prelude::*, props::*, render};
use std::collections::HashMap;
use yew::{html::Children, prelude::*};
pub struct BreadcrumbItem {
props: Props,
}
#[derive(Properties, Clone, PartialEq)]
#[derive(Properties, Clone, PartialEq, Default)]
pub struct Props {
// component specific
pub active: bool,
// bootstrap specific
#[prop_or_default]
pub border: Option<Border>,
#[prop_or_default]
@ -20,10 +24,14 @@ pub struct Props {
pub padding: Option<Padding>,
#[prop_or_default]
pub paddings: Vec<Padding>,
// html specific
#[prop_or_default]
pub id: Option<String>,
#[prop_or_default]
pub class: String,
pub class: Classes,
#[prop_or_default]
pub style: String,
pub style: Option<String>,
#[prop_or_default]
pub children: Children,
}
@ -45,43 +53,87 @@ impl Component for BreadcrumbItem {
}
fn view(&self) -> Html {
if self.props.active {
html! {
<li class=self.classes() aria-current="page">
{ self.props.children.render() }
</li>
}
let (prefix, html) = if self.props.active {
(
vec!["breadcrumb-item", "active"],
html! {
<li class=self.classes() aria-current="page">
{ self.props.children.render() }
</li>
},
)
} else {
html! {
<li class=self.classes()>
{ self.props.children.render() }
</li>
}
}
(
vec!["breadcrumb-item"],
html! {
<li class=self.classes()>
{ self.props.children.render() }
</li>
},
)
};
render::render_with_prefix(&self.props, prefix, html)
}
}
impl BreadcrumbItem {
fn classes(&self) -> String {
fn classes(&self) -> Classes {
let props: BootstrapProps<'_> = (&self.props).into();
let mut classes = props.calculate_classes("breadcrumb-item");
if self.props.active {
calculate_classes("breadcrumb-item active", (&self.props).into())
} else {
calculate_classes("breadcrumb-item", (&self.props).into())
classes.push("active")
}
classes
}
}
impl<'a> From<&'a Props> for BootstrapProps<'a> {
fn from(props: &Props) -> BootstrapProps {
let class = &props.class;
let borders = collect_bs(&props.border, &props.borders);
let margins = collect_bs(&props.margin, &props.margins);
let paddings = collect_bs(&props.padding, &props.paddings);
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();
if let Some(ref id) = props.id {
attributes.insert("id", id);
}
if let Some(ref style) = props.style {
attributes.insert("style", style);
}
BootstrapProps {
class,
borders,
margins,
paddings,
attributes,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_active_prop() {
let item = BreadcrumbItem {
props: Props {
active: true,
id: Some("test".into()),
margin: Some(Margin(Edge::All, 3)),
padding: Some(Padding(Edge::Top, 3)),
..Props::default()
},
};
let expected = html! {
<li
class="breadcrumb-item active m-3 pt-3"
aria-current="page"
id="test"
>
<></>
</li>
};
assert_eq!(expected, item.view());
}
}

18
src/breadcrumb/mod.rs

@ -1,6 +1,6 @@
mod item;
use crate::prelude::*;
use crate::{prelude::*, props::*, render};
pub use item::BreadcrumbItem;
use yew::prelude::*;
@ -25,19 +25,13 @@ impl Component for Breadcrumb {
}
fn view(&self) -> Html {
let class = calculate_classes("breadcrumb", (&self.props).into());
html! {
<nav
class=class
aria-label="breadcrumb"
style=self.props.style.clone()
>
<ol
class="breadcrumb"
>
let html = html! {
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
{ self.props.children.render() }
</ol>
</nav>
}
};
render::render_with_prefix(&self.props, "breadcrumb", html)
}
}

22
src/button/mod.rs

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{prelude::render_on_change, props::Props, render};
use yew::prelude::*;
pub struct ButtonGroup {
@ -22,24 +22,6 @@ impl Component for ButtonGroup {
}
fn view(&self) -> Html {
html! {
<div class=self.class()
style=self.props.style.clone()
role=self.props.role.clone()
aria-label=self.props.aria_label.clone()
>
{ self.props.children.render() }
</div>
}
}
}
impl ButtonGroup {
fn class(&self) -> String {
if self.props.class.is_empty() {
"btn-group".into()
} else {
format!("btn-group {}", self.props.class)
}
render::render_with_prefix(&self.props, "btn-group", render::div(&self.props.children))
}
}

30
src/card/body.rs

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{prelude::*, props::*, render};
use yew::prelude::*;
pub struct CardBody {
@ -22,11 +22,29 @@ impl Component for CardBody {
}
fn view(&self) -> Html {
let class = calculate_classes("card-body", (&self.props).into());
html! {
<div class=class style=self.props.style.clone()>
{ self.props.children.render() }
render::render_with_prefix(&self.props, "card-body", render::div(&self.props.children))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_view() {
let comp = CardBody {
props: Props {
style: Some("display: none;".into()),
..Props::default()
},
};
let expected = html! {
<div class="card-body" style="display: none;">
<></>
</div>
}
};
assert_eq!(expected, comp.view());
}
}

50
src/card/header.rs

@ -1,20 +1,10 @@
use crate::prelude::*;
use yew::{html::Children, prelude::*};
use crate::{prelude::*, props::*, render};
use yew::prelude::*;
pub struct CardHeader {
props: Props,
}
#[derive(Properties, Clone, PartialEq)]
pub struct Props {
#[prop_or_default]
pub class: String,
#[prop_or_default]
pub style: String,
#[prop_or_default]
pub children: Children,
}
impl Component for CardHeader {
type Properties = Props;
type Message = ();
@ -32,28 +22,26 @@ impl Component for CardHeader {
}
fn view(&self) -> Html {
html! {
<p class=self.class() style=self.style()>
{ self.props.children.render() }
</p>
}
render::render_with_prefix(&self.props, "card-header", render::p(&self.props.children))
}
}
impl CardHeader {
fn class(&self) -> String {
if self.props.class.is_empty() {
"card-header".into()
} else {
format!("card-header {}", self.props.class)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_comp() {
let comp = CardHeader {
props: Props::default(),
};
let expected = html! {
<p class="card-header">
<></>
</p>
};
fn style(&self) -> &str {
if self.props.style.is_empty() {
""
} else {
&self.props.style
}
assert_eq!(expected, comp.view());
}
}

9
src/card/mod.rs

@ -3,7 +3,7 @@ mod header;
mod text;
pub use self::{body::CardBody, header::CardHeader, text::CardText};
use crate::prelude::*;
use crate::{prelude::*, props::*, render};
use yew::prelude::*;
pub struct Card {
@ -27,11 +27,6 @@ impl Component for Card {
}
fn view(&self) -> Html {
let class = calculate_classes("card", (&self.props).into());
html! {
<div class=class style=self.props.style.clone()>
{ self.props.children.render() }
</div>
}
render::render_with_prefix(&self.props, "card", render::div(&self.props.children))
}
}

38
src/card/text.rs

@ -1,20 +1,10 @@
use crate::prelude::*;
use yew::{html::Children, prelude::*};
use crate::{prelude::*, props::*, render};
use yew::prelude::*;
pub struct CardText {
props: Props,
}
#[derive(Properties, Clone, PartialEq)]
pub struct Props {
#[prop_or_default]
pub class: String,
#[prop_or_default]
pub style: String,
#[prop_or_default]
pub children: Children,
}
impl Component for CardText {
type Properties = Props;
type Message = ();
@ -32,28 +22,6 @@ impl Component for CardText {
}
fn view(&self) -> Html {
html! {
<p class=self.class() style=self.style()>
{ self.props.children.render() }
</p>
}
}
}
impl CardText {
fn class(&self) -> String {
if self.props.class.is_empty() {
"card-text".into()
} else {
format!("card-text {}", self.props.class)
}
}
fn style(&self) -> &str {
if self.props.style.is_empty() {
""
} else {
&self.props.style
}
render::render_with_prefix(&self.props, "card-text", render::p(&self.props.children))
}
}

8
src/container.rs

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{prelude::*, props::*, render};
use yew::prelude::*;
pub struct Container {
@ -22,11 +22,7 @@ impl Component for Container {
}
fn view(&self) -> Html {
html! {
<div class=calculate_classes("container", (&self.props).into())>
{ self.props.children.render() }
</div>
}
render::render_with_prefix(&self.props, "container", render::div(&self.props.children))
}
}

30
src/form/mod.rs

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{prelude::*, props::*, render};
use yew::prelude::*;
pub struct FormGroup {
@ -22,32 +22,6 @@ impl Component for FormGroup {
}
fn view(&self) -> Html {
html! {
<div class=calculate_classes("form-group", (&self.props).into())>
{ self.props.children.render() }
</div>
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let form_group = FormGroup {
props: Props {
margin: Some(Margin(Edge::All, 3)),
padding: Some(Padding(Edge::Top, 3)),
..Props::default()
},
};
let expected = html! {
<div class="form-group m-3 pt-3">
<></>
</div>
};
assert_eq!(expected, form_group.view());
render::render_with_prefix(&self.props, "form-group", render::div(&self.props.children))
}
}

13
src/input/group.rs

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{prelude::*, props::*, render};
use yew::prelude::*;
pub struct InputGroup {
@ -22,11 +22,10 @@ impl Component for InputGroup {
}
fn view(&self) -> Html {
let class = calculate_classes("input-group", (&self.props).into());
html! {
<div class=class>
{ self.props.children.render() }
</div>
}
render::render_with_prefix(
&self.props,
"input-group",
render::div(&self.props.children),
)
}
}

90
src/input/mod.rs

@ -1,19 +1,28 @@
mod group;
mod props;
mod textarea;
use self::props::Props;
pub use self::{group::InputGroup, textarea::TextArea};
use crate::prelude::*;
use crate::{prelude::*, render};
use std::fmt::{Display, Formatter, Result as FmtResult};
use yew::prelude::*;
#[derive(Clone, PartialEq)]
pub enum InputType {
Text,
Date,
Checkbox,
Color,
}
impl InputType {
fn as_str(&self) -> &str {
impl Display for InputType {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Self::Text => "text",
Self::Text => write!(f, "text"),
Self::Date => write!(f, "date"),
Self::Checkbox => write!(f, "checkbox"),
Self::Color => write!(f, "color"),
}
}
}
@ -27,38 +36,6 @@ pub struct Input {
#[derive(Debug)]
pub struct InputChange(ChangeData);
#[derive(Properties, Clone, PartialEq)]
pub struct Props {
#[prop_or_default]
pub name: String,
#[prop_or_default]
pub id: String,
pub on_change: Callback<String>,
pub input_type: InputType,
#[prop_or_default]
pub readonly: bool,
#[prop_or_default]
pub value: String,
#[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>,
#[prop_or_default]
pub class: String,
#[prop_or_default]
pub style: String,
#[prop_or_default]
pub valid: Option<bool>,
}
impl Component for Input {
type Message = InputChange;
type Properties = Props;
@ -86,40 +63,25 @@ impl Component for Input {
}
fn view(&self) -> Html {
let prefix = if self.props.readonly {
format!(
"form-control-plaintext{}",
valid_as_class(&self.props.valid)
)
let input_type = self
.props
.input_type
.as_ref()
.unwrap_or_else(|| &InputType::Text);
let mut prefix = if self.props.readonly {
vec!["form-control-plaintext"]
} else {
format!("form-control{}", valid_as_class(&self.props.valid))
vec!["form-control"]
};
let class = calculate_classes(prefix, (&self.props).into());
html! {
prefix.push(valid_as_class(&self.props.valid));
let html = html! {
<input
name=&self.props.name
id=&self.props.id
type=self.props.input_type.as_str()
class=class
type=input_type
value=&self.state
readonly=self.props.readonly
onchange=self.link.callback(|evt| InputChange(evt))
/>
}
}
}
impl<'a> From<&'a Props> for BootstrapProps<'a> {
fn from(props: &Props) -> BootstrapProps {
let class = &props.class;
let borders = collect_bs(&props.border, &props.borders);
let margins = collect_bs(&props.margin, &props.margins);
let paddings = collect_bs(&props.padding, &props.paddings);
BootstrapProps {
class,
borders,
margins,
paddings,
}
};
render::render_with_prefix(&self.props, prefix, html)
}
}

73
src/input/props.rs

@ -0,0 +1,73 @@
use super::InputType;
use crate::{
prelude::*,
props::{collect_props, BootstrapProps},
};
use std::collections::HashMap;
use yew::{html::Children, prelude::*};
#[derive(Properties, Clone, PartialEq)]
pub struct Props {
// component specific
#[prop_or_default]
pub name: Option<String>,
#[prop_or_default]
pub value: String,
#[prop_or_default]
pub valid: Option<bool>,
pub on_change: Callback<String>,
#[prop_or_default]
pub readonly: bool,
#[prop_or_default]
pub input_type: Option<InputType>,
// 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>,
// html specific
#[prop_or_default]
pub id: Option<String>,
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
pub style: Option<String>,
#[prop_or_default]
pub children: Children,
}
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();
if let Some(ref style) = props.style {
attributes.insert("style", style);
}
if let Some(ref id) = props.id {
attributes.insert("id", id);
}
if let Some(ref name) = props.name {
attributes.insert("name", name);
}
BootstrapProps {
class,
borders,
margins,
paddings,
attributes,
}
}
}

70
src/input/textarea.rs

@ -1,5 +1,6 @@
use crate::prelude::*;
use yew::prelude::*;
use super::props::Props;
use crate::{prelude::*, render};
use yew::{prelude::*, virtual_dom::VNode};
pub struct TextArea {
link: ComponentLink<Self>,
@ -10,41 +11,21 @@ pub struct TextArea {
#[derive(Debug)]
pub struct InputChange(ChangeData);
#[derive(Properties, Clone, PartialEq)]
pub struct Props {
#[prop_or_default]
pub name: String,
#[prop_or_default]
pub id: String,
pub on_change: Callback<String>,
#[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>,
#[prop_or_default]
pub class: String,
#[prop_or_default]
pub style: String,
#[prop_or_default]
pub value: String,
#[prop_or_default]
pub valid: Option<bool>,
}
impl Component for TextArea {
type Message = InputChange;
type Properties = Props;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let state = props.value.clone();
let state = if props.children.is_empty() {
props.value.clone()
} else {
let node = props.children.render();
if let VNode::VText(text) = node {
text.text
} else {
props.value.clone()
}
};
Self { props, state, link }
}
@ -67,31 +48,14 @@ impl Component for TextArea {
}
fn view(&self) -> Html {
let prefix = format!("form-control{}", valid_as_class(&self.props.valid));
html! {
let prefix = vec!["form-control", &valid_as_class(&self.props.valid)];
let html = html! {
<textarea
name=&self.props.name
id=&self.props.id
class=calculate_classes(prefix, (&self.props).into())
onchange=self.link.callback(|evt| InputChange(evt))
>
{ &self.state }
</textarea>
}
}
}
impl<'a> From<&'a Props> for BootstrapProps<'a> {
fn from(props: &Props) -> BootstrapProps {
let class = &props.class;
let borders = collect_bs(&props.border, &props.borders);
let margins = collect_bs(&props.margin, &props.margins);
let paddings = collect_bs(&props.padding, &props.paddings);
BootstrapProps {
class,
borders,
margins,
paddings,
}
};
render::render_with_prefix(&self.props, prefix, html)
}
}

10
src/jumbotron.rs

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{props::Props, render};
use yew::prelude::*;
pub struct Jumbotron {
@ -22,11 +22,11 @@ impl Component for Jumbotron {
}
fn view(&self) -> Html {
let class = calculate_classes("jumbotron", (&self.props).into());
html! {
<div class=class>
let html = html! {
<div>
{ self.props.children.render() }
</div>
}
};
render::render_with_prefix(&self.props, "jumbotron", html)
}
}

2
src/lib.rs

@ -7,6 +7,8 @@ mod form;
pub mod input;
mod jumbotron;
pub mod prelude;
pub(crate) mod props;
mod render;
pub use self::{
alert::Alert,

4
src/prelude/border.rs

@ -1,8 +1,10 @@
use super::{Color, Edge};
use crate::props::IntoBsClass;
#[derive(Clone, PartialEq)]
pub struct Border(pub Edge, pub Color);
impl super::BootstrapClass for Border {
impl IntoBsClass for Border {
fn as_classname(&self) -> String {
let edge = match self.0 {
Edge::All => "border".to_owned(),

10
src/prelude/color.rs

@ -34,3 +34,13 @@ impl Color {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_prefix() {
assert_eq!(Color::Primary.with_prefix("alert"), "alert-primary");
}
}

54
src/prelude/edge.rs

@ -0,0 +1,54 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug, Clone, PartialEq)]
pub enum Edge {
All,
Top,
Right,
Bottom,
Left,
LeftAndRight,
TopAndBottom,
}
impl Display for Edge {
fn fmt(&self, fmt: &mut Formatter) -> Result {
match self {
Self::All => write!(fmt, ""),
Self::Top => write!(fmt, "t"),
Self::Bottom => write!(fmt, "b"),
Self::Right => write!(fmt, "r"),
Self::Left => write!(fmt, "l"),
Self::LeftAndRight => write!(fmt, "x"),
Self::TopAndBottom => write!(fmt, "y"),
}
}
}
impl Edge {
fn suffix(&self) -> &str {
match self {
Self::Top => "-top",
_ => "",
}
}
pub(crate) fn with_prefix<S: AsRef<str>>(&self, prefix: S) -> String {
match self {
Self::All => prefix.as_ref().to_owned(),
Self::LeftAndRight => format!(
"{0}{1} {0}{2}",
prefix.as_ref(),
Self::Left.suffix(),
Self::Right.suffix()
),
Self::TopAndBottom => format!(
"{0}{1} {0}{2}",
prefix.as_ref(),
Self::Top.suffix(),
Self::Bottom.suffix()
),
_ => format!("{}{}", prefix.as_ref(), self.suffix()),
}
}
}

4
src/prelude/margin.rs

@ -1,7 +1,9 @@
use crate::props::IntoBsClass;
#[derive(Debug, Clone, PartialEq)]
pub struct Margin(pub super::Edge, pub usize);
impl super::BootstrapClass for Margin {
impl IntoBsClass for Margin {
fn as_classname(&self) -> String {
format!("m{}-{}", self.0, self.1)
}

144
src/prelude/mod.rs

@ -1,117 +1,12 @@
mod border;
mod color;
mod edge;
mod margin;
mod padding;
pub use self::{border::Border, color::Color, margin::Margin, padding::Padding};
use std::fmt::{Display, Formatter, Result};
pub use self::{border::Border, color::Color, edge::Edge, margin::Margin, padding::Padding};
use yew::prelude::*;
#[derive(Properties, Clone, PartialEq, Default)]
pub struct Props {
#[prop_or_default]
pub aria_label: String,
#[prop_or_default]
pub role: String,
#[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>,
#[prop_or_default]
pub class: String,
#[prop_or_default]
pub style: String,
#[prop_or_default]
pub children: Children,
}
impl<'a> From<&'a Props> for BootstrapProps<'a> {
fn from(props: &Props) -> BootstrapProps {
let class = &props.class;
let borders = collect_bs(&props.border, &props.borders);
let margins = collect_bs(&props.margin, &props.margins);
let paddings = collect_bs(&props.padding, &props.paddings);
BootstrapProps {
class,
borders,
margins,
paddings,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Edge {
All,
Top,
Right,
Bottom,
Left,
LeftAndRight,
TopAndBottom,
}
impl Display for Edge {
fn fmt(&self, fmt: &mut Formatter) -> Result {
match self {
Self::All => write!(fmt, ""),
Self::Top => write!(fmt, "t"),
Self::Bottom => write!(fmt, "b"),
Self::Right => write!(fmt, "r"),
Self::Left => write!(fmt, "l"),
Self::LeftAndRight => write!(fmt, "x"),
Self::TopAndBottom => write!(fmt, "y"),
}
}
}
impl Edge {
fn suffix(&self) -> &str {
match self {
Self::Top => "-top",
_ => "",
}
}
fn with_prefix<S: AsRef<str>>(&self, prefix: S) -> String {
match self {
Self::All => prefix.as_ref().to_owned(),
Self::LeftAndRight => format!(
"{0}{1} {0}{2}",
prefix.as_ref(),
Self::Left.suffix(),
Self::Right.suffix()
),
Self::TopAndBottom => format!(
"{0}{1} {0}{2}",
prefix.as_ref(),
Self::Top.suffix(),
Self::Bottom.suffix()
),
_ => format!("{}{}", prefix.as_ref(), self.suffix()),
}
}
}
trait BootstrapClass {
fn as_classname(&self) -> String;
}
pub struct BootstrapProps<'a> {
pub class: &'a str,
pub borders: Vec<&'a Border>,
pub margins: Vec<&'a Margin>,
pub paddings: Vec<&'a Padding>,
}
pub fn render_on_change<P: Properties + PartialEq>(
props_on_comp: &mut P,
props: P,
@ -131,38 +26,3 @@ pub fn valid_as_class(v: &Option<bool>) -> &'static str {
Some(false) => " is-invalid",
}
}
pub fn collect_bs<'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()
}
}
pub fn calculate_classes<S: AsRef<str>>(prefix: S, props: BootstrapProps) -> String {
let BootstrapProps {
class,
borders,
margins,
paddings,
} = props;
let mut classes = Vec::new();
if !prefix.as_ref().is_empty() {
classes.push(prefix.as_ref().to_owned());
}
if !props.class.is_empty() {
classes.push(class.to_owned());
}
classes.append(&mut into_classnames(borders));
classes.append(&mut into_classnames(margins));
classes.append(&mut into_classnames(paddings));
classes.join(" ")
}
fn into_classnames<C: BootstrapClass>(c: Vec<&C>) -> Vec<String> {
c.into_iter().map(|c| c.as_classname()).collect()
}

4
src/prelude/padding.rs

@ -1,7 +1,9 @@
use crate::props::IntoBsClass;
#[derive(Debug, Clone, PartialEq)]
pub struct Padding(pub super::Edge, pub usize);
impl super::BootstrapClass for Padding {
impl IntoBsClass for Padding {
fn as_classname(&self) -> String {
format!("p{}-{}", self.0, self.1)
}

142
src/props.rs

@ -0,0 +1,142 @@
use crate::prelude::*;
use std::collections::HashMap;
use yew::{prelude::*, virtual_dom::VNode};
pub(crate) trait IntoBsClass {
fn as_classname(&self) -> String;
}
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()
}
}