app

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

commit e46ff11e7fa3086aedc61e00414d036dbc476300
parent 6c3ede3ca52dcc3ca1a417db874671d4a670b684
Author: triesap <triesap@radroots.dev>
Date:   Wed, 21 Jan 2026 20:45:24 +0000

ui: add sheet components

- add sheet root, trigger, portal, overlay, content, title, description, close
- add sheet data-ui helpers and optional handle slot
- allow dialog components to accept optional prop values directly
- add unit test for sheet data-ui strings

Diffstat:
Mcrates/ui-components/src/dialog.rs | 52++++++++++++++++++++++++++++------------------------
Mcrates/ui-components/src/lib.rs | 14++++++++++++++
Acrates/ui-components/src/sheet.rs | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 239 insertions(+), 24 deletions(-)

diff --git a/crates/ui-components/src/dialog.rs b/crates/ui-components/src/dialog.rs @@ -39,10 +39,10 @@ pub fn radroots_app_ui_dialog_state_value(open: bool) -> &'static str { #[component] pub fn RadrootsAppUiDialogRoot( - #[prop(optional)] open: Option<ReadSignal<bool>>, + open: Option<ReadSignal<bool>>, #[prop(optional)] default_open: bool, - #[prop(optional)] modal: Option<bool>, - #[prop(optional)] on_open_change: Option<Callback<bool>>, + modal: Option<bool>, + on_open_change: Option<Callback<bool>>, children: ChildrenFn, ) -> impl IntoView { let open_state = RwSignal::new(default_open); @@ -79,9 +79,9 @@ pub fn RadrootsAppUiDialogRoot( #[component] pub fn RadrootsAppUiDialogTrigger( #[prop(optional)] disabled: bool, - #[prop(optional)] class: Option<String>, - #[prop(optional)] id: Option<String>, - #[prop(optional)] style: Option<String>, + class: Option<String>, + id: Option<String>, + style: Option<String>, children: Children, ) -> impl IntoView { let context = use_context::<RadrootsAppUiDialogContext>() @@ -129,14 +129,16 @@ pub fn RadrootsAppUiDialogPortal(children: ChildrenFn) -> impl IntoView { #[component] pub fn RadrootsAppUiDialogOverlay( - #[prop(optional)] close_on_click: Option<bool>, - #[prop(optional)] class: Option<String>, - #[prop(optional)] id: Option<String>, - #[prop(optional)] style: Option<String>, + close_on_click: Option<bool>, + data_ui: Option<String>, + class: Option<String>, + id: Option<String>, + style: Option<String>, ) -> impl IntoView { let context = use_context::<RadrootsAppUiDialogContext>() .expect("dialog context"); let close_on_click = close_on_click.unwrap_or(true); + let data_ui = StoredValue::new(data_ui.unwrap_or_else(|| "dialog-overlay".to_string())); let on_click = move |_event: MouseEvent| { if close_on_click { context.set_open.run(false); @@ -147,7 +149,7 @@ pub fn RadrootsAppUiDialogOverlay( id=id class=class style=style - data-ui="dialog-overlay" + data-ui=move || data_ui.get_value() data-state=move || radroots_app_ui_dialog_state_value(context.open.get()) on:click=on_click ></div> @@ -157,9 +159,10 @@ pub fn RadrootsAppUiDialogOverlay( #[component] pub fn RadrootsAppUiDialogContent( #[prop(optional)] disable_outside_pointer_events: bool, - #[prop(optional)] class: Option<String>, - #[prop(optional)] id: Option<String>, - #[prop(optional)] style: Option<String>, + data_ui: Option<String>, + class: Option<String>, + id: Option<String>, + style: Option<String>, children: ChildrenFn, ) -> impl IntoView { let context = use_context::<RadrootsAppUiDialogContext>() @@ -218,6 +221,7 @@ pub fn RadrootsAppUiDialogContent( let labelled_by = move || context.title_id.get(); let described_by = move || context.description_id.get(); let aria_modal = StoredValue::new(if modal { Some("true".to_string()) } else { None }); + let data_ui = StoredValue::new(data_ui.unwrap_or_else(|| "dialog".to_string())); let id_value = StoredValue::new(id.unwrap_or_else(|| content_id.clone())); let class_value = StoredValue::new(class); let style_value = StoredValue::new(style); @@ -238,7 +242,7 @@ pub fn RadrootsAppUiDialogContent( aria-modal=move || aria_modal.get_value() aria-labelledby=labelled_by aria-describedby=described_by - data-ui="dialog" + data-ui=move || data_ui.get_value() data-state=move || radroots_app_ui_dialog_state_value(context.open.get()) > {(children.get_value())()} @@ -250,9 +254,9 @@ pub fn RadrootsAppUiDialogContent( #[component] pub fn RadrootsAppUiDialogTitle( - #[prop(optional)] class: Option<String>, - #[prop(optional)] id: Option<String>, - #[prop(optional)] style: Option<String>, + class: Option<String>, + id: Option<String>, + style: Option<String>, children: Children, ) -> impl IntoView { let context = use_context::<RadrootsAppUiDialogContext>() @@ -280,9 +284,9 @@ pub fn RadrootsAppUiDialogTitle( #[component] pub fn RadrootsAppUiDialogDescription( - #[prop(optional)] class: Option<String>, - #[prop(optional)] id: Option<String>, - #[prop(optional)] style: Option<String>, + class: Option<String>, + id: Option<String>, + style: Option<String>, children: Children, ) -> impl IntoView { let context = use_context::<RadrootsAppUiDialogContext>() @@ -310,9 +314,9 @@ pub fn RadrootsAppUiDialogDescription( #[component] pub fn RadrootsAppUiDialogClose( - #[prop(optional)] class: Option<String>, - #[prop(optional)] id: Option<String>, - #[prop(optional)] style: Option<String>, + class: Option<String>, + id: Option<String>, + style: Option<String>, children: Children, ) -> impl IntoView { let context = use_context::<RadrootsAppUiDialogContext>() diff --git a/crates/ui-components/src/lib.rs b/crates/ui-components/src/lib.rs @@ -4,6 +4,7 @@ mod button; mod label; mod separator; mod dialog; +mod sheet; pub use button::RadrootsAppUiButton; pub use label::RadrootsAppUiLabel; @@ -23,3 +24,16 @@ pub use dialog::{ RadrootsAppUiDialogTitle, RadrootsAppUiDialogTrigger, }; +pub use sheet::{ + radroots_app_ui_sheet_data_ui_value, + radroots_app_ui_sheet_handle_data_ui_value, + radroots_app_ui_sheet_overlay_data_ui_value, + RadrootsAppUiSheetClose, + RadrootsAppUiSheetContent, + RadrootsAppUiSheetDescription, + RadrootsAppUiSheetOverlay, + RadrootsAppUiSheetPortal, + RadrootsAppUiSheetRoot, + RadrootsAppUiSheetTitle, + RadrootsAppUiSheetTrigger, +}; diff --git a/crates/ui-components/src/sheet.rs b/crates/ui-components/src/sheet.rs @@ -0,0 +1,197 @@ +use leptos::prelude::*; + +use super::{ + RadrootsAppUiDialogClose, + RadrootsAppUiDialogContent, + RadrootsAppUiDialogDescription, + RadrootsAppUiDialogOverlay, + RadrootsAppUiDialogPortal, + RadrootsAppUiDialogRoot, + RadrootsAppUiDialogTitle, + RadrootsAppUiDialogTrigger, +}; + +pub fn radroots_app_ui_sheet_data_ui_value() -> &'static str { + "sheet" +} + +pub fn radroots_app_ui_sheet_overlay_data_ui_value() -> &'static str { + "sheet-overlay" +} + +pub fn radroots_app_ui_sheet_handle_data_ui_value() -> &'static str { + "sheet-handle" +} + +#[component] +pub fn RadrootsAppUiSheetRoot( + #[prop(optional)] open: Option<ReadSignal<bool>>, + #[prop(optional)] default_open: bool, + #[prop(optional)] modal: Option<bool>, + #[prop(optional)] on_open_change: Option<Callback<bool>>, + children: ChildrenFn, +) -> impl IntoView { + view! { + <RadrootsAppUiDialogRoot + open=open + default_open=default_open + modal=modal + on_open_change=on_open_change + > + {children()} + </RadrootsAppUiDialogRoot> + } +} + +#[component] +pub fn RadrootsAppUiSheetTrigger( + #[prop(optional)] disabled: bool, + #[prop(optional)] class: Option<String>, + #[prop(optional)] id: Option<String>, + #[prop(optional)] style: Option<String>, + children: Children, +) -> impl IntoView { + view! { + <RadrootsAppUiDialogTrigger + disabled=disabled + class=class + id=id + style=style + > + {children()} + </RadrootsAppUiDialogTrigger> + } +} + +#[component] +pub fn RadrootsAppUiSheetPortal(children: ChildrenFn) -> impl IntoView { + view! { + <RadrootsAppUiDialogPortal> + {children()} + </RadrootsAppUiDialogPortal> + } +} + +#[component] +pub fn RadrootsAppUiSheetOverlay( + #[prop(optional)] close_on_click: Option<bool>, + #[prop(optional)] class: Option<String>, + #[prop(optional)] id: Option<String>, + #[prop(optional)] style: Option<String>, +) -> impl IntoView { + view! { + <RadrootsAppUiDialogOverlay + close_on_click=close_on_click + data_ui=Some(radroots_app_ui_sheet_overlay_data_ui_value().to_string()) + class=class + id=id + style=style + ></RadrootsAppUiDialogOverlay> + } +} + +#[component] +pub fn RadrootsAppUiSheetContent( + #[prop(optional)] disable_outside_pointer_events: bool, + #[prop(optional)] show_handle: bool, + #[prop(optional)] class: Option<String>, + #[prop(optional)] id: Option<String>, + #[prop(optional)] style: Option<String>, + children: ChildrenFn, +) -> impl IntoView { + let handle = show_handle; + let children = StoredValue::new(children); + let content_children = move || { + let inner = (children.get_value())(); + if handle { + view! { + <div data-ui=radroots_app_ui_sheet_handle_data_ui_value()></div> + {inner} + } + .into_any() + } else { + inner + } + }; + view! { + <RadrootsAppUiDialogContent + disable_outside_pointer_events=disable_outside_pointer_events + data_ui=Some(radroots_app_ui_sheet_data_ui_value().to_string()) + class=class + id=id + style=style + > + {content_children} + </RadrootsAppUiDialogContent> + } +} + +#[component] +pub fn RadrootsAppUiSheetTitle( + #[prop(optional)] class: Option<String>, + #[prop(optional)] id: Option<String>, + #[prop(optional)] style: Option<String>, + children: Children, +) -> impl IntoView { + view! { + <RadrootsAppUiDialogTitle + class=class + id=id + style=style + > + {children()} + </RadrootsAppUiDialogTitle> + } +} + +#[component] +pub fn RadrootsAppUiSheetDescription( + #[prop(optional)] class: Option<String>, + #[prop(optional)] id: Option<String>, + #[prop(optional)] style: Option<String>, + children: Children, +) -> impl IntoView { + view! { + <RadrootsAppUiDialogDescription + class=class + id=id + style=style + > + {children()} + </RadrootsAppUiDialogDescription> + } +} + +#[component] +pub fn RadrootsAppUiSheetClose( + #[prop(optional)] class: Option<String>, + #[prop(optional)] id: Option<String>, + #[prop(optional)] style: Option<String>, + children: Children, +) -> impl IntoView { + view! { + <RadrootsAppUiDialogClose + class=class + id=id + style=style + > + {children()} + </RadrootsAppUiDialogClose> + } +} + +#[cfg(test)] +mod tests { + use super::{ + radroots_app_ui_sheet_data_ui_value, + radroots_app_ui_sheet_handle_data_ui_value, + radroots_app_ui_sheet_overlay_data_ui_value, + }; + + #[test] + fn sheet_data_ui_values() { + assert_eq!(radroots_app_ui_sheet_data_ui_value(), "sheet"); + assert_eq!(radroots_app_ui_sheet_overlay_data_ui_value(), "sheet-overlay"); + assert_eq!(radroots_app_ui_sheet_handle_data_ui_value(), "sheet-handle"); + } +}