WIP
This commit is contained in:
parent
5ce50b76f5
commit
86fc33916e
24 changed files with 3920 additions and 104 deletions
394
src/ui/primitives/input/toggle.rs
Normal file
394
src/ui/primitives/input/toggle.rs
Normal file
|
@ -0,0 +1,394 @@
|
|||
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) };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue