commit b48fc9d496e474df01d5cc3ac20ffa40b8c7aa62
parent afd26d008605a3f671353c96abb9374be14f5dae
Author: triesap <triesap@radroots.dev>
Date: Thu, 22 Jan 2026 10:59:33 +0000
app: add list touch row view
- add touch row component wiring label/display/end
- add active class helper for consistent active styling
- pass optional end slot via list line wrapper
- cover active class helper in unit tests
Diffstat:
2 files changed, 60 insertions(+), 6 deletions(-)
diff --git a/crates/ui-components/src/lib.rs b/crates/ui-components/src/lib.rs
@@ -36,6 +36,7 @@ pub use list::{
RadrootsAppUiListSection,
RadrootsAppUiListTitleView,
RadrootsAppUiListTouchEndView,
+ RadrootsAppUiListTouchRow,
};
pub use list_types::{
radroots_app_ui_list_icon_key,
diff --git a/crates/ui-components/src/list.rs b/crates/ui-components/src/list.rs
@@ -1,5 +1,7 @@
#![forbid(unsafe_code)]
+use std::sync::Arc;
+
use leptos::ev::MouseEvent;
use leptos::prelude::*;
@@ -16,6 +18,7 @@ use crate::{
RadrootsAppUiListOffsetMod,
RadrootsAppUiListTitle,
RadrootsAppUiListTitleValue,
+ RadrootsAppUiListTouch,
RadrootsAppUiListTouchEnd,
RadrootsAppUiSpinner,
};
@@ -151,6 +154,10 @@ fn radroots_app_ui_list_class_merge(parts: &[Option<&str>]) -> String {
result
}
+fn radroots_app_ui_list_active_class(hide_active: bool) -> Option<&'static str> {
+ if hide_active { None } else { Some("opacity-active") }
+}
+
pub fn radroots_app_ui_list_border_classes(
hide_border_top: bool,
hide_border_bottom: bool,
@@ -173,8 +180,8 @@ pub fn RadrootsAppUiListLine(
#[prop(optional)] loading: bool,
#[prop(optional)] hide_border_top: bool,
#[prop(optional)] hide_border_bottom: bool,
- #[prop(optional)] on_click: Option<Callback<MouseEvent>>,
- #[prop(optional)] end: Option<ChildrenFn>,
+ on_click: Option<Callback<MouseEvent>>,
+ end: Option<ChildrenFn>,
children: ChildrenFn,
) -> impl IntoView {
let border_class = radroots_app_ui_list_border_classes(hide_border_top, hide_border_bottom);
@@ -258,12 +265,12 @@ fn radroots_app_ui_list_label_value_view(
hide_truncate,
value,
} = value;
+ let active_class = radroots_app_ui_list_active_class(hide_active);
let wrap_class = radroots_app_ui_list_class_merge(&[
Some("flex flex-row h-full items-center"),
if hide_truncate { None } else { Some("truncate") },
classes_wrap.as_deref(),
]);
- let active_class = if hide_active { None } else { Some("opacity-active") };
let view = match value {
RadrootsAppUiListLabelValueKind::Text(value) => {
let text_class = radroots_app_ui_list_class_merge(&[
@@ -329,9 +336,10 @@ pub fn RadrootsAppUiListRowDisplayValue(
let display = match basis.value {
RadrootsAppUiListDisplayValue::Icon(icon) => {
let icon_key = radroots_app_ui_list_icon_key(&icon);
+ let active_class = radroots_app_ui_list_active_class(hide_active);
let icon_class = radroots_app_ui_list_class_merge(&[
Some("ui-text-secondary"),
- if hide_active { None } else { Some("opacity-active") },
+ active_class,
icon.class.as_deref(),
]);
if let Some(icon_key) = icon_key {
@@ -341,9 +349,10 @@ pub fn RadrootsAppUiListRowDisplayValue(
}
}
RadrootsAppUiListDisplayValue::Label(label) => {
+ let active_class = radroots_app_ui_list_active_class(hide_active);
let text_class = radroots_app_ui_list_class_merge(&[
Some("text-line_d_e ui-text-secondary line-clamp-1"),
- if hide_active { None } else { Some("opacity-active") },
+ active_class,
label.classes.as_deref(),
]);
view! { <p class=text_class>{label.value}</p> }.into_any()
@@ -491,9 +500,10 @@ pub fn RadrootsAppUiListTouchEndView(
#[prop(optional)] hide_active: bool,
) -> impl IntoView {
let icon_key = radroots_app_ui_list_icon_key(&basis.icon);
+ let active_class = radroots_app_ui_list_active_class(hide_active);
let icon_class = radroots_app_ui_list_class_merge(&[
Some("ui-text-secondary opacity-70 translate-y-[1px]"),
- if hide_active { None } else { Some("opacity-active") },
+ active_class,
basis.icon.class.as_deref(),
]);
let on_click = basis.on_click;
@@ -518,6 +528,42 @@ pub fn RadrootsAppUiListTouchEndView(
}
#[component]
+pub fn RadrootsAppUiListTouchRow(
+ basis: RadrootsAppUiListTouch,
+ #[prop(optional)] hide_active: bool,
+ #[prop(optional)] hide_border_top: bool,
+ #[prop(optional)] hide_border_bottom: bool,
+ #[prop(optional)] loading: bool,
+) -> impl IntoView {
+ let label = basis.label;
+ let display = basis.display;
+ let end = basis.end;
+ let on_click = basis.on_click;
+ let end_slot = end.map(|end| {
+ let hide_active = hide_active;
+ Arc::new(move || {
+ let end_value = end.clone();
+ view! { <RadrootsAppUiListTouchEndView basis=end_value hide_active=hide_active /> }.into_any()
+ }) as ChildrenFn
+ });
+ view! {
+ <RadrootsAppUiListLine
+ loading=loading
+ hide_border_top=hide_border_top
+ hide_border_bottom=hide_border_bottom
+ on_click=on_click
+ end=end_slot
+ >
+ <RadrootsAppUiListRowLabel basis=label.clone() hide_active=hide_active />
+ {display.as_ref().map(|display| {
+ let display = display.clone();
+ view! { <RadrootsAppUiListRowDisplayValue basis=display hide_active=hide_active /> }.into_any()
+ })}
+ </RadrootsAppUiListLine>
+ }
+}
+
+#[component]
pub fn RadrootsAppUiListTitleView(basis: RadrootsAppUiListTitle) -> impl IntoView {
let title_class = radroots_app_ui_list_class_merge(&[
Some("flex flex-row h-[24px] w-full pl-[2px] gap-1 items-center"),
@@ -656,6 +702,7 @@ pub fn RadrootsAppUiListDefaultLabels(
#[cfg(test)]
mod tests {
use super::{
+ radroots_app_ui_list_active_class,
radroots_app_ui_list_class_merge,
radroots_app_ui_list_border_classes,
radroots_app_ui_list_group_data_ui_value,
@@ -696,6 +743,12 @@ mod tests {
}
#[test]
+ fn list_active_class_respects_flag() {
+ assert_eq!(radroots_app_ui_list_active_class(true), None);
+ assert_eq!(radroots_app_ui_list_active_class(false), Some("opacity-active"));
+ }
+
+ #[test]
fn list_border_classes_match_flags() {
let classes = radroots_app_ui_list_border_classes(true, false);
assert_eq!(classes, "group-first:border-t-0 group-last:border-b-line");