ev_lib 0.3.5

EV-invest shared Rust libraries, one per feature
Documentation
use dioxus::prelude::*;
use tailwind_fuse::{AsTailwindClass, TwVariant};

use crate::cn;

#[derive(PartialEq, TwVariant)]
#[tw(class = "relative w-full rounded-lg border px-4 py-3 text-sm grid \
              has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] \
              has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 \
              [&>svg]:text-current")]
pub enum AlertVariant {
	#[tw(default, class = "bg-card text-card-foreground")]
	Default,
	#[tw(class = "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90")]
	Destructive,
}

#[component]
pub fn Alert(#[props(default)] variant: AlertVariant, #[props(default)] class: String, children: Element) -> Element {
	let cls = cn!(variant.as_class(), class);
	rsx! {
		div { class: cls, "data-slot": "alert", role: "alert", {children} }
	}
}

#[component]
pub fn AlertTitle(#[props(default)] class: String, children: Element) -> Element {
	let cls = cn!("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", class);
	rsx! {
		div { class: cls, "data-slot": "alert-title", {children} }
	}
}

#[component]
pub fn AlertDescription(#[props(default)] class: String, children: Element) -> Element {
	let cls = cn!("text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed", class);
	rsx! {
		div { class: cls, "data-slot": "alert-description", {children} }
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::uikit::test_util::render;

	#[test]
	fn default_variant_renders_base_and_slot() {
		fn app() -> Element {
			rsx! { Alert { "body" } }
		}
		let html = render(app);
		assert!(html.contains("bg-card"), "{html}");
		assert!(html.contains("data-slot=\"alert\""), "{html}");
		assert!(html.contains("role=\"alert\""), "{html}");
		assert!(html.contains("body"));
	}

	#[test]
	fn destructive_variant() {
		fn app() -> Element {
			rsx! {
				Alert { variant: AlertVariant::Destructive, "x" }
			}
		}
		let html = render(app);
		assert!(html.contains("text-destructive"), "{html}");
	}

	#[test]
	fn title_and_description_slots() {
		fn app() -> Element {
			rsx! {
				Alert {
					AlertTitle { "t" }
					AlertDescription { "d" }
				}
			}
		}
		let html = render(app);
		assert!(html.contains("data-slot=\"alert-title\""), "{html}");
		assert!(html.contains("data-slot=\"alert-description\""), "{html}");
		assert!(html.contains("text-muted-foreground"), "{html}");
	}
}