This commit is contained in:
JMARyA 2025-01-15 18:28:59 +01:00
parent 8208fa8899
commit ed739d792f
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
35 changed files with 1675 additions and 447 deletions

View file

@ -0,0 +1,73 @@
use maud::{Markup, Render, html};
use crate::ui::UIWidget;
pub struct Aspect {
kind: u8,
inner: Box<dyn UIWidget>,
}
impl Aspect {
pub fn auto<T: UIWidget + 'static>(inner: T) -> Self {
Self {
kind: 0,
inner: Box::new(inner),
}
}
pub fn square<T: UIWidget + 'static>(inner: T) -> Self {
Self {
kind: 1,
inner: Box::new(inner),
}
}
pub fn video<T: UIWidget + 'static>(inner: T) -> Self {
Self {
kind: 2,
inner: Box::new(inner),
}
}
}
impl Render for Aspect {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for Aspect {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = match self.kind {
0 => "aspect-auto",
1 => "aspect-square",
2 => "aspect-video",
_ => "",
};
vec![class.to_string()]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.inner.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.inner.as_ref().can_inherit() {
html! {
(self.inner.as_ref().render_with_class(&format!("{class} {}", self.base_class().join(" "))))
}
} else {
html! {
div class=(format!("{class} {}", self.base_class().join(" "))) {
(self.inner.as_ref())
}
}
}
}
}

View file

@ -0,0 +1,49 @@
use maud::{Markup, Render, html};
use crate::ui::{UIWidget, color::UIColor};
#[allow(non_snake_case)]
pub fn Background<T: UIWidget + 'static, C: UIColor + 'static>(
color: C,
inner: T,
) -> BackgroundWidget {
BackgroundWidget(Box::new(inner), Box::new(color))
}
pub struct BackgroundWidget(Box<dyn UIWidget>, Box<dyn UIColor>);
impl Render for BackgroundWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for BackgroundWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![format!("bg-{}", self.1.color_class())]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() {
self.0
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.0.as_ref())
}
}
}
}
}

View file

@ -0,0 +1,47 @@
use maud::{Markup, Render, html};
use crate::ui::UIWidget;
#[allow(non_snake_case)]
/// A component for fixing an element's width to the current breakpoint.
pub fn Container<T: UIWidget + 'static>(inner: T) -> ContainerWidget {
ContainerWidget(Box::new(inner))
}
pub struct ContainerWidget(Box<dyn UIWidget>);
impl Render for ContainerWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for ContainerWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec!["container".to_string()]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() {
self.0
.as_ref()
.render_with_class(&format!("container {class}"))
} else {
html! {
div class=(format!("container {class}")) {
(self.0.as_ref())
}
}
}
}
}

108
src/ui/primitives/div.rs Normal file
View file

@ -0,0 +1,108 @@
use std::collections::HashMap;
use maud::{Markup, PreEscaped, Render, html};
use crate::ui::{AttrExtendable, UIWidget, htmx::HTMXAttributes};
#[allow(non_snake_case)]
/// `<div>` element
///
/// Useful for grouping values together
pub fn Div() -> DivWidget {
DivWidget(Vec::new(), false, HashMap::new())
}
pub struct DivWidget(Vec<Box<dyn UIWidget>>, bool, HashMap<String, String>);
impl AttrExtendable for DivWidget {
fn add_attr(mut self, key: &str, val: &str) -> Self {
self.2.insert(key.to_string(), val.to_string());
self
}
fn id(self, id: &str) -> Self {
self.add_attr("id", id)
}
}
impl DivWidget {
/// Add an element to the `<div>`
pub fn add<T: UIWidget + 'static>(mut self, element: T) -> Self {
self.0.push(Box::new(element));
self
}
/// Add an optional element to the `<div>`
///
/// # Example
///
/// ```ignore
/// use based::ui::basic::*;
///
/// let div = Div().add_some(Some("hello"), |value| Text(value));
/// ```
pub fn add_some<T: UIWidget + 'static, X, U: Fn(&X) -> T>(
mut self,
option: Option<&X>,
then: U,
) -> Self {
if let Some(val) = option {
self.0.push(Box::new(then(val)));
}
self
}
/// Extract the `<div>`s innerHTML
///
/// This will render `<content>` instead of `<div> <content> </div>`
pub fn vanish(mut self) -> Self {
self.1 = true;
self
}
}
impl Render for DivWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for DivWidget {
fn can_inherit(&self) -> bool {
false
}
fn base_class(&self) -> Vec<String> {
vec![]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
for e in &self.0 {
c.extend_from_slice(&e.extended_class());
}
c
}
fn render_with_class(&self, _: &str) -> Markup {
let inner = html! {
@for e in &self.0 {
(e.as_ref())
}
};
if self.1 {
inner
} else {
let attrs = self
.2
.iter()
.map(|(k, v)| format!("{k}='{v}'"))
.collect::<Vec<_>>()
.join(" ");
PreEscaped(format!("<div {attrs}> {} </a>", inner.0))
}
}
}
impl HTMXAttributes for DivWidget {}

85
src/ui/primitives/flex.rs Normal file
View file

@ -0,0 +1,85 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
#[allow(non_snake_case)]
pub fn Flex<T: UIWidget + 'static>(inner: T) -> FlexWidget {
FlexWidget(Box::new(inner), vec![], false)
}
pub enum Justify {
Center,
Between,
}
pub struct FlexWidget(Box<dyn UIWidget>, Vec<String>, bool);
impl Render for FlexWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl FlexWidget {
pub fn full_center(mut self) -> Self {
self.1.push("items-center".to_owned());
self.1.push("justify-center".to_owned());
self
}
pub fn group(mut self) -> Self {
self.2 = true;
self
}
pub fn justify(mut self, value: Justify) -> Self {
let class = match value {
Justify::Center => "justify-center".to_owned(),
Justify::Between => "justify-between".to_owned(),
};
self.1.push(class);
self
}
pub fn items_center(mut self) -> Self {
self.1.push("items-center".to_owned());
self
}
pub fn gap(mut self, amount: u32) -> Self {
self.1.push(format!("gap-{amount}"));
self
}
}
impl UIWidget for FlexWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let mut res = vec!["flex".to_string()];
res.extend_from_slice(&self.1);
res
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() && !self.2 {
self.0
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.0.as_ref())
}
}
}
}
}

View file

@ -0,0 +1,40 @@
use maud::{Markup, Render, html};
use crate::ui::UIWidget;
#[allow(non_snake_case)]
pub fn Header<T: UIWidget + 'static>(inner: T) -> HeaderWidget {
HeaderWidget(Box::new(inner))
}
pub struct HeaderWidget(Box<dyn UIWidget>);
impl Render for HeaderWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for HeaderWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
html! {
header class=(class) {
(self.0.as_ref())
}
}
}
}

View file

@ -0,0 +1,48 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
#[allow(non_snake_case)]
pub fn Image(src: &str) -> ImageWidget {
ImageWidget {
src: src.to_owned(),
alt: String::new(),
}
}
pub struct ImageWidget {
src: String,
alt: String,
}
impl Render for ImageWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl ImageWidget {
pub fn alt(mut self, alt: &str) -> Self {
self.alt = alt.to_owned();
self
}
}
impl UIWidget for ImageWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![]
}
fn extended_class(&self) -> Vec<String> {
self.base_class()
}
fn render_with_class(&self, class: &str) -> Markup {
html! {
img src=(self.src) alt=(self.alt) class=(class) {};
}
}
}

View file

@ -0,0 +1 @@
// TODO : Implement input types

77
src/ui/primitives/link.rs Normal file
View file

@ -0,0 +1,77 @@
use std::collections::HashMap;
use maud::{Markup, PreEscaped, Render};
use crate::ui::{
AttrExtendable, UIWidget,
htmx::{HTMXAttributes, Selector, SwapStrategy},
};
#[allow(non_snake_case)]
/// A component for fixing an element's width to the current breakpoint.
pub fn Link<T: UIWidget + 'static>(reference: &str, inner: T) -> LinkWidget {
LinkWidget(Box::new(inner), reference.to_owned(), HashMap::new())
}
pub struct LinkWidget(Box<dyn UIWidget>, String, HashMap<String, String>);
impl AttrExtendable for LinkWidget {
fn add_attr(mut self, key: &str, val: &str) -> Self {
self.2.insert(key.to_string(), val.to_string());
self
}
fn id(self, id: &str) -> Self {
self.add_attr("id", id)
}
}
impl HTMXAttributes for LinkWidget {}
impl Render for LinkWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for LinkWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
let attrs = self
.2
.iter()
.map(|(k, v)| format!("{k}='{v}'"))
.collect::<Vec<_>>()
.join(" ");
PreEscaped(format!(
"<a href='{}' class='{class}' {attrs}> {} </a>",
self.1,
self.0.render().0
))
}
}
impl LinkWidget {
/// Enable HTMX link capabilities
pub fn use_htmx(self) -> Self {
let url = self.1.clone();
self.hx_get(&url)
.hx_target(Selector::Query("#main_content".to_string()))
.hx_push_url()
.hx_swap(SwapStrategy::innerHTML)
}
}

108
src/ui/primitives/mod.rs Normal file
View file

@ -0,0 +1,108 @@
use maud::{PreEscaped, html};
pub mod aspect;
pub mod background;
pub mod container;
pub mod div;
pub mod flex;
pub mod header;
pub mod image;
pub mod input;
pub mod link;
pub mod padding;
pub mod rounded;
pub mod shadow;
pub mod sized;
pub mod space;
pub mod text;
pub mod width;
#[allow(non_snake_case)]
pub fn Nothing() -> PreEscaped<String> {
html! {}
}
/// Generates a `<script>` element containing the provided JavaScript code.
///
/// This function wraps the provided JavaScript code in a `<script>` tag,
/// allowing for easy inclusion of custom scripts in the rendered HTML.
///
/// # Arguments
/// * `script` - The JavaScript code to include.
///
/// # Returns
/// A `PreEscaped<String>` containing the rendered `<script>` element.
#[must_use]
pub fn script(script: &str) -> PreEscaped<String> {
html!(
script {
(PreEscaped(script))
};
)
}
pub enum Size {
None,
Small,
Regular,
Medium,
Large,
XL,
_2XL,
_3XL,
Full,
}
impl Size {
pub fn to_string(&self) -> &str {
match self {
Self::None => "none",
Self::Small => "sm",
Self::Regular => "",
Self::Medium => "md",
Self::Large => "lg",
Self::XL => "xl",
Self::_2XL => "2xl",
Self::_3XL => "3xl",
Self::Full => "full",
}
}
}
pub enum Side {
Start,
End,
Top,
Right,
Bottom,
Left,
StartStart,
StartEnd,
EndEnd,
EndStart,
TopLeft,
TopRight,
BottomRight,
BottomLeft,
}
impl Side {
pub fn to_string(&self) -> &str {
match self {
Side::Start => "s",
Side::End => "e",
Side::Top => "t",
Side::Right => "r",
Side::Bottom => "b",
Side::Left => "l",
Side::StartStart => "ss",
Side::StartEnd => "se",
Side::EndEnd => "ee",
Side::EndStart => "es",
Side::TopLeft => "tl",
Side::TopRight => "tr",
Side::BottomRight => "br",
Side::BottomLeft => "bl",
}
}
}

View file

@ -0,0 +1,90 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
pub struct PaddingInfo {
pub right: Option<u32>,
}
#[allow(non_snake_case)]
pub fn Padding<T: UIWidget + 'static>(inner: T) -> PaddingWidget {
PaddingWidget {
inner: Box::new(inner),
right: None,
y: None,
x: None,
}
}
pub struct PaddingWidget {
pub inner: Box<dyn UIWidget>,
pub right: Option<u32>,
pub y: Option<u32>,
pub x: Option<u32>,
}
impl PaddingWidget {
pub fn right(mut self, right: u32) -> Self {
self.right = Some(right);
self
}
pub fn y(mut self, y: u32) -> Self {
self.y = Some(y);
self
}
pub fn x(mut self, x: u32) -> Self {
self.x = Some(x);
self
}
}
impl Render for PaddingWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for PaddingWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let mut our_class = Vec::new();
if let Some(r) = self.right {
our_class.push(format!("pr-{r}"));
}
if let Some(y) = self.y {
our_class.push(format!("py-{y}"));
}
if let Some(x) = self.x {
our_class.push(format!("px-{x}"));
}
our_class
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.inner.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.inner.as_ref().can_inherit() {
self.inner
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.inner.as_ref())
}
}
}
}
}

View file

@ -0,0 +1,68 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
use super::{Side, Size};
#[allow(non_snake_case)]
pub fn Rounded<T: UIWidget + 'static>(inner: T) -> RoundedWidget {
RoundedWidget(Box::new(inner), None, None)
}
pub struct RoundedWidget(Box<dyn UIWidget>, Option<Size>, Option<Side>);
impl RoundedWidget {
pub fn size(mut self, size: Size) -> Self {
self.1 = Some(size);
self
}
pub fn side(mut self, side: Side) -> Self {
self.2 = Some(side);
self
}
}
impl Render for RoundedWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for RoundedWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
if let Some(side) = &self.2 {
if let Some(size) = &self.1 {
return vec![format!("rounded-{}-{}", side.to_string(), size.to_string())];
}
} else {
if let Some(size) = &self.1 {
return vec![format!("rounded-{}", size.to_string())];
}
}
vec!["rounded".to_owned()]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() {
self.0
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.0.as_ref())
}
}
}
}
}

View file

@ -0,0 +1,78 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
pub struct Shadow(Box<dyn UIWidget>, String);
impl Shadow {
pub fn medium<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "md".to_owned())
}
pub fn small<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "sm".to_owned())
}
pub fn regular<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), String::new())
}
pub fn large<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "lg".to_owned())
}
pub fn none<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "none".to_owned())
}
pub fn xl<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "xl".to_owned())
}
pub fn _2xl<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "2xl".to_owned())
}
pub fn inner<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "inner".to_owned())
}
}
impl Render for Shadow {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for Shadow {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
if self.1.is_empty() {
vec!["shadow".to_string()]
} else {
vec![format!("shadow-{}", self.1)]
}
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() {
self.0
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.0.as_ref())
}
}
}
}
}

View file

@ -0,0 +1,45 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
#[allow(non_snake_case)]
pub fn Sized<T: UIWidget + 'static>(height: u32, width: u32, inner: T) -> SizedWidget {
SizedWidget(Box::new(inner), height, width)
}
pub struct SizedWidget(Box<dyn UIWidget>, u32, u32);
impl Render for SizedWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for SizedWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![format!("h-{}", self.1), format!("w-{}", self.2)]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() {
self.0
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.0.as_ref())
}
}
}
}
}

151
src/ui/primitives/space.rs Normal file
View file

@ -0,0 +1,151 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
#[allow(non_snake_case)]
/// Controlling the space between child elements.
pub fn SpaceBetween<T: UIWidget + 'static>(inner: T) -> SpaceBetweenWidget {
SpaceBetweenWidget(Box::new(inner), None, None)
}
pub struct SpaceBetweenWidget(Box<dyn UIWidget>, Option<ScreenValue>, Option<ScreenValue>);
impl Render for SpaceBetweenWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl SpaceBetweenWidget {
pub fn x(mut self, x: ScreenValue) -> Self {
self.1 = Some(x);
self
}
pub fn y(mut self, y: ScreenValue) -> Self {
self.2 = Some(y);
self
}
}
impl UIWidget for SpaceBetweenWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let mut ret = Vec::new();
if let Some(x) = &self.1 {
ret.push(format!("space-x-{}", x.to_string()));
}
if let Some(y) = &self.2 {
ret.push(format!("space-y-{}", y.to_string()));
}
ret
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() {
self.0
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.0.as_ref())
}
}
}
}
}
#[allow(non_camel_case_types)]
pub enum ScreenValue {
_0,
_0p5,
_1,
_1p5,
_2,
_2p5,
_3,
_3p5,
_4,
_5,
_6,
_7,
_8,
_9,
_10,
_11,
_12,
_14,
_16,
_20,
_24,
_28,
_32,
_36,
_40,
_44,
_48,
_52,
_56,
_60,
_64,
_72,
_80,
_90,
px,
reverse,
}
impl ScreenValue {
pub fn to_string(&self) -> &str {
match self {
ScreenValue::_0 => "0",
ScreenValue::_0p5 => "0.5",
ScreenValue::_1 => "1",
ScreenValue::_1p5 => "1.5",
ScreenValue::_2 => "2",
ScreenValue::_2p5 => "2.5",
ScreenValue::_3 => "3",
ScreenValue::_3p5 => "3.5",
ScreenValue::_4 => "4",
ScreenValue::_5 => "5",
ScreenValue::_6 => "6",
ScreenValue::_7 => "7",
ScreenValue::_8 => "8",
ScreenValue::_9 => "9",
ScreenValue::_10 => "10",
ScreenValue::_11 => "11",
ScreenValue::_12 => "12",
ScreenValue::_14 => "14",
ScreenValue::_16 => "16",
ScreenValue::_20 => "20",
ScreenValue::_24 => "24",
ScreenValue::_28 => "28",
ScreenValue::_32 => "32",
ScreenValue::_36 => "36",
ScreenValue::_40 => "40",
ScreenValue::_44 => "44",
ScreenValue::_48 => "48",
ScreenValue::_52 => "52",
ScreenValue::_56 => "56",
ScreenValue::_60 => "60",
ScreenValue::_64 => "64",
ScreenValue::_72 => "72",
ScreenValue::_80 => "80",
ScreenValue::_90 => "90",
ScreenValue::px => "px",
ScreenValue::reverse => "reverse",
}
}
}

163
src/ui/primitives/text.rs Normal file
View file

@ -0,0 +1,163 @@
use crate::ui::{UIWidget, color::UIColor};
use maud::{Markup, Render, html};
#[allow(non_snake_case)]
/// Text UI Widget
pub fn Text(txt: &str) -> TextWidget {
TextWidget {
inner: None,
txt: txt.to_string(),
font: String::new(),
color: String::new(),
size: String::new(),
span: false,
}
}
#[allow(non_snake_case)]
/// HTML `<p>` Paragraph
pub fn Paragraph<T: UIWidget + 'static>(inner: T) -> TextWidget {
TextWidget {
inner: Some(Box::new(inner)),
font: String::new(),
color: String::new(),
txt: String::new(),
size: String::new(),
span: false,
}
}
#[allow(non_snake_case)]
/// `<span>` element
pub fn Span(txt: &str) -> TextWidget {
TextWidget {
inner: None,
txt: txt.to_string(),
font: String::new(),
color: String::new(),
size: String::new(),
span: true,
}
}
pub struct TextWidget {
inner: Option<Box<dyn UIWidget>>,
txt: String,
font: String,
color: String,
size: String,
span: bool,
}
impl TextWidget {
/// Turn `Text` semibold.
///
/// Adds the class `font-semibold`
pub fn semibold(mut self) -> Self {
self.font = "font-semibold".to_owned();
self
}
/// Turn `Text` bold.
///
/// Adds the class `font-bold`
pub fn bold(mut self) -> Self {
self.font = "font-bold".to_owned();
self
}
/// Turn `Text` medium.
///
/// Adds the class `font-medium`
pub fn medium(mut self) -> Self {
self.font = "font-medium".to_owned();
self
}
/// Turn `Text` size to 2XL.
///
/// Adds the class `text-2xl`
pub fn _2xl(mut self) -> Self {
self.size = "text-2xl".to_owned();
self
}
/// Turn `Text` size to xl.
///
/// Adds the class `text-xl`
pub fn xl(mut self) -> Self {
self.size = "text-xl".to_owned();
self
}
/// Turn `Text` size to small.
///
/// Adds the class `text-sm`
pub fn sm(mut self) -> Self {
self.size = "text-sm".to_owned();
self
}
pub fn color<T: UIColor>(mut self, color: T) -> Self {
self.color = format!("text-{}", color.color_class());
self
}
pub fn black(mut self) -> Self {
self.color = "text-black".to_owned();
self
}
pub fn white(mut self) -> Self {
self.color = "text-white".to_owned();
self
}
}
impl Render for TextWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for TextWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![self.color.clone(), self.font.clone(), self.size.clone()]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
if let Some(inner) = &self.inner {
c.extend_from_slice(&inner.extended_class());
}
c
}
fn render_with_class(&self, class: &str) -> Markup {
if let Some(inner) = &self.inner {
if self.span {
html! {
span class=(format!("{} {}", class, self.base_class().join(" "))) { (inner) }
}
} else {
html! {
p class=(format!("{} {}", class, self.base_class().join(" "))) { (inner) }
}
}
} else {
if self.span {
html! {
span class=(format!("{} {}", class, self.base_class().join(" "))) { (self.txt) }
}
} else {
html! {
p class=(format!("{} {}", class, self.base_class().join(" "))) { (self.txt) }
}
}
}
}
}

View file

@ -0,0 +1,45 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
#[allow(non_snake_case)]
pub fn FitWidth<T: UIWidget + 'static>(inner: T) -> FitWidthWidget {
FitWidthWidget(Box::new(inner))
}
pub struct FitWidthWidget(Box<dyn UIWidget>);
impl Render for FitWidthWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for FitWidthWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec!["max-w-fit".to_string()]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() {
self.0
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.0.as_ref())
}
}
}
}
}