Re-factor props and attrs handling.
This commit is contained in:
parent
c6ba1f9b11
commit
6236cf6ae9
27 changed files with 604 additions and 500 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bootstrap-rs"
|
name = "bootstrap-rs"
|
||||||
version = "0.2.6"
|
version = "0.2.7"
|
||||||
authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
|
authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +1,14 @@
|
||||||
mod convert;
|
mod props;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use self::props::Props;
|
||||||
use yew::{html::Children, prelude::*};
|
use crate::{prelude::*, render};
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct Alert {
|
pub struct Alert {
|
||||||
link: ComponentLink<Self>,
|
link: ComponentLink<Self>,
|
||||||
props: Props,
|
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 {
|
impl Component for Alert {
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
type Message = ();
|
type Message = ();
|
||||||
|
@ -54,10 +27,8 @@ impl Component for Alert {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let color_class = self.props.color.with_prefix("alert");
|
let html = html! {
|
||||||
let class = calculate_classes(format!("alert {}", color_class), (&self.props).into());
|
<div>
|
||||||
html! {
|
|
||||||
<div class=class>
|
|
||||||
{ self.props.children.render() }
|
{ self.props.children.render() }
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -69,6 +40,11 @@ impl Component for Alert {
|
||||||
<span aria-hidden="true">{ "×" }</span>
|
<span aria-hidden="true">{ "×" }</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
};
|
||||||
|
render::render_with_prefix(
|
||||||
|
&self.props,
|
||||||
|
vec!["alert", &self.props.color.with_prefix("alert")],
|
||||||
|
html,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
55
src/alert/props.rs
Normal file
55
src/alert/props.rs
Normal file
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,17 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
|
use std::collections::HashMap;
|
||||||
use yew::{html::Children, prelude::*};
|
use yew::{html::Children, prelude::*};
|
||||||
|
|
||||||
pub struct BreadcrumbItem {
|
pub struct BreadcrumbItem {
|
||||||
props: Props,
|
props: Props,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Properties, Clone, PartialEq)]
|
#[derive(Properties, Clone, PartialEq, Default)]
|
||||||
pub struct Props {
|
pub struct Props {
|
||||||
|
// component specific
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
|
|
||||||
|
// bootstrap specific
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub border: Option<Border>,
|
pub border: Option<Border>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
|
@ -20,10 +24,14 @@ pub struct Props {
|
||||||
pub padding: Option<Padding>,
|
pub padding: Option<Padding>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub paddings: Vec<Padding>,
|
pub paddings: Vec<Padding>,
|
||||||
|
|
||||||
|
// html specific
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub class: String,
|
pub id: Option<String>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub style: String,
|
pub class: Classes,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub style: Option<String>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub children: Children,
|
pub children: Children,
|
||||||
}
|
}
|
||||||
|
@ -45,43 +53,87 @@ impl Component for BreadcrumbItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
if self.props.active {
|
let (prefix, html) = if self.props.active {
|
||||||
|
(
|
||||||
|
vec!["breadcrumb-item", "active"],
|
||||||
html! {
|
html! {
|
||||||
<li class=self.classes() aria-current="page">
|
<li class=self.classes() aria-current="page">
|
||||||
{ self.props.children.render() }
|
{ self.props.children.render() }
|
||||||
</li>
|
</li>
|
||||||
}
|
},
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
|
(
|
||||||
|
vec!["breadcrumb-item"],
|
||||||
html! {
|
html! {
|
||||||
<li class=self.classes()>
|
<li class=self.classes()>
|
||||||
{ self.props.children.render() }
|
{ self.props.children.render() }
|
||||||
</li>
|
</li>
|
||||||
}
|
},
|
||||||
}
|
)
|
||||||
|
};
|
||||||
|
render::render_with_prefix(&self.props, prefix, html)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BreadcrumbItem {
|
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 {
|
if self.props.active {
|
||||||
calculate_classes("breadcrumb-item active", (&self.props).into())
|
classes.push("active")
|
||||||
} else {
|
|
||||||
calculate_classes("breadcrumb-item", (&self.props).into())
|
|
||||||
}
|
}
|
||||||
|
classes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a Props> for BootstrapProps<'a> {
|
impl<'a> From<&'a Props> for BootstrapProps<'a> {
|
||||||
fn from(props: &Props) -> BootstrapProps {
|
fn from(props: &Props) -> BootstrapProps {
|
||||||
let class = &props.class;
|
let class = &props.class;
|
||||||
let borders = collect_bs(&props.border, &props.borders);
|
let borders = collect_props(&props.border, &props.borders);
|
||||||
let margins = collect_bs(&props.margin, &props.margins);
|
let margins = collect_props(&props.margin, &props.margins);
|
||||||
let paddings = collect_bs(&props.padding, &props.paddings);
|
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 {
|
BootstrapProps {
|
||||||
class,
|
class,
|
||||||
borders,
|
borders,
|
||||||
margins,
|
margins,
|
||||||
paddings,
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod item;
|
mod item;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
pub use item::BreadcrumbItem;
|
pub use item::BreadcrumbItem;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
@ -25,19 +25,13 @@ impl Component for Breadcrumb {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let class = calculate_classes("breadcrumb", (&self.props).into());
|
let html = html! {
|
||||||
html! {
|
<nav aria-label="breadcrumb">
|
||||||
<nav
|
<ol class="breadcrumb">
|
||||||
class=class
|
|
||||||
aria-label="breadcrumb"
|
|
||||||
style=self.props.style.clone()
|
|
||||||
>
|
|
||||||
<ol
|
|
||||||
class="breadcrumb"
|
|
||||||
>
|
|
||||||
{ self.props.children.render() }
|
{ self.props.children.render() }
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
}
|
};
|
||||||
|
render::render_with_prefix(&self.props, "breadcrumb", html)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::render_on_change, props::Props, render};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct ButtonGroup {
|
pub struct ButtonGroup {
|
||||||
|
@ -22,24 +22,6 @@ impl Component for ButtonGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
html! {
|
render::render_with_prefix(&self.props, "btn-group", render::div(&self.props.children))
|
||||||
<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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct CardBody {
|
pub struct CardBody {
|
||||||
|
@ -22,11 +22,29 @@ impl Component for CardBody {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let class = calculate_classes("card-body", (&self.props).into());
|
render::render_with_prefix(&self.props, "card-body", render::div(&self.props.children))
|
||||||
html! {
|
}
|
||||||
<div class=class style=self.props.style.clone()>
|
}
|
||||||
{ self.props.children.render() }
|
|
||||||
|
#[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>
|
</div>
|
||||||
}
|
};
|
||||||
|
|
||||||
|
assert_eq!(expected, comp.view());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,10 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
use yew::{html::Children, prelude::*};
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct CardHeader {
|
pub struct CardHeader {
|
||||||
props: Props,
|
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 {
|
impl Component for CardHeader {
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
type Message = ();
|
type Message = ();
|
||||||
|
@ -32,28 +22,26 @@ impl Component for CardHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
html! {
|
render::render_with_prefix(&self.props, "card-header", render::p(&self.props.children))
|
||||||
<p class=self.class() style=self.style()>
|
}
|
||||||
{ self.props.children.render() }
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_comp() {
|
||||||
|
let comp = CardHeader {
|
||||||
|
props: Props::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let expected = html! {
|
||||||
|
<p class="card-header">
|
||||||
|
<></>
|
||||||
</p>
|
</p>
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CardHeader {
|
assert_eq!(expected, comp.view());
|
||||||
fn class(&self) -> String {
|
|
||||||
if self.props.class.is_empty() {
|
|
||||||
"card-header".into()
|
|
||||||
} else {
|
|
||||||
format!("card-header {}", self.props.class)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn style(&self) -> &str {
|
|
||||||
if self.props.style.is_empty() {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
&self.props.style
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ mod header;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
pub use self::{body::CardBody, header::CardHeader, text::CardText};
|
pub use self::{body::CardBody, header::CardHeader, text::CardText};
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct Card {
|
pub struct Card {
|
||||||
|
@ -27,11 +27,6 @@ impl Component for Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let class = calculate_classes("card", (&self.props).into());
|
render::render_with_prefix(&self.props, "card", render::div(&self.props.children))
|
||||||
html! {
|
|
||||||
<div class=class style=self.props.style.clone()>
|
|
||||||
{ self.props.children.render() }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,10 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
use yew::{html::Children, prelude::*};
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct CardText {
|
pub struct CardText {
|
||||||
props: Props,
|
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 {
|
impl Component for CardText {
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
type Message = ();
|
type Message = ();
|
||||||
|
@ -32,28 +22,6 @@ impl Component for CardText {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
html! {
|
render::render_with_prefix(&self.props, "card-text", render::p(&self.props.children))
|
||||||
<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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
|
@ -22,11 +22,7 @@ impl Component for Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
html! {
|
render::render_with_prefix(&self.props, "container", render::div(&self.props.children))
|
||||||
<div class=calculate_classes("container", (&self.props).into())>
|
|
||||||
{ self.props.children.render() }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct FormGroup {
|
pub struct FormGroup {
|
||||||
|
@ -22,32 +22,6 @@ impl Component for FormGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
html! {
|
render::render_with_prefix(&self.props, "form-group", render::div(&self.props.children))
|
||||||
<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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, props::*, render};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct InputGroup {
|
pub struct InputGroup {
|
||||||
|
@ -22,11 +22,10 @@ impl Component for InputGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let class = calculate_classes("input-group", (&self.props).into());
|
render::render_with_prefix(
|
||||||
html! {
|
&self.props,
|
||||||
<div class=class>
|
"input-group",
|
||||||
{ self.props.children.render() }
|
render::div(&self.props.children),
|
||||||
</div>
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,28 @@
|
||||||
mod group;
|
mod group;
|
||||||
|
mod props;
|
||||||
mod textarea;
|
mod textarea;
|
||||||
|
|
||||||
|
use self::props::Props;
|
||||||
pub use self::{group::InputGroup, textarea::TextArea};
|
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::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum InputType {
|
pub enum InputType {
|
||||||
Text,
|
Text,
|
||||||
|
Date,
|
||||||
|
Checkbox,
|
||||||
|
Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputType {
|
impl Display for InputType {
|
||||||
fn as_str(&self) -> &str {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
match self {
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct InputChange(ChangeData);
|
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 {
|
impl Component for Input {
|
||||||
type Message = InputChange;
|
type Message = InputChange;
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
|
@ -86,40 +63,25 @@ impl Component for Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let prefix = if self.props.readonly {
|
let input_type = self
|
||||||
format!(
|
.props
|
||||||
"form-control-plaintext{}",
|
.input_type
|
||||||
valid_as_class(&self.props.valid)
|
.as_ref()
|
||||||
)
|
.unwrap_or_else(|| &InputType::Text);
|
||||||
|
let mut prefix = if self.props.readonly {
|
||||||
|
vec!["form-control-plaintext"]
|
||||||
} else {
|
} else {
|
||||||
format!("form-control{}", valid_as_class(&self.props.valid))
|
vec!["form-control"]
|
||||||
};
|
};
|
||||||
let class = calculate_classes(prefix, (&self.props).into());
|
prefix.push(valid_as_class(&self.props.valid));
|
||||||
html! {
|
let html = html! {
|
||||||
<input
|
<input
|
||||||
name=&self.props.name
|
type=input_type
|
||||||
id=&self.props.id
|
|
||||||
type=self.props.input_type.as_str()
|
|
||||||
class=class
|
|
||||||
value=&self.state
|
value=&self.state
|
||||||
readonly=self.props.readonly
|
readonly=self.props.readonly
|
||||||
onchange=self.link.callback(|evt| InputChange(evt))
|
onchange=self.link.callback(|evt| InputChange(evt))
|
||||||
/>
|
/>
|
||||||
}
|
};
|
||||||
}
|
render::render_with_prefix(&self.props, prefix, html)
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
73
src/input/props.rs
Normal file
73
src/input/props.rs
Normal file
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::prelude::*;
|
use super::props::Props;
|
||||||
use yew::prelude::*;
|
use crate::{prelude::*, render};
|
||||||
|
use yew::{prelude::*, virtual_dom::VNode};
|
||||||
|
|
||||||
pub struct TextArea {
|
pub struct TextArea {
|
||||||
link: ComponentLink<Self>,
|
link: ComponentLink<Self>,
|
||||||
|
@ -10,41 +11,21 @@ pub struct TextArea {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InputChange(ChangeData);
|
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 {
|
impl Component for TextArea {
|
||||||
type Message = InputChange;
|
type Message = InputChange;
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
|
|
||||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
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 }
|
Self { props, state, link }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,31 +48,14 @@ impl Component for TextArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let prefix = format!("form-control{}", valid_as_class(&self.props.valid));
|
let prefix = vec!["form-control", &valid_as_class(&self.props.valid)];
|
||||||
html! {
|
let html = html! {
|
||||||
<textarea
|
<textarea
|
||||||
name=&self.props.name
|
|
||||||
id=&self.props.id
|
|
||||||
class=calculate_classes(prefix, (&self.props).into())
|
|
||||||
onchange=self.link.callback(|evt| InputChange(evt))
|
onchange=self.link.callback(|evt| InputChange(evt))
|
||||||
>
|
>
|
||||||
{ &self.state }
|
{ &self.state }
|
||||||
</textarea>
|
</textarea>
|
||||||
}
|
};
|
||||||
}
|
render::render_with_prefix(&self.props, prefix, html)
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use crate::{props::Props, render};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub struct Jumbotron {
|
pub struct Jumbotron {
|
||||||
|
@ -22,11 +22,11 @@ impl Component for Jumbotron {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let class = calculate_classes("jumbotron", (&self.props).into());
|
let html = html! {
|
||||||
html! {
|
<div>
|
||||||
<div class=class>
|
|
||||||
{ self.props.children.render() }
|
{ self.props.children.render() }
|
||||||
</div>
|
</div>
|
||||||
}
|
};
|
||||||
|
render::render_with_prefix(&self.props, "jumbotron", html)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ mod form;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
mod jumbotron;
|
mod jumbotron;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
pub(crate) mod props;
|
||||||
|
mod render;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
alert::Alert,
|
alert::Alert,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use super::{Color, Edge};
|
use super::{Color, Edge};
|
||||||
|
use crate::props::IntoBsClass;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Border(pub Edge, pub Color);
|
pub struct Border(pub Edge, pub Color);
|
||||||
|
|
||||||
impl super::BootstrapClass for Border {
|
impl IntoBsClass for Border {
|
||||||
fn as_classname(&self) -> String {
|
fn as_classname(&self) -> String {
|
||||||
let edge = match self.0 {
|
let edge = match self.0 {
|
||||||
Edge::All => "border".to_owned(),
|
Edge::All => "border".to_owned(),
|
||||||
|
|
|
@ -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
Normal file
54
src/prelude/edge.rs
Normal file
|
@ -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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
|
use crate::props::IntoBsClass;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Margin(pub super::Edge, pub usize);
|
pub struct Margin(pub super::Edge, pub usize);
|
||||||
|
|
||||||
impl super::BootstrapClass for Margin {
|
impl IntoBsClass for Margin {
|
||||||
fn as_classname(&self) -> String {
|
fn as_classname(&self) -> String {
|
||||||
format!("m{}-{}", self.0, self.1)
|
format!("m{}-{}", self.0, self.1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,117 +1,12 @@
|
||||||
mod border;
|
mod border;
|
||||||
mod color;
|
mod color;
|
||||||
|
mod edge;
|
||||||
mod margin;
|
mod margin;
|
||||||
mod padding;
|
mod padding;
|
||||||
|
|
||||||
pub use self::{border::Border, color::Color, margin::Margin, padding::Padding};
|
pub use self::{border::Border, color::Color, edge::Edge, margin::Margin, padding::Padding};
|
||||||
use std::fmt::{Display, Formatter, Result};
|
|
||||||
use yew::prelude::*;
|
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>(
|
pub fn render_on_change<P: Properties + PartialEq>(
|
||||||
props_on_comp: &mut P,
|
props_on_comp: &mut P,
|
||||||
props: P,
|
props: P,
|
||||||
|
@ -131,38 +26,3 @@ pub fn valid_as_class(v: &Option<bool>) -> &'static str {
|
||||||
Some(false) => " is-invalid",
|
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()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
use crate::props::IntoBsClass;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Padding(pub super::Edge, pub usize);
|
pub struct Padding(pub super::Edge, pub usize);
|
||||||
|
|
||||||
impl super::BootstrapClass for Padding {
|
impl IntoBsClass for Padding {
|
||||||
fn as_classname(&self) -> String {
|
fn as_classname(&self) -> String {
|
||||||
format!("p{}-{}", self.0, self.1)
|
format!("p{}-{}", self.0, self.1)
|
||||||
}
|
}
|
||||||
|
|
142
src/props.rs
Normal file
142
src/props.rs
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
if let Some(ref id) = props.id {
|
||||||
|
attributes.insert("id", id);
|
||||||
|
}
|
||||||
|
if let Some(ref aria_label) = props.aria_label {
|
||||||
|
attributes.insert("aria-label", aria_label);
|
||||||
|
}
|
||||||
|
if let Some(ref role) = props.role {
|
||||||
|
attributes.insert("role", role);
|
||||||
|
}
|
||||||
|
if let Some(ref style) = props.style {
|
||||||
|
attributes.insert("style", 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 {
|
||||||
|
let attrs = self
|
||||||
|
.attributes
|
||||||
|
.iter()
|
||||||
|
.map(|(key, value)| (key.to_string(), (*value).to_owned()))
|
||||||
|
.collect();
|
||||||
|
tag.add_attributes(attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_classes<C: Into<Classes>>(&self, prefix: C) -> Classes {
|
||||||
|
let BootstrapProps {
|
||||||
|
class,
|
||||||
|
borders,
|
||||||
|
margins,
|
||||||
|
paddings,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
let classes = prefix.into();
|
||||||
|
let classes = classes.extend((*class).to_owned());
|
||||||
|
let classes = classes.extend(into_classnames(borders));
|
||||||
|
let classes = classes.extend(into_classnames(margins));
|
||||||
|
classes.extend(into_classnames(paddings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_classnames<C: IntoBsClass>(c: &[&C]) -> Classes {
|
||||||
|
c.iter().fold(Classes::new(), |mut cs, c| {
|
||||||
|
cs.push(&c.as_classname());
|
||||||
|
cs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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" />
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expected,
|
||||||
|
crate::render::render_with_prefix(&props, "", html! { <div aria-label="test" /> })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
51
src/render.rs
Normal file
51
src/render.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::props::*;
|
||||||
|
use yew::{html::Children, prelude::*, virtual_dom::VNode};
|
||||||
|
|
||||||
|
pub(crate) fn render_with_prefix<'a, B: Into<BootstrapProps<'a>>, C: Into<Classes>>(
|
||||||
|
props: B,
|
||||||
|
prefix: C,
|
||||||
|
mut node: VNode,
|
||||||
|
) -> VNode {
|
||||||
|
let props = props.into();
|
||||||
|
if let VNode::VTag(tag) = &mut node {
|
||||||
|
let classes = &props.calculate_classes(prefix);
|
||||||
|
if !classes.is_empty() {
|
||||||
|
tag.add_attribute("class", classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.add_attributes(&mut node);
|
||||||
|
node
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn div(children: &Children) -> Html {
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
{ children.render() }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn p(children: &Children) -> Html {
|
||||||
|
html! {
|
||||||
|
<p>
|
||||||
|
{ children.render() }
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::props::Props;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_prefixes() {
|
||||||
|
let props = Props::default();
|
||||||
|
let comp = render_with_prefix(&props, vec!["first", "second"], html! { <div/> });
|
||||||
|
let expected = html! {
|
||||||
|
<div class="first second"/>
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(expected, comp);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue