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:
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");
+ }
+}