This commit is contained in:
JMARyA 2025-01-15 18:53:55 +01:00
parent ed739d792f
commit e9a9dad037
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
19 changed files with 278 additions and 177 deletions

View file

@ -32,24 +32,27 @@ impl<T, E: std::fmt::Debug> LogAndIgnore for Result<T, E> {
}
pub trait LogNoneAndPass {
#[must_use]
fn log_warn_none_and_pass(self, msg: impl Fn() -> String) -> Self;
#[must_use]
fn log_err_none_and_pass(self, msg: impl Fn() -> String) -> Self;
}
impl<T> LogNoneAndPass for Option<T> {
fn log_warn_none_and_pass(self, msg: impl Fn() -> String) -> Option<T> {
if matches!(self, None) {
if self.is_none() {
log::warn!("{}", msg());
}
return self;
self
}
fn log_err_none_and_pass(self, msg: impl Fn() -> String) -> Option<T> {
if matches!(self, None) {
if self.is_none() {
log::error!("{}", msg());
}
return self;
self
}
}

View file

@ -1,10 +1,14 @@
/// UI Color
pub trait UIColor {
#[must_use]
fn color_class(&self) -> &str;
}
pub trait ColorCircle {
#[must_use]
fn previous(&self) -> Self;
#[must_use]
fn next(&self) -> Self;
}
@ -119,11 +123,11 @@ pub enum Colors {
impl UIColor for Colors {
fn color_class(&self) -> &str {
match self {
Colors::Inherit => "inherit",
Colors::Current => "current",
Colors::Transparent => "transparent",
Colors::Black => "black",
Colors::White => "white",
Self::Inherit => "inherit",
Self::Current => "current",
Self::Transparent => "transparent",
Self::Black => "black",
Self::White => "white",
}
}
}

View file

@ -5,6 +5,7 @@ use crate::auth::User;
use crate::ui::{UIWidget, prelude::*};
#[allow(non_snake_case)]
#[must_use]
pub fn AppBar(name: &str, user: Option<User>) -> AppBarWidget {
AppBarWidget {
name: name.to_owned(),
@ -44,25 +45,25 @@ impl UIWidget for AppBarWidget {
Flex(
Div()
.vanish()
.add(
.push(
SpaceBetween(
Flex(Link(
"/",
Div()
.vanish()
.add(Sized(
.push(Sized(
10,
10,
Rounded(Image("/favicon").alt("Logo"))
.size(Size::Medium),
))
.add(Span(&self.name).semibold().xl().white()),
.push(Span(&self.name).semibold().xl().white()),
))
.items_center(),
)
.x(ScreenValue::_2),
)
.add_some(self.user.as_ref(), |user| Text(&user.username).white()),
.push_some(self.user.as_ref(), |user| Text(&user.username).white()),
)
.group()
.justify(Justify::Between)

View file

@ -13,67 +13,80 @@ use super::AttrExtendable;
pub trait HTMXAttributes: AttrExtendable + std::marker::Sized {
/// Issues a `GET` request to the specified URL
#[must_use]
fn hx_get(self, url: &str) -> Self {
self.add_attr("hx-get", url)
}
/// Issues a `POST` request to the specified URL
#[must_use]
fn hx_post(self, url: &str) -> Self {
self.add_attr("hx-post", url)
}
/// Push a URL into the browser location bar to create history
#[must_use]
fn hx_push_url(self) -> Self {
self.add_attr("hx-push-url", "true")
}
/// Select content to swap in from a response
#[must_use]
fn hx_select(self, element: &str) -> Self {
self.add_attr("hx-select", element)
}
/// Select content to swap in from a response, somewhere other than the target (out of band).
/// Select `element` from response and replace `element` in the DOM.
#[must_use]
fn hx_select_oob(self, element: &str) -> Self {
self.add_attr("hx-select-oob", element)
}
/// The hx-boost attribute allows you to “boost” normal anchors and form tags to use AJAX instead.
#[must_use]
fn hx_boost(self) -> Self {
self.add_attr("hx-boost", "true")
}
/// The hx-confirm attribute allows you to confirm an action before issuing a request.
#[must_use]
fn hx_confirm(self, msg: &str) -> Self {
self.add_attr("hx-confirm", msg)
}
/// The hx-delete attribute will cause an element to issue a `DELETE` request to the specified URL and swap the HTML into the DOM using a swap strategy.
#[must_use]
fn hx_delete(self, url: &str) -> Self {
self.add_attr("hx-delete", url)
}
/// The hx-disable attribute will disable htmx processing for a given element and all its children.
#[must_use]
fn hx_disable(self) -> Self {
self.add_attr("hx-disable", "")
}
/// The hx-disabled-elt attribute allows you to specify elements that will have the disabled attribute added to them for the duration of the request.
#[must_use]
fn hx_disabled_elt(self, element: Selector) -> Self {
self.add_attr("hx-disabled-elt", &element.to_string())
self.add_attr("hx-disabled-elt", &element.to_value())
}
/// The hx-disinherit attribute allows you to control automatic attribute inheritance.
#[must_use]
fn hx_disinherit(self, attrs: &str) -> Self {
self.add_attr("hx-disinherit", attrs)
}
/// The hx-encoding attribute allows you to switch the request encoding from the usual `application/x-www-form-urlencoded` encoding to `multipart/form-data`, usually to support file uploads in an ajax request.
#[must_use]
fn hx_encoding(self) -> Self {
self.add_attr("hx-encoding", "multipart/form-data")
}
/// The hx-headers attribute allows you to add to the headers that will be submitted with an AJAX request.
#[must_use]
fn hx_headers(self, headers: HashMap<String, String>) -> Self {
let json = serde_json::to_value(headers).unwrap();
let json_str = serde_json::to_string(&json).unwrap();
@ -81,6 +94,7 @@ pub trait HTMXAttributes: AttrExtendable + std::marker::Sized {
}
/// The hx-vals attribute allows you to add to the parameters that will be submitted with an AJAX request.
#[must_use]
fn hx_vals(self, vals: HashMap<String, String>) -> Self {
let json = serde_json::to_value(vals).unwrap();
let json_str = serde_json::to_string(&json).unwrap();
@ -90,25 +104,29 @@ pub trait HTMXAttributes: AttrExtendable + std::marker::Sized {
/// Set the hx-history attribute to false on any element in the current document, or any html fragment loaded into the current document by htmx, to prevent sensitive data being saved to the localStorage cache when htmx takes a snapshot of the page state.
///
/// History navigation will work as expected, but on restoration the URL will be requested from the server instead of the history cache.
#[must_use]
fn hx_history(self) -> Self {
self.add_attr("hx-history", "false")
}
/// The hx-history-elt attribute allows you to specify the element that will be used to snapshot and restore page state during navigation. By default, the body tag is used. This is typically good enough for most setups, but you may want to narrow it down to a child element. Just make sure that the element is always visible in your application, or htmx will not be able to restore history navigation properly.
#[must_use]
fn hx_history_elt(self) -> Self {
self.add_attr("hx-history-elt", "")
}
/// The hx-include attribute allows you to include additional element values in an AJAX request.
#[must_use]
fn hx_include(self, element: Selector) -> Self {
self.add_attr("hx-include", &element.to_string())
self.add_attr("hx-include", &element.to_value())
}
/// The hx-indicator attribute allows you to specify the element that will have the htmx-request class added to it for the duration of the request. This can be used to show spinners or progress indicators while the request is in flight.
///
/// Note: This attribute only supports CSS queries and `closest` match.
#[must_use]
fn hx_indicator(self, indicator: Selector) -> Self {
self.add_attr("hx-indicator", &indicator.to_string())
self.add_attr("hx-indicator", &indicator.to_value())
}
/// The hx-params attribute allows you to filter the parameters that will be submitted with an AJAX request.
@ -118,16 +136,19 @@ pub trait HTMXAttributes: AttrExtendable + std::marker::Sized {
/// `none` - Include no parameters
/// `not <param-list>` - Include all except the comma separated list of parameter names
/// `<param-list>` - Include all the comma separated list of parameter names
#[must_use]
fn hx_params(self, params: &str) -> Self {
self.add_attr("hx-params", params)
}
/// The hx-patch attribute will cause an element to issue a PATCH to the specified URL and swap the HTML into the DOM using a swap strategy.
#[must_use]
fn hx_patch(self, url: &str) -> Self {
self.add_attr("hx-patch", url)
}
/// The hx-put attribute will cause an element to issue a PUT to the specified URL and swap the HTML into the DOM using a swap strategy
#[must_use]
fn hx_put(self, url: &str) -> Self {
self.add_attr("hx-put", url)
}
@ -137,43 +158,51 @@ pub trait HTMXAttributes: AttrExtendable + std::marker::Sized {
/// The possible values of this attribute are:
/// `true`, which replaces the fetched URL in the browser navigation bar.
/// `false`, which disables replacing the fetched URL if it would otherwise be replaced due to inheritance.
/// A URL to be replaced into the location bar. This may be relative or absolute, as per history.replaceState().
/// A URL to be replaced into the location bar. This may be relative or absolute, as per `history.replaceState()`.
#[must_use]
fn hx_replace_url(self, value: &str) -> Self {
self.add_attr("hx-replace-url", value)
}
/// The hx-validate attribute will cause an element to validate itself by way of the HTML5 Validation API before it submits a request.
#[must_use]
fn hx_validate(self) -> Self {
self.add_attr("hx-validte", "true")
}
/// The hx-preserve attribute allows you to keep an element unchanged during HTML replacement. Elements with hx-preserve set are preserved by id when htmx updates any ancestor element. You must set an unchanging id on elements for hx-preserve to work. The response requires an element with the same id, but its type and other attributes are ignored.
#[must_use]
fn hx_preserve(self) -> Self {
self.add_attr("hx-preserve", "")
}
/// The hx-prompt attribute allows you to show a prompt before issuing a request. The value of the prompt will be included in the request in the HX-Prompt header.
#[must_use]
fn hx_prompt(self, msg: &str) -> Self {
self.add_attr("hx-prompt", msg)
}
/// The hx-swap attribute allows you to specify how the response will be swapped in relative to the target of an AJAX request.
#[must_use]
fn hx_swap<T: Into<ModifiedSwapStrategy>>(self, swap: T) -> Self {
self.add_attr("hx-swap", &swap.into().to_string())
self.add_attr("hx-swap", &swap.into().to_value())
}
/// The hx-swap-oob attribute allows you to specify that some content in a response should be swapped into the DOM somewhere other than the target, that is “Out of Band”. This allows you to piggy back updates to other element updates on a response.
#[must_use]
fn hx_swap_oob(self) -> Self {
self.add_attr("hx-swap-oob", "true")
}
/// The hx-target attribute allows you to target a different element for swapping than the one issuing the AJAX request.
#[must_use]
fn hx_target(self, element: Selector) -> Self {
self.add_attr("hx-target", &element.to_string())
self.add_attr("hx-target", &element.to_value())
}
/// The hx-trigger attribute allows you to specify what triggers an AJAX request.
#[must_use]
fn hx_trigger<T: Into<Trigger>>(self, trigger: T) -> Self {
self.add_attr("hx-trigger", &trigger.into().to_string())
self.add_attr("hx-trigger", &trigger.into().to_value())
}
}

View file

@ -18,16 +18,17 @@ pub enum Selector {
}
impl Selector {
pub fn to_string(&self) -> String {
#[must_use]
pub fn to_value(&self) -> String {
match self {
Selector::Query(query) => query.clone(),
Selector::This => "this".to_owned(),
Selector::Closest(css) => format!("closest {css}"),
Selector::Find(css) => format!("find {css}"),
Selector::Next => "next".to_owned(),
Selector::NextQuery(css) => format!("next {css}"),
Selector::Previous => "previous".to_owned(),
Selector::PreviousQuery(css) => format!("previous {css}"),
Self::Query(query) => query.clone(),
Self::This => "this".to_owned(),
Self::Closest(css) => format!("closest {css}"),
Self::Find(css) => format!("find {css}"),
Self::Next => "next".to_owned(),
Self::NextQuery(css) => format!("next {css}"),
Self::Previous => "previous".to_owned(),
Self::PreviousQuery(css) => format!("previous {css}"),
}
}
}

View file

@ -27,57 +27,65 @@ impl Default for SwapStrategy {
}
impl SwapStrategy {
pub fn to_string(&self) -> &str {
#[must_use]
pub const fn to_value(&self) -> &str {
match self {
SwapStrategy::innerHTML => "innerHTML",
SwapStrategy::outerHTML => "outerHTML",
SwapStrategy::textContent => "textContent",
SwapStrategy::beforebegin => "beforebegin",
SwapStrategy::afterbegin => "afterbegin",
SwapStrategy::beforeend => "beforeend",
SwapStrategy::afterend => "afterend",
SwapStrategy::delete => "delete",
SwapStrategy::none => "none",
Self::innerHTML => "innerHTML",
Self::outerHTML => "outerHTML",
Self::textContent => "textContent",
Self::beforebegin => "beforebegin",
Self::afterbegin => "afterbegin",
Self::beforeend => "beforeend",
Self::afterend => "afterend",
Self::delete => "delete",
Self::none => "none",
}
}
/// If you want to use the new View Transitions API when a swap occurs, you can use the transition:true option for your swap.
#[must_use]
pub fn transition(self) -> ModifiedSwapStrategy {
let modifier = "transition".to_owned();
ModifiedSwapStrategy::new(self, modifier)
}
/// You can modify the amount of time that htmx will wait after receiving a response to swap the content by including a swap modifier.
#[must_use]
pub fn swap(self, duration: &str) -> ModifiedSwapStrategy {
let modifier = format!("swap:{duration}");
ModifiedSwapStrategy::new(self, modifier)
}
/// You can modify the time between the swap and the settle logic by including a settle modifier.
#[must_use]
pub fn settle(self, duration: &str) -> ModifiedSwapStrategy {
let modifier = format!("settle:{duration}");
ModifiedSwapStrategy::new(self, modifier)
}
/// By default, htmx will update the title of the page if it finds a `<title>` tag in the response content. You can turn off this behavior.
#[must_use]
pub fn ignore_title(self) -> ModifiedSwapStrategy {
let modifier = "ignoreTitle:true";
ModifiedSwapStrategy::new(self, modifier.to_owned())
}
/// htmx preserves focus between requests for inputs that have a defined id attribute. By default htmx prevents auto-scrolling to focused inputs between requests which can be unwanted behavior on longer requests when the user has already scrolled away.
#[must_use]
pub fn focus_scroll(self, enable: bool) -> ModifiedSwapStrategy {
let modifier = format!("focus-scroll:{enable}");
ModifiedSwapStrategy::new(self, modifier)
}
/// Ensure visibility
#[must_use]
pub fn show(self, e: &str) -> ModifiedSwapStrategy {
let modifier = format!("show:{e}");
ModifiedSwapStrategy::new(self, modifier)
}
/// Scroll to this location after load
#[must_use]
pub fn scroll(self, e: &str) -> ModifiedSwapStrategy {
let modifier = format!("scroll:{e}");
ModifiedSwapStrategy::new(self, modifier)
@ -97,7 +105,7 @@ impl From<SwapStrategy> for ModifiedSwapStrategy {
impl ModifiedSwapStrategy {
fn new(strategy: SwapStrategy, modifier: String) -> Self {
ModifiedSwapStrategy {
Self {
strategy,
modifiers: vec![modifier],
}
@ -152,7 +160,7 @@ impl ModifiedSwapStrategy {
self
}
pub fn to_string(&self) -> String {
format!("{} {}", self.strategy.to_string(), self.modifiers.join(" "))
pub fn to_value(&self) -> String {
format!("{} {}", self.strategy.to_value(), self.modifiers.join(" "))
}
}

View file

@ -6,7 +6,8 @@ pub struct Event {
}
impl Event {
pub fn to_string(&self) -> String {
#[must_use]
pub fn to_value(&self) -> String {
if self.kind.starts_with("every") {
return self.kind.clone();
}
@ -15,7 +16,8 @@ impl Event {
}
/// Add a second event trigger
pub fn and(self, event: Event) -> Trigger {
#[must_use]
pub fn and(self, event: Self) -> Trigger {
Trigger {
triggers: vec![self, event],
}
@ -24,32 +26,37 @@ impl Event {
/// Periodically poll
///
/// Value can be something like `1s [someConditional]`
#[must_use]
pub fn poll(value: &str) -> Self {
Event {
Self {
modifiers: vec![],
kind: format!("every {value}"),
}
}
/// Standard events refer to web API events (e.g. `click`, `keydown`, `mouseup`, `load`).
#[allow(clippy::self_named_constructors)]
#[must_use]
pub fn event(event: &str) -> Self {
Event {
Self {
modifiers: vec![],
kind: event.to_string(),
}
}
/// triggered on load (useful for lazy-loading something)
#[must_use]
pub fn on_load() -> Self {
Event {
Self {
modifiers: vec![],
kind: "load".to_string(),
}
}
/// triggered when an element is scrolled into the viewport (also useful for lazy-loading). If you are using overflow in css like `overflow-y: scroll` you should use `intersect once` instead of `revealed`.
#[must_use]
pub fn on_revealed() -> Self {
Event {
Self {
modifiers: vec![],
kind: "revealed".to_string(),
}
@ -58,67 +65,78 @@ impl Event {
/// fires once when an element first intersects the viewport. This supports two additional options:
/// `root:<selector>` - a CSS selector of the root element for intersection
/// `threshold:<float>` - a floating point number between 0.0 and 1.0, indicating what amount of intersection to fire the event on
#[must_use]
pub fn on_intersect(value: &str) -> Self {
Event {
Self {
modifiers: vec![],
kind: format!("intersect:{value}"),
}
}
/// the event will only trigger once (e.g. the first click)
#[must_use]
pub fn once(mut self) -> Self {
self.modifiers.push("once".to_string());
self
}
/// the event will only change if the value of the element has changed.
#[must_use]
pub fn changed(mut self) -> Self {
self.modifiers.push("changed".to_string());
self
}
/// a delay will occur before an event triggers a request. If the event is seen again it will reset the delay.
#[must_use]
pub fn delay(mut self, delay: &str) -> Self {
self.modifiers.push(format!("delay:{delay}"));
self
}
/// a throttle will occur after an event triggers a request. If the event is seen again before the delay completes, it is ignored, the element will trigger at the end of the delay.
#[must_use]
pub fn throttle(mut self, delay: &str) -> Self {
self.modifiers.push(format!("throttle:{delay}"));
self
}
/// allows the event that triggers a request to come from another element in the document (e.g. listening to a key event on the body, to support hot keys)
#[must_use]
pub fn from(mut self, element: Selector) -> Self {
self.modifiers.push(format!("from:{}", element.to_string()));
self.modifiers.push(format!("from:{}", element.to_value()));
self
}
/// allows you to filter via a CSS selector on the target of the event. This can be useful when you want to listen for triggers from elements that might not be in the DOM at the point of initialization, by, for example, listening on the body, but with a target filter for a child element
#[must_use]
pub fn target(mut self, selector: &str) -> Self {
self.modifiers.push(format!("target:{selector}"));
self
}
/// if this option is included the event will not trigger any other htmx requests on parents (or on elements listening on parents)
#[must_use]
pub fn consume(mut self) -> Self {
self.modifiers.push("consume".to_string());
self
}
/// determines how events are queued if an event occurs while a request for another event is in flight.
#[must_use]
pub fn queue(mut self, opt: QueueOption) -> Self {
self.modifiers.push(format!("queue:{}", opt.to_string()));
self.modifiers.push(format!("queue:{}", opt.to_value()));
self
}
}
#[allow(non_camel_case_types)]
#[derive(Default)]
pub enum QueueOption {
/// queue the first event
first,
/// queue the last event (default)
#[default]
last,
/// queue all events (issue a request for each event)
all,
@ -127,53 +145,58 @@ pub enum QueueOption {
}
impl QueueOption {
pub fn to_string(&self) -> &str {
#[must_use]
pub const fn to_value(&self) -> &str {
match self {
QueueOption::first => "first",
QueueOption::last => "last",
QueueOption::all => "all",
QueueOption::none => "none",
Self::first => "first",
Self::last => "last",
Self::all => "all",
Self::none => "none",
}
}
}
impl Default for QueueOption {
fn default() -> Self {
QueueOption::last
}
}
pub struct Trigger {
triggers: Vec<Event>,
}
impl From<Event> for Trigger {
fn from(value: Event) -> Self {
Trigger {
Self {
triggers: vec![value],
}
}
}
impl Trigger {
pub fn new() -> Self {
#[must_use]
pub const fn new() -> Self {
Self { triggers: vec![] }
}
pub fn add(mut self, event: Event) -> Self {
#[must_use]
pub fn push(mut self, event: Event) -> Self {
self.triggers.push(event);
self
}
#[must_use]
pub fn and(self, event: Event) -> Self {
self.add(event)
self.push(event)
}
pub fn to_string(&self) -> String {
#[must_use]
pub fn to_value(&self) -> String {
self.triggers
.iter()
.map(|x| x.to_string())
.map(Event::to_value)
.collect::<Vec<_>>()
.join(", ")
}
}
impl Default for Trigger {
fn default() -> Self {
Self::new()
}
}

View file

@ -113,8 +113,10 @@ impl UIWidget for PreEscaped<String> {
/// Trait for an element which can add new `attrs`
pub trait AttrExtendable {
#[must_use]
fn add_attr(self, key: &str, val: &str) -> Self;
/// Set the `id` attribute of an element.
#[must_use]
fn id(self, id: &str) -> Self;
}

View file

@ -8,6 +8,7 @@ use crate::ui::{AttrExtendable, UIWidget, htmx::HTMXAttributes};
/// `<div>` element
///
/// Useful for grouping values together
#[must_use]
pub fn Div() -> DivWidget {
DivWidget(Vec::new(), false, HashMap::new())
}
@ -16,7 +17,7 @@ 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.2.insert(key.to_string(), val.replace('\'', "\\'"));
self
}
@ -27,7 +28,8 @@ impl AttrExtendable for DivWidget {
impl DivWidget {
/// Add an element to the `<div>`
pub fn add<T: UIWidget + 'static>(mut self, element: T) -> Self {
#[must_use]
pub fn push<T: UIWidget + 'static>(mut self, element: T) -> Self {
self.0.push(Box::new(element));
self
}
@ -39,9 +41,10 @@ impl DivWidget {
/// ```ignore
/// use based::ui::basic::*;
///
/// let div = Div().add_some(Some("hello"), |value| Text(value));
/// let div = Div().push(Some("hello"), |value| Text(value));
/// ```
pub fn add_some<T: UIWidget + 'static, X, U: Fn(&X) -> T>(
#[must_use]
pub fn push_some<T: UIWidget + 'static, X, U: Fn(&X) -> T>(
mut self,
option: Option<&X>,
then: U,
@ -55,7 +58,8 @@ impl DivWidget {
/// Extract the `<div>`s innerHTML
///
/// This will render `<content>` instead of `<div> <content> </div>`
pub fn vanish(mut self) -> Self {
#[must_use]
pub const fn vanish(mut self) -> Self {
self.1 = true;
self
}

View file

@ -20,17 +20,20 @@ impl Render for FlexWidget {
}
impl FlexWidget {
#[must_use]
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 {
#[must_use]
pub const fn group(mut self) -> Self {
self.2 = true;
self
}
#[must_use]
pub fn justify(mut self, value: Justify) -> Self {
let class = match value {
Justify::Center => "justify-center".to_owned(),
@ -41,11 +44,13 @@ impl FlexWidget {
self
}
#[must_use]
pub fn items_center(mut self) -> Self {
self.1.push("items-center".to_owned());
self
}
#[must_use]
pub fn gap(mut self, amount: u32) -> Self {
self.1.push(format!("gap-{amount}"));
self

View file

@ -2,6 +2,7 @@ use crate::ui::UIWidget;
use maud::{Markup, Render, html};
#[allow(non_snake_case)]
#[must_use]
pub fn Image(src: &str) -> ImageWidget {
ImageWidget {
src: src.to_owned(),
@ -21,8 +22,9 @@ impl Render for ImageWidget {
}
impl ImageWidget {
#[must_use]
pub fn alt(mut self, alt: &str) -> Self {
self.alt = alt.to_owned();
self.alt = alt.to_string();
self
}
}

View file

@ -17,7 +17,7 @@ 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.2.insert(key.to_string(), val.replace('\'', "\\'"));
self
}
@ -67,6 +67,7 @@ impl UIWidget for LinkWidget {
impl LinkWidget {
/// Enable HTMX link capabilities
#[must_use]
pub fn use_htmx(self) -> Self {
let url = self.1.clone();
self.hx_get(&url)

View file

@ -18,6 +18,7 @@ pub mod text;
pub mod width;
#[allow(non_snake_case)]
#[must_use]
pub fn Nothing() -> PreEscaped<String> {
html! {}
}
@ -54,7 +55,8 @@ pub enum Size {
}
impl Size {
pub fn to_string(&self) -> &str {
#[must_use]
pub const fn to_value(&self) -> &str {
match self {
Self::None => "none",
Self::Small => "sm",
@ -87,22 +89,23 @@ pub enum Side {
}
impl Side {
pub fn to_string(&self) -> &str {
#[must_use]
pub const fn to_value(&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",
Self::Start => "s",
Self::End => "e",
Self::Top => "t",
Self::Right => "r",
Self::Bottom => "b",
Self::Left => "l",
Self::StartStart => "ss",
Self::StartEnd => "se",
Self::EndEnd => "ee",
Self::EndStart => "es",
Self::TopLeft => "tl",
Self::TopRight => "tr",
Self::BottomRight => "br",
Self::BottomLeft => "bl",
}
}
}

View file

@ -23,17 +23,20 @@ pub struct PaddingWidget {
}
impl PaddingWidget {
pub fn right(mut self, right: u32) -> Self {
#[must_use]
pub const fn right(mut self, right: u32) -> Self {
self.right = Some(right);
self
}
pub fn y(mut self, y: u32) -> Self {
#[must_use]
pub const fn y(mut self, y: u32) -> Self {
self.y = Some(y);
self
}
pub fn x(mut self, x: u32) -> Self {
#[must_use]
pub const fn x(mut self, x: u32) -> Self {
self.x = Some(x);
self
}

View file

@ -11,12 +11,14 @@ pub fn Rounded<T: UIWidget + 'static>(inner: T) -> RoundedWidget {
pub struct RoundedWidget(Box<dyn UIWidget>, Option<Size>, Option<Side>);
impl RoundedWidget {
pub fn size(mut self, size: Size) -> Self {
#[must_use]
pub const fn size(mut self, size: Size) -> Self {
self.1 = Some(size);
self
}
pub fn side(mut self, side: Side) -> Self {
#[must_use]
pub const fn side(mut self, side: Side) -> Self {
self.2 = Some(side);
self
}
@ -36,12 +38,10 @@ impl UIWidget for RoundedWidget {
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())];
return vec![format!("rounded-{}-{}", side.to_value(), size.to_value())];
}
} else if let Some(size) = &self.1 {
return vec![format!("rounded-{}", size.to_value())];
}
vec!["rounded".to_owned()]
}

View file

@ -4,36 +4,36 @@ 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 medium<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "md".to_owned())
}
pub fn small<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "sm".to_owned())
pub fn small<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "sm".to_owned())
}
pub fn regular<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), String::new())
pub fn regular<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), String::new())
}
pub fn large<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "lg".to_owned())
pub fn large<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "lg".to_owned())
}
pub fn none<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "none".to_owned())
pub fn none<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "none".to_owned())
}
pub fn xl<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "xl".to_owned())
pub fn xl<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "xl".to_owned())
}
pub fn _2xl<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "2xl".to_owned())
pub fn _2xl<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "2xl".to_owned())
}
pub fn inner<T: UIWidget + 'static>(inner: T) -> Shadow {
Shadow(Box::new(inner), "inner".to_owned())
pub fn inner<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "inner".to_owned())
}
}

View file

@ -16,12 +16,14 @@ impl Render for SpaceBetweenWidget {
}
impl SpaceBetweenWidget {
pub fn x(mut self, x: ScreenValue) -> Self {
#[must_use]
pub const fn x(mut self, x: ScreenValue) -> Self {
self.1 = Some(x);
self
}
pub fn y(mut self, y: ScreenValue) -> Self {
#[must_use]
pub const fn y(mut self, y: ScreenValue) -> Self {
self.2 = Some(y);
self
}
@ -36,11 +38,11 @@ impl UIWidget for SpaceBetweenWidget {
let mut ret = Vec::new();
if let Some(x) = &self.1 {
ret.push(format!("space-x-{}", x.to_string()));
ret.push(format!("space-x-{}", x.to_value()));
}
if let Some(y) = &self.2 {
ret.push(format!("space-y-{}", y.to_string()));
ret.push(format!("space-y-{}", y.to_value()));
}
ret
@ -108,44 +110,45 @@ pub enum ScreenValue {
}
impl ScreenValue {
pub fn to_string(&self) -> &str {
#[must_use]
pub const fn to_value(&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",
Self::_0 => "0",
Self::_0p5 => "0.5",
Self::_1 => "1",
Self::_1p5 => "1.5",
Self::_2 => "2",
Self::_2p5 => "2.5",
Self::_3 => "3",
Self::_3p5 => "3.5",
Self::_4 => "4",
Self::_5 => "5",
Self::_6 => "6",
Self::_7 => "7",
Self::_8 => "8",
Self::_9 => "9",
Self::_10 => "10",
Self::_11 => "11",
Self::_12 => "12",
Self::_14 => "14",
Self::_16 => "16",
Self::_20 => "20",
Self::_24 => "24",
Self::_28 => "28",
Self::_32 => "32",
Self::_36 => "36",
Self::_40 => "40",
Self::_44 => "44",
Self::_48 => "48",
Self::_52 => "52",
Self::_56 => "56",
Self::_60 => "60",
Self::_64 => "64",
Self::_72 => "72",
Self::_80 => "80",
Self::_90 => "90",
Self::px => "px",
Self::reverse => "reverse",
}
}
}

View file

@ -3,6 +3,7 @@ use maud::{Markup, Render, html};
#[allow(non_snake_case)]
/// Text UI Widget
#[must_use]
pub fn Text(txt: &str) -> TextWidget {
TextWidget {
inner: None,
@ -29,6 +30,7 @@ pub fn Paragraph<T: UIWidget + 'static>(inner: T) -> TextWidget {
#[allow(non_snake_case)]
/// `<span>` element
#[must_use]
pub fn Span(txt: &str) -> TextWidget {
TextWidget {
inner: None,
@ -53,63 +55,72 @@ impl TextWidget {
/// Turn `Text` semibold.
///
/// Adds the class `font-semibold`
#[must_use]
pub fn semibold(mut self) -> Self {
self.font = "font-semibold".to_owned();
self.font = "font-semibold".to_string();
self
}
/// Turn `Text` bold.
///
/// Adds the class `font-bold`
#[must_use]
pub fn bold(mut self) -> Self {
self.font = "font-bold".to_owned();
self.font = "font-bold".to_string();
self
}
/// Turn `Text` medium.
///
/// Adds the class `font-medium`
#[must_use]
pub fn medium(mut self) -> Self {
self.font = "font-medium".to_owned();
self.font = "font-medium".to_string();
self
}
/// Turn `Text` size to 2XL.
///
/// Adds the class `text-2xl`
#[must_use]
pub fn _2xl(mut self) -> Self {
self.size = "text-2xl".to_owned();
self.size = "text-2xl".to_string();
self
}
/// Turn `Text` size to xl.
///
/// Adds the class `text-xl`
#[must_use]
pub fn xl(mut self) -> Self {
self.size = "text-xl".to_owned();
self.size = "text-xl".to_string();
self
}
/// Turn `Text` size to small.
///
/// Adds the class `text-sm`
#[must_use]
pub fn sm(mut self) -> Self {
self.size = "text-sm".to_owned();
self.size = "text-sm".to_string();
self
}
pub fn color<T: UIColor>(mut self, color: T) -> Self {
#[must_use]
pub fn color<T: UIColor>(mut self, color: &T) -> Self {
self.color = format!("text-{}", color.color_class());
self
}
#[must_use]
pub fn black(mut self) -> Self {
self.color = "text-black".to_owned();
self.color = "text-black".to_string();
self
}
#[must_use]
pub fn white(mut self) -> Self {
self.color = "text-white".to_owned();
self.color = "text-white".to_string();
self
}
}
@ -148,8 +159,7 @@ impl UIWidget for TextWidget {
p class=(format!("{} {}", class, self.base_class().join(" "))) { (inner) }
}
}
} else {
if self.span {
} else if self.span {
html! {
span class=(format!("{} {}", class, self.base_class().join(" "))) { (self.txt) }
}
@ -159,5 +169,4 @@ impl UIWidget for TextWidget {
}
}
}
}
}

View file

@ -10,7 +10,7 @@ pub fn Hover<T: UIWidget + 'static, I: UIWidget + 'static>(inherit: I, inner: T)
pub struct HoverWrapper(Box<dyn UIWidget>, Box<dyn UIWidget>);
impl HoverWrapper {
pub fn hovered_class(&self) -> String {
fn hovered_class(&self) -> String {
self.1
.extended_class()
.into_iter()