From 5832ca7cd45af609b9377ed74b965289938cbdfe Mon Sep 17 00:00:00 2001 From: asepharyana Date: Fri, 3 Jul 2026 17:59:38 +0700 Subject: [PATCH] =?UTF-8?q?feat(leptos):=20UI=20primitives=20=E2=80=94=20S?= =?UTF-8?q?keleton,=20StatusBadge,=20EmptyState,=20Modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/ui/empty_state.rs | 19 ++++++++ .../frontend-leptos/frontend/src/ui/mod.rs | 4 ++ .../frontend-leptos/frontend/src/ui/modal.rs | 46 +++++++++++++++++++ .../frontend/src/ui/skeleton.rs | 30 ++++++++++++ .../frontend/src/ui/status_badge.rs | 21 +++++++++ 5 files changed, 120 insertions(+) create mode 100644 services/frontend-leptos/frontend/src/ui/empty_state.rs create mode 100644 services/frontend-leptos/frontend/src/ui/modal.rs create mode 100644 services/frontend-leptos/frontend/src/ui/skeleton.rs create mode 100644 services/frontend-leptos/frontend/src/ui/status_badge.rs diff --git a/services/frontend-leptos/frontend/src/ui/empty_state.rs b/services/frontend-leptos/frontend/src/ui/empty_state.rs new file mode 100644 index 0000000..662f5d5 --- /dev/null +++ b/services/frontend-leptos/frontend/src/ui/empty_state.rs @@ -0,0 +1,19 @@ +// services/frontend-leptos/frontend/src/ui/empty_state.rs +use leptos::prelude::*; + +#[component] +pub fn EmptyState( + #[prop(optional)] icon: Option, + title: &'static str, + #[prop(optional)] description: Option<&'static str>, + #[prop(optional)] children: Option, +) -> impl IntoView { + view! { +
+ {icon.map(|i| view! {
{i}
})} +
{title}
+ {description.map(|d| view! {

{d}

})} + {children.map(|c| c())} +
+ } +} diff --git a/services/frontend-leptos/frontend/src/ui/mod.rs b/services/frontend-leptos/frontend/src/ui/mod.rs index d67bd50..7ee2c6f 100644 --- a/services/frontend-leptos/frontend/src/ui/mod.rs +++ b/services/frontend-leptos/frontend/src/ui/mod.rs @@ -7,3 +7,7 @@ pub mod scroll_area; pub mod select; pub mod tabs; pub mod toast; +pub mod skeleton; +pub mod status_badge; +pub mod empty_state; +pub mod modal; diff --git a/services/frontend-leptos/frontend/src/ui/modal.rs b/services/frontend-leptos/frontend/src/ui/modal.rs new file mode 100644 index 0000000..866f9a9 --- /dev/null +++ b/services/frontend-leptos/frontend/src/ui/modal.rs @@ -0,0 +1,46 @@ +// services/frontend-leptos/frontend/src/ui/modal.rs +use std::sync::Arc; +use leptos::prelude::*; + +#[component] +pub fn Modal( + is_open: RwSignal, + #[prop(optional)] title: Option<&'static str>, + #[prop(optional)] on_close: Option>, + children: Children, +) -> impl IntoView { + let oc1 = on_close.clone(); + let oc2 = on_close; + + view! { + + } +} diff --git a/services/frontend-leptos/frontend/src/ui/skeleton.rs b/services/frontend-leptos/frontend/src/ui/skeleton.rs new file mode 100644 index 0000000..5cb9f49 --- /dev/null +++ b/services/frontend-leptos/frontend/src/ui/skeleton.rs @@ -0,0 +1,30 @@ +// services/frontend-leptos/frontend/src/ui/skeleton.rs +use leptos::prelude::*; + +#[derive(Clone, Default)] +pub enum SkeletonShape { + #[default] + Rounded, + Circular, + Rectangular, +} + +#[component] +pub fn Skeleton( + #[prop(optional)] width: &'static str, + #[prop(optional)] height: &'static str, + #[prop(optional)] shape: SkeletonShape, +) -> impl IntoView { + let shape_class = match shape { + SkeletonShape::Rounded => "", + SkeletonShape::Circular => "skeleton-circular", + SkeletonShape::Rectangular => "skeleton-rectangular", + }; + let combined = format!("skeleton {}", shape_class); + view! { +
+ } +} diff --git a/services/frontend-leptos/frontend/src/ui/status_badge.rs b/services/frontend-leptos/frontend/src/ui/status_badge.rs new file mode 100644 index 0000000..5dfb59d --- /dev/null +++ b/services/frontend-leptos/frontend/src/ui/status_badge.rs @@ -0,0 +1,21 @@ +// services/frontend-leptos/frontend/src/ui/status_badge.rs +use leptos::prelude::*; +use shared_types::message::AiStatus; + +#[component] +pub fn StatusBadge(status: AiStatus) -> impl IntoView { + let (class, label) = match status { + AiStatus::Flagged => ("status-badge-flagged", "Flagged"), + AiStatus::Clean => ("status-badge-clean", "Clean"), + AiStatus::Warn => ("status-badge-warn", "Warned"), + AiStatus::Pending => ("status-badge-pending", "Pending"), + AiStatus::Processing => ("status-badge-processing", "Processing"), + AiStatus::Error => ("status-badge-error", "Error"), + }; + let combined = format!("status-badge {}", class); + view! { + + {label} + + } +}