From 96b6152d7bca189d590da799b13d3814e7723613 Mon Sep 17 00:00:00 2001 From: Thomas Gideon Date: Thu, 28 May 2020 14:09:25 -0400 Subject: [PATCH] Initial commit --- .gitignore | 2 + Cargo.toml | 20 ++++++++ src/breadcrumb/item.rs | 46 ++++++++++++++++++ src/breadcrumb/mod.rs | 77 ++++++++++++++++++++++++++++++ src/button/mod.rs | 67 ++++++++++++++++++++++++++ src/card/body.rs | 59 +++++++++++++++++++++++ src/card/header.rs | 59 +++++++++++++++++++++++ src/card/mod.rs | 93 +++++++++++++++++++++++++++++++++++++ src/card/text.rs | 59 +++++++++++++++++++++++ src/container.rs | 37 +++++++++++++++ src/input/group.rs | 57 +++++++++++++++++++++++ src/input/mod.rs | 88 +++++++++++++++++++++++++++++++++++ src/input/textarea.rs | 57 +++++++++++++++++++++++ src/jumbotron.rs | 43 +++++++++++++++++ src/lib.rs | 16 +++++++ src/prelude/border.rs | 20 ++++++++ src/prelude/border_color.rs | 22 +++++++++ src/prelude/margin.rs | 8 ++++ src/prelude/mod.rs | 76 ++++++++++++++++++++++++++++++ src/prelude/padding.rs | 8 ++++ 20 files changed, 914 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/breadcrumb/item.rs create mode 100644 src/breadcrumb/mod.rs create mode 100644 src/button/mod.rs create mode 100644 src/card/body.rs create mode 100644 src/card/header.rs create mode 100644 src/card/mod.rs create mode 100644 src/card/text.rs create mode 100644 src/container.rs create mode 100644 src/input/group.rs create mode 100644 src/input/mod.rs create mode 100644 src/input/textarea.rs create mode 100644 src/jumbotron.rs create mode 100644 src/lib.rs create mode 100644 src/prelude/border.rs create mode 100644 src/prelude/border_color.rs create mode 100644 src/prelude/margin.rs create mode 100644 src/prelude/mod.rs create mode 100644 src/prelude/padding.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9f68c1c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "bootstrap-rs" +version = "0.1.0" +authors = ["Thomas Gideon "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +yew = "~0.16.2" +log = "~0.4.8" +serde_json = "^1.0.40" +serde = "^1.0.98" +anyhow = "^1.0.28" +wasm-bindgen = "~0.2.61" +web-sys = "~0.3.38" +wasm-logger = "~0.2.0" +yew-router = "~0.13.0" +yew-components = "~0.1.2" diff --git a/src/breadcrumb/item.rs b/src/breadcrumb/item.rs new file mode 100644 index 0000000..cb11304 --- /dev/null +++ b/src/breadcrumb/item.rs @@ -0,0 +1,46 @@ +use crate::prelude::*; +use yew::{html::Children, prelude::*}; + +pub struct BreadcrumbItem { + props: Props, +} + +#[derive(Properties, Clone, PartialEq)] +pub struct Props { + pub active: bool, + #[prop_or_default] + pub children: Children, +} + +impl Component for BreadcrumbItem { + type Properties = Props; + type Message = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + if self.props.active { + html! { + + } + } else { + html! { + + } + } + } +} diff --git a/src/breadcrumb/mod.rs b/src/breadcrumb/mod.rs new file mode 100644 index 0000000..74c43e7 --- /dev/null +++ b/src/breadcrumb/mod.rs @@ -0,0 +1,77 @@ +mod item; + +use crate::prelude::*; +pub use item::BreadcrumbItem; +use yew::{html::Children, prelude::*}; + +pub struct Breadcrumb { + props: Props, +} + +#[derive(Properties, Clone, PartialEq)] +pub struct Props { + #[prop_or_default] + pub border: Option, + #[prop_or_default] + pub borders: Vec, + #[prop_or_default] + pub border_color: Option, + #[prop_or_default] + pub border_colors: Vec, + #[prop_or_default] + pub margin: Option, + #[prop_or_default] + pub margins: Vec, + #[prop_or_default] + pub class: String, + #[prop_or_default] + pub style: String, + #[prop_or_default] + pub children: Children, +} + +impl Component for Breadcrumb { + type Properties = Props; + type Message = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + html! { + + } + } +} + +impl<'a> From<&'a Props> for BootstrapProps<'a> { + fn from(props: &Props) -> BootstrapProps { + let borders = collect_bs(&props.border, &props.borders); + let border_colors = collect_bs(&props.border_color, &props.border_colors); + let margins = collect_bs(&props.margin, &props.margins); + BootstrapProps { + borders, + border_colors, + margins, + } + } +} diff --git a/src/button/mod.rs b/src/button/mod.rs new file mode 100644 index 0000000..acbf6d3 --- /dev/null +++ b/src/button/mod.rs @@ -0,0 +1,67 @@ +use crate::prelude::*; +use yew::{html::Children, prelude::*}; + +pub struct ButtonGroup { + 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 role: String, + #[prop_or_default] + pub aria_label: String, + #[prop_or_default] + pub children: Children, +} + +impl Component for ButtonGroup { + type Properties = Props; + type Message = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + html! { +
+ { self.props.children.render() } +
+ } + } +} + +impl ButtonGroup { + fn class(&self) -> String { + if self.props.class.is_empty() { + "btn-grp".into() + } else { + format!("btn-group {}", self.props.class) + } + } + + fn style(&self) -> &str { + if self.props.style.is_empty() { + "" + } else { + &self.props.style + } + } +} diff --git a/src/card/body.rs b/src/card/body.rs new file mode 100644 index 0000000..d0b7f50 --- /dev/null +++ b/src/card/body.rs @@ -0,0 +1,59 @@ +use crate::prelude::*; +use yew::{html::Children, prelude::*}; + +pub struct CardBody { + 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 CardBody { + type Properties = Props; + type Message = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + html! { +
+ { self.props.children.render() } +
+ } + } +} + +impl CardBody { + fn class(&self) -> String { + if self.props.class.is_empty() { + "card-body".into() + } else { + format!("card-body {}", self.props.class) + } + } + + fn style(&self) -> &str { + if self.props.style.is_empty() { + "" + } else { + &self.props.style + } + } +} diff --git a/src/card/header.rs b/src/card/header.rs new file mode 100644 index 0000000..c617270 --- /dev/null +++ b/src/card/header.rs @@ -0,0 +1,59 @@ +use crate::prelude::*; +use yew::{html::Children, 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 = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + html! { +

+ { self.props.children.render() } +

+ } + } +} + +impl CardHeader { + 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 + } + } +} diff --git a/src/card/mod.rs b/src/card/mod.rs new file mode 100644 index 0000000..4cc7a47 --- /dev/null +++ b/src/card/mod.rs @@ -0,0 +1,93 @@ +mod body; +mod header; +mod text; + +pub use self::{body::CardBody, header::CardHeader, text::CardText}; +use crate::prelude::*; +use yew::{html::Children, prelude::*}; + +pub struct Card { + props: Props, +} + +#[derive(Properties, Clone, PartialEq)] +pub struct Props { + #[prop_or_default] + pub margin: Option, + #[prop_or_default] + pub margins: Vec, + #[prop_or_default] + pub border: Option, + #[prop_or_default] + pub borders: Vec, + #[prop_or_default] + pub border_color: Option, + #[prop_or_default] + pub border_colors: Vec, + #[prop_or_default] + pub class: String, + #[prop_or_default] + pub style: String, + #[prop_or_default] + pub children: Children, +} + +impl Component for Card { + type Properties = Props; + type Message = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + html! { +
+ { self.props.children.render() } +
+ } + } +} + +impl Card { + fn class(&self) -> String { + if self.props.class.is_empty() { + format!("card {}", calculate_classes((&self.props).into())) + } else { + format!( + "card {} {}", + self.props.class, + calculate_classes((&self.props).into()) + ) + } + } + + fn style(&self) -> &str { + if self.props.style.is_empty() { + "" + } else { + &self.props.style + } + } +} + +impl<'a> From<&'a Props> for BootstrapProps<'a> { + fn from(props: &'a Props) -> Self { + let borders = collect_bs(&props.border, &props.borders); + let border_colors = collect_bs(&props.border_color, &props.border_colors); + let margins = collect_bs(&props.margin, &props.margins); + Self { + borders, + border_colors, + margins, + } + } +} diff --git a/src/card/text.rs b/src/card/text.rs new file mode 100644 index 0000000..9d319ad --- /dev/null +++ b/src/card/text.rs @@ -0,0 +1,59 @@ +use crate::prelude::*; +use yew::{html::Children, 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 = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + html! { +

+ { self.props.children.render() } +

+ } + } +} + +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 + } + } +} diff --git a/src/container.rs b/src/container.rs new file mode 100644 index 0000000..f4ce492 --- /dev/null +++ b/src/container.rs @@ -0,0 +1,37 @@ +use crate::prelude::*; +use yew::{html::Children, prelude::*}; + +pub struct Container { + props: Props, +} + +#[derive(Properties, Clone, PartialEq)] +pub struct Props { + #[prop_or_default] + pub children: Children, +} + +impl Component for Container { + type Properties = Props; + type Message = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + html! { +
+ { self.props.children.render() } +
+ } + } +} diff --git a/src/input/group.rs b/src/input/group.rs new file mode 100644 index 0000000..ec4a3a0 --- /dev/null +++ b/src/input/group.rs @@ -0,0 +1,57 @@ +use crate::prelude::*; +use yew::{html::Children, prelude::*}; + +pub struct InputGroup { + props: Props, +} + +#[derive(Properties, Clone, PartialEq)] +pub struct Props { + #[prop_or_default] + pub margin: Option, + #[prop_or_default] + pub margins: Vec, + #[prop_or_default] + pub class: String, + #[prop_or_default] + pub children: Children, +} + +impl<'a> From<&'a Props> for BootstrapProps<'a> { + fn from(props: &'a Props) -> BootstrapProps<'a> { + let borders = Vec::new(); + let border_colors = Vec::new(); + let margins = collect_bs(&props.margin, &props.margins); + Self { + borders, + border_colors, + margins, + } + } +} + +impl Component for InputGroup { + type Message = (); + type Properties = Props; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + let classes = calculate_classes((&self.props).into()); + html! { +
+ { self.props.children.render() } +
+ } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 0000000..19cb4f9 --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,88 @@ +mod group; +mod textarea; + +pub use self::{group::InputGroup, textarea::TextArea}; +use crate::prelude::*; +use yew::prelude::*; + +#[derive(Clone, PartialEq)] +pub enum InputType { + Text, +} + +impl InputType { + fn as_str(&self) -> &str { + match self { + Self::Text => "text", + } + } +} + +pub struct Input { + link: ComponentLink, + state: String, + props: Props, +} + +#[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_signal: Callback, + pub input_type: InputType, + #[prop_or_default] + pub readonly: bool, + #[prop_or_default] + pub value: String, +} + +impl Component for Input { + type Message = InputChange; + type Properties = Props; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + let state = props.value.clone(); + Self { props, state, link } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + if let InputChange(ChangeData::Value(value)) = msg { + self.state = value.clone(); + self.props.on_signal.emit(value); + true + } else { + false + } + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + if self.props.value != props.value { + self.state = props.value.clone(); + } + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + let class = if self.props.readonly { + "form-control-plaintext pl-3" + } else { + "form-control" + }; + html! { + + } + } +} diff --git a/src/input/textarea.rs b/src/input/textarea.rs new file mode 100644 index 0000000..dee63dd --- /dev/null +++ b/src/input/textarea.rs @@ -0,0 +1,57 @@ +use crate::prelude::*; +use yew::prelude::*; + +pub struct TextArea { + link: ComponentLink, + state: String, + props: Props, +} + +#[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_signal: Callback, +} + +impl Component for TextArea { + type Message = InputChange; + type Properties = Props; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + let state = String::default(); + Self { props, state, link } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + if let InputChange(ChangeData::Value(value)) = msg { + self.state = value.clone(); + self.props.on_signal.emit(value); + true + } else { + false + } + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + render_on_change(&mut self.props, props) + } + + fn view(&self) -> Html { + html! { + + } + } +} diff --git a/src/jumbotron.rs b/src/jumbotron.rs new file mode 100644 index 0000000..585e9f9 --- /dev/null +++ b/src/jumbotron.rs @@ -0,0 +1,43 @@ +use crate::prelude::*; +use yew::{html::Children, prelude::*}; + +pub struct Jumbotron { + props: Props, +} + +#[derive(Properties, Clone)] +pub struct Props { + #[prop_or_default] + pub margin: Option, + #[prop_or_default] + pub margins: Vec, + #[prop_or_default] + pub class: String, + #[prop_or_default] + pub children: Children, +} + +impl Component for Jumbotron { + type Properties = Props; + type Message = (); + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self { props } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, _: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { +
+ { self.props.children.render() } +
+ } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..57e1b0c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +mod breadcrumb; +mod button; +mod card; +mod container; +pub mod input; +mod jumbotron; +pub mod prelude; + +pub use self::{ + breadcrumb::{Breadcrumb, BreadcrumbItem}, + button::ButtonGroup, + card::{Card, CardBody, CardHeader, CardText}, + container::Container, + input::{Input, InputGroup, TextArea}, + jumbotron::Jumbotron, +}; diff --git a/src/prelude/border.rs b/src/prelude/border.rs new file mode 100644 index 0000000..a791ae9 --- /dev/null +++ b/src/prelude/border.rs @@ -0,0 +1,20 @@ +#[derive(Clone, PartialEq)] +pub enum Border { + All, + Top, + Right, + Bottom, + Left, +} + +impl super::BootstrapClass for Border { + fn as_classname(&self) -> String { + match self { + Self::All => "border".into(), + Self::Top => "border-top".into(), + Self::Right => "border-right".into(), + Self::Bottom => "border-bottom".into(), + Self::Left => "border-left".into(), + } + } +} diff --git a/src/prelude/border_color.rs b/src/prelude/border_color.rs new file mode 100644 index 0000000..62a6e22 --- /dev/null +++ b/src/prelude/border_color.rs @@ -0,0 +1,22 @@ +#[derive(Clone, PartialEq)] +pub enum BorderColor { + Primary, + Secondary, + Unset, +} + +impl Default for BorderColor { + fn default() -> Self { + BorderColor::Unset + } +} + +impl super::BootstrapClass for BorderColor { + fn as_classname(&self) -> String { + match self { + Self::Primary => "border-primary".into(), + Self::Secondary => "border-secondary".into(), + Self::Unset => "".into(), + } + } +} diff --git a/src/prelude/margin.rs b/src/prelude/margin.rs new file mode 100644 index 0000000..1ab372e --- /dev/null +++ b/src/prelude/margin.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone, PartialEq)] +pub struct Margin(pub super::Edge, pub usize); + +impl super::BootstrapClass for Margin { + fn as_classname(&self) -> String { + format!("m{}-{}", self.0, self.1) + } +} diff --git a/src/prelude/mod.rs b/src/prelude/mod.rs new file mode 100644 index 0000000..116d0d4 --- /dev/null +++ b/src/prelude/mod.rs @@ -0,0 +1,76 @@ +mod border; +mod border_color; +mod margin; + +pub use self::{border::Border, border_color::BorderColor, margin::Margin}; +use std::fmt::{Display, Formatter, Result}; +use yew::prelude::*; + +#[derive(Debug, Clone, PartialEq)] +pub enum Edge { + Top, + Right, + Bottom, + Left, +} + +impl Display for Edge { + fn fmt(&self, fmt: &mut Formatter) -> Result { + match self { + Self::Top => write!(fmt, "t"), + Self::Bottom => write!(fmt, "b"), + Self::Right => write!(fmt, "r"), + Self::Left => write!(fmt, "l"), + } + } +} + +trait BootstrapClass { + fn as_classname(&self) -> String; +} + +pub struct BootstrapProps<'a> { + pub borders: Vec<&'a Border>, + pub border_colors: Vec<&'a BorderColor>, + pub margins: Vec<&'a Margin>, +} + +pub fn render_on_change( + props_on_comp: &mut P, + props: P, +) -> ShouldRender { + if props_on_comp == &props { + false + } else { + *props_on_comp = props; + true + } +} + +pub fn collect_bs<'a, T>(t: &'a Option, 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(props: BootstrapProps) -> String { + let BootstrapProps { + borders, + border_colors, + margins, + } = props; + let mut classes = Vec::new(); + classes.append(&mut into_classnames(borders)); + classes.append(&mut into_classnames(border_colors)); + classes.append(&mut into_classnames(margins)); + + classes.join(" ") +} + +fn into_classnames(c: Vec<&C>) -> Vec { + c.into_iter().map(|c| c.as_classname()).collect() +} diff --git a/src/prelude/padding.rs b/src/prelude/padding.rs new file mode 100644 index 0000000..01766ba --- /dev/null +++ b/src/prelude/padding.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone, PartialEq)] +pub struct Padding(pub super::Edge, pub usize); + +impl super::BootstrapClass for Padding { + fn as_classname(&self) -> String { + format!("m{}-{}", self.0, self.1) + } +}