394 lines
10 KiB
Rust
394 lines
10 KiB
Rust
use std::{collections::HashMap, fmt::Write};
|
|
|
|
use maud::{Markup, PreEscaped, Render, html};
|
|
|
|
use crate::ui::{
|
|
AttrExtendable, UIWidget,
|
|
color::{Blue, UIColor},
|
|
prelude::Nothing,
|
|
};
|
|
|
|
use super::InputAttr;
|
|
|
|
#[allow(non_snake_case)]
|
|
pub fn Range() -> RangeWidget {
|
|
RangeWidget {
|
|
attrs: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub struct RangeWidget {
|
|
attrs: HashMap<String, String>,
|
|
}
|
|
|
|
impl AttrExtendable for RangeWidget {
|
|
fn add_attr(mut self, key: &str, val: &str) -> Self {
|
|
self.attrs.insert(key.into(), val.into());
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RangeWidget {
|
|
pub fn min(self, min: u32) -> Self {
|
|
self.add_attr("min", &min.to_string())
|
|
}
|
|
|
|
pub fn max(self, max: u32) -> Self {
|
|
self.add_attr("max", &max.to_string())
|
|
}
|
|
|
|
pub fn step(self, step: u32) -> Self {
|
|
self.add_attr("step", &step.to_string())
|
|
}
|
|
}
|
|
|
|
impl InputAttr for RangeWidget {}
|
|
|
|
impl Render for RangeWidget {
|
|
fn render(&self) -> Markup {
|
|
self.render_with_class("")
|
|
}
|
|
}
|
|
|
|
impl UIWidget for RangeWidget {
|
|
fn can_inherit(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn base_class(&self) -> Vec<String> {
|
|
vec![
|
|
"w-full".into(),
|
|
"h-2".into(),
|
|
"bg-gray-200".into(),
|
|
"rounded-lg".into(),
|
|
"appearence-none".into(),
|
|
"cursor-pointer".into(),
|
|
"dark:bg-gray-700".into(),
|
|
]
|
|
}
|
|
|
|
fn extended_class(&self) -> Vec<String> {
|
|
self.base_class()
|
|
}
|
|
|
|
fn render_with_class(&self, class: &str) -> Markup {
|
|
let mut attrs = self.attrs.clone();
|
|
attrs.insert(
|
|
"class".into(),
|
|
format!("{class} {}", self.base_class().join(" ")),
|
|
);
|
|
attrs.insert("type".into(), "range".into());
|
|
build_element("input", &attrs, PreEscaped(String::new()))
|
|
}
|
|
}
|
|
|
|
pub fn build_element(
|
|
element: &str,
|
|
attrs: &HashMap<String, String>,
|
|
inner: PreEscaped<String>,
|
|
) -> PreEscaped<String> {
|
|
let mut ret = String::with_capacity(256);
|
|
write!(&mut ret, "<{element}").unwrap();
|
|
|
|
for (key, value) in attrs {
|
|
if value.is_empty() {
|
|
write!(&mut ret, " {key}").unwrap();
|
|
} else {
|
|
write!(&mut ret, " {key}='{}'", value.replace("'", "\\'")).unwrap();
|
|
};
|
|
}
|
|
|
|
if inner.0.is_empty() {
|
|
if element == "textarea" {
|
|
write!(&mut ret, "></textarea>").unwrap();
|
|
} else {
|
|
write!(&mut ret, ">").unwrap();
|
|
}
|
|
} else {
|
|
write!(&mut ret, ">{}</{element}>", inner.0).unwrap();
|
|
}
|
|
|
|
PreEscaped(ret)
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
pub fn Toggle(title: &str) -> ToggleWidget {
|
|
ToggleWidget {
|
|
color: Box::new(Blue::_600),
|
|
checked: false,
|
|
attrs: HashMap::new(),
|
|
title: title.to_string(),
|
|
}
|
|
}
|
|
|
|
pub struct ToggleWidget {
|
|
color: Box<dyn UIColor>,
|
|
checked: bool,
|
|
attrs: HashMap<String, String>,
|
|
title: String,
|
|
}
|
|
|
|
impl ToggleWidget {
|
|
pub fn color<C: UIColor + 'static>(mut self, color: C) -> Self {
|
|
self.color = Box::new(color);
|
|
self
|
|
}
|
|
|
|
pub fn checked(mut self, checked: bool) -> Self {
|
|
self.checked = checked;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl AttrExtendable for ToggleWidget {
|
|
fn add_attr(mut self, key: &str, val: &str) -> Self {
|
|
self.attrs.insert(key.to_string(), val.to_string());
|
|
self
|
|
}
|
|
}
|
|
|
|
impl InputAttr for ToggleWidget {}
|
|
|
|
impl Render for ToggleWidget {
|
|
fn render(&self) -> Markup {
|
|
self.render_with_class("")
|
|
}
|
|
}
|
|
|
|
impl UIWidget for ToggleWidget {
|
|
fn can_inherit(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn base_class(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn extended_class(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn render_with_class(&self, _: &str) -> Markup {
|
|
let color = self.color.color_class();
|
|
let class = format!(
|
|
"relative w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-{color} dark:peer-focus:ring-blue-800 dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-{color} dark:peer-checked:bg-{color}"
|
|
);
|
|
|
|
let mut attrs = self.attrs.clone();
|
|
|
|
attrs.insert("class".into(), "sr-only peer".into());
|
|
attrs.insert("type".into(), "checkbox".into());
|
|
|
|
if self.checked {
|
|
attrs.insert("checked".into(), "".into());
|
|
}
|
|
|
|
let input = build_element("input", &attrs, Nothing());
|
|
|
|
html! {
|
|
label class="inline-flex items-center cursor-pointer" {
|
|
(input)
|
|
div class=(class) {};
|
|
span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300" { (self.title) };
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
pub fn CustomRadio<T: UIWidget + 'static>(inner: T, group_name: &str) -> RadioWidget {
|
|
RadioWidget {
|
|
color: Box::new(Blue::_600),
|
|
checked: false,
|
|
attrs: HashMap::new(),
|
|
title: inner.render().0,
|
|
custom: true,
|
|
}
|
|
.name(group_name)
|
|
.id(&format!("radio-{}", uuid::Uuid::new_v4().to_string()))
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
pub fn Radio(title: &str, group_name: &str) -> RadioWidget {
|
|
RadioWidget {
|
|
color: Box::new(Blue::_600),
|
|
checked: false,
|
|
attrs: HashMap::new(),
|
|
title: title.to_string(),
|
|
custom: false,
|
|
}
|
|
.name(group_name)
|
|
}
|
|
|
|
pub struct RadioWidget {
|
|
color: Box<dyn UIColor>,
|
|
checked: bool,
|
|
attrs: HashMap<String, String>,
|
|
title: String,
|
|
custom: bool,
|
|
}
|
|
|
|
impl RadioWidget {
|
|
pub fn color<C: UIColor + 'static>(mut self, color: C) -> Self {
|
|
self.color = Box::new(color);
|
|
self
|
|
}
|
|
|
|
pub fn checked(mut self, checked: bool) -> Self {
|
|
self.checked = checked;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl AttrExtendable for RadioWidget {
|
|
fn add_attr(mut self, key: &str, val: &str) -> Self {
|
|
self.attrs.insert(key.to_string(), val.to_string());
|
|
self
|
|
}
|
|
}
|
|
|
|
impl InputAttr for RadioWidget {
|
|
fn name(self, name: &str) -> Self {
|
|
self.add_attr("name", name)
|
|
}
|
|
}
|
|
|
|
impl Render for RadioWidget {
|
|
fn render(&self) -> Markup {
|
|
self.render_with_class("")
|
|
}
|
|
}
|
|
|
|
impl UIWidget for RadioWidget {
|
|
fn can_inherit(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn base_class(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn extended_class(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn render_with_class(&self, _: &str) -> Markup {
|
|
let mut attrs = self.attrs.clone();
|
|
|
|
let color = self.color.color_class();
|
|
|
|
if self.custom {
|
|
attrs.insert("class".into(), "hidden peer".into());
|
|
} else {
|
|
attrs.insert("class".into(), format!("w-4 h-4 text-{color} bg-gray-100 border-gray-300 focus:ring-{color} dark:focus:ring-{color} dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"));
|
|
}
|
|
attrs.insert("type".into(), "radio".into());
|
|
|
|
let input = build_element("input", &attrs, Nothing());
|
|
|
|
let input_id = attrs.get("id").map(|x| x.as_str()).unwrap_or_default();
|
|
|
|
let label_class = if self.custom {
|
|
format!(
|
|
"inline-flex items-center justify-between w-full p-5 text-gray-500 bg-white border border-gray-200 rounded-lg cursor-pointer dark:hover:text-gray-300 dark:border-gray-700 dark:peer-checked:text-{color} peer-checked:border-{color} dark:peer-checked:border-{color} peer-checked:text-{color} hover:text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:bg-gray-800 dark:hover:bg-gray-700"
|
|
)
|
|
} else {
|
|
"ms-2 text-sm font-medium text-gray-900 dark:text-gray-300".to_string()
|
|
};
|
|
|
|
html! {
|
|
div class="flex items-center me-4" {
|
|
(input)
|
|
|
|
label for=(input_id) class=(label_class) {
|
|
(PreEscaped(self.title.clone()))
|
|
};
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
pub fn Checkbox(title: &str) -> CheckboxWidget {
|
|
CheckboxWidget {
|
|
color: Box::new(Blue::_600),
|
|
checked: false,
|
|
attrs: HashMap::new(),
|
|
title: title.to_string(),
|
|
}
|
|
}
|
|
|
|
pub struct CheckboxWidget {
|
|
color: Box<dyn UIColor>,
|
|
checked: bool,
|
|
attrs: HashMap<String, String>,
|
|
title: String,
|
|
}
|
|
|
|
impl CheckboxWidget {
|
|
pub fn color<C: UIColor + 'static>(mut self, color: C) -> Self {
|
|
self.color = Box::new(color);
|
|
self
|
|
}
|
|
|
|
pub fn checked(mut self, checked: bool) -> Self {
|
|
self.checked = checked;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl AttrExtendable for CheckboxWidget {
|
|
fn add_attr(mut self, key: &str, val: &str) -> Self {
|
|
self.attrs.insert(key.to_string(), val.to_string());
|
|
self
|
|
}
|
|
}
|
|
|
|
impl InputAttr for CheckboxWidget {}
|
|
|
|
impl Render for CheckboxWidget {
|
|
fn render(&self) -> Markup {
|
|
self.render_with_class("")
|
|
}
|
|
}
|
|
|
|
impl UIWidget for CheckboxWidget {
|
|
fn can_inherit(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn base_class(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn extended_class(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn render_with_class(&self, _: &str) -> Markup {
|
|
let color = self.color.color_class();
|
|
let class = format!(
|
|
"w-4 h-4 text-{color} bg-gray-100 border-gray-300 rounded-sm focus:ring-{color} dark:focus:ring-{color} dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
|
);
|
|
|
|
let mut attrs = self.attrs.clone();
|
|
|
|
attrs.insert("class".into(), class);
|
|
attrs.insert("type".into(), "checkbox".into());
|
|
|
|
if self.checked {
|
|
attrs.insert("checked".into(), "".into());
|
|
}
|
|
|
|
let input = build_element("input", &attrs, Nothing());
|
|
|
|
let input_id = attrs.get("id").map(|x| x.as_str()).unwrap_or_default();
|
|
|
|
html! {
|
|
div class="flex items-center me-4" {
|
|
(input)
|
|
label for=(input_id) class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300" { (self.title) };
|
|
};
|
|
}
|
|
}
|
|
}
|