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, } 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 { 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 { 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, inner: PreEscaped, ) -> PreEscaped { 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, ">").unwrap(); } else { write!(&mut ret, ">").unwrap(); } } else { write!(&mut ret, ">{}", 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, checked: bool, attrs: HashMap, title: String, } impl ToggleWidget { pub fn color(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 { Vec::new() } fn extended_class(&self) -> Vec { 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(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, checked: bool, attrs: HashMap, title: String, custom: bool, } impl RadioWidget { pub fn color(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 { Vec::new() } fn extended_class(&self) -> Vec { 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, checked: bool, attrs: HashMap, title: String, } impl CheckboxWidget { pub fn color(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 { Vec::new() } fn extended_class(&self) -> Vec { 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) }; }; } } }