app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit a92ba8c76703060371beaf10bbd5b35a10542ba2
parent ba19560a50ee1ccd64c9f4c6fd6d5d9c4c7ddeef
Author: triesap <triesap@radroots.dev>
Date:   Thu, 22 Jan 2026 11:05:17 +0000

app: add list select row view

- render select row with label, display, and end

- wire select change callbacks and disabled state

- support loading display with spinner

- cover display loading helper defaults in tests

Diffstat:
Mcrates/ui-components/src/lib.rs | 1+
Mcrates/ui-components/src/list.rs | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 92 insertions(+), 0 deletions(-)

diff --git a/crates/ui-components/src/lib.rs b/crates/ui-components/src/lib.rs @@ -29,6 +29,7 @@ pub use list::{ RadrootsAppUiListLine, RadrootsAppUiListInputRow, RadrootsAppUiListOffsetView, + RadrootsAppUiListSelectRow, RadrootsAppUiListRowDisplayValue, RadrootsAppUiListRow, RadrootsAppUiListRowLabel, diff --git a/crates/ui-components/src/list.rs b/crates/ui-components/src/list.rs @@ -19,6 +19,7 @@ use crate::{ RadrootsAppUiListLabelValueKind, RadrootsAppUiListOffset, RadrootsAppUiListOffsetMod, + RadrootsAppUiListSelect, RadrootsAppUiListTitle, RadrootsAppUiListTitleValue, RadrootsAppUiListTouch, @@ -268,6 +269,10 @@ fn radroots_app_ui_list_input_action_icon_key( .unwrap_or(RadrootsAppUiIconKey::Plus) } +fn radroots_app_ui_list_display_loading(display: Option<&RadrootsAppUiListDisplay>) -> bool { + display.map(|value| value.loading).unwrap_or(false) +} + fn radroots_app_ui_list_label_value_view( value: RadrootsAppUiListLabelValue, is_right: bool, @@ -676,6 +681,86 @@ pub fn RadrootsAppUiListInputRow( } #[component] +pub fn RadrootsAppUiListSelectRow( + basis: RadrootsAppUiListSelect, + #[prop(optional)] hide_active: bool, + #[prop(optional)] hide_border_top: bool, + #[prop(optional)] hide_border_bottom: bool, +) -> impl IntoView { + let RadrootsAppUiListSelect { + field, + label, + display, + end, + loading, + on_click, + } = basis; + 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 + }); + let select_class = radroots_app_ui_list_class_merge(&[ + Some("el-select"), + field.classes.as_deref(), + ]); + let select_id = field.id; + let select_value = field.value.clone(); + let select_disabled = field.disabled; + let on_change = field.on_change; + let display_loading = radroots_app_ui_list_display_loading(display.as_ref()); + let options = field.options; + 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 /> + <div class="flex flex-row pr-3 justify-center items-end" data-ui="list-select"> + {if display_loading { + view! { <RadrootsAppUiSpinner class="text-[12px]".to_string() /> }.into_any() + } else if let Some(display) = display.as_ref() { + let display = display.clone(); + view! { <RadrootsAppUiListRowDisplayValue basis=display hide_active=hide_active /> }.into_any() + } else { + let options_view = options + .iter() + .cloned() + .map(|option| { + let class = radroots_app_ui_list_class_merge(&[ + option.classes.as_deref(), + ]); + view! { <option value=option.value class=class>{option.label}</option> } + }) + .collect_view(); + view! { + <select + id=select_id.clone() + class=select_class.clone() + disabled=select_disabled + prop:value=select_value.clone() + on:change=move |ev| { + if let Some(callback) = &on_change { + callback.run(event_target_value(&ev)); + } + } + > + {options_view} + </select> + } + .into_any() + }} + </div> + </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"), @@ -825,6 +910,7 @@ mod tests { radroots_app_ui_list_default_labels, radroots_app_ui_list_offset_mod, radroots_app_ui_list_input_action_icon_key, + radroots_app_ui_list_display_loading, radroots_app_ui_list_title_padding_class, }; use crate::{ @@ -912,4 +998,9 @@ mod tests { RadrootsAppUiIconKey::Plus ); } + + #[test] + fn list_display_loading_defaults_false() { + assert!(!radroots_app_ui_list_display_loading(None)); + } }