app

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

commit a5d74574ba56d110512f0b3dd72a64c29e53f966
parent ff21f1ec6d7445c813153de6683e1bdb007d6afe
Author: triesap <triesap@radroots.dev>
Date:   Thu, 22 Jan 2026 00:33:22 +0000

app: stabilize dialog presence lifecycle

- move presence wrapper inside portal mount
- store dialog open as ReadSignal to avoid untracked reads
- derive tracked presence signal for portal rendering
- keep sheet reopen behavior consistent after close

Diffstat:
Mapp/assets/styles.css | 53++++++++++++++++++++++++++++++++++++++++++++++++++---
Mcrates/ui-components/src/dialog.rs | 16++++++++--------
2 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/app/assets/styles.css b/app/assets/styles.css @@ -72,12 +72,13 @@ inset: 0; background: rgba(0, 0, 0, 0.32); opacity: 0; - transition: opacity var(--dur-2) var(--ease-ios); + animation: overlay-fade-out 200ms ease both; } [data-ui="dialog-overlay"][data-state="open"], [data-ui="sheet-overlay"][data-state="open"] { opacity: 1; + animation: overlay-fade-in 240ms var(--ease-ios) both; } [data-ui="sheet"] { @@ -93,15 +94,17 @@ background: var(--material-regular); backdrop-filter: blur(18px) saturate(180%); box-shadow: var(--shadow-sheet); - transform: translateY(12px); + transform: translateY(110%); opacity: 0; - transition: transform var(--dur-3) var(--ease-ios), opacity var(--dur-2) ease; + animation: sheet-slide-out 260ms ease both; + will-change: transform, opacity; overscroll-behavior: none; } [data-ui="sheet"][data-state="open"] { transform: translateY(0); opacity: 1; + animation: sheet-slide-in 420ms var(--ease-ios) both; } [data-ui="sheet-handle"] { @@ -142,3 +145,47 @@ color: var(--text-secondary); } } + +@keyframes overlay-fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes overlay-fade-out { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +@keyframes sheet-slide-in { + 0% { + transform: translateY(110%); + opacity: 0; + } + 60% { + transform: translateY(-2%); + opacity: 1; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes sheet-slide-out { + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(110%); + opacity: 0; + } +} diff --git a/crates/ui-components/src/dialog.rs b/crates/ui-components/src/dialog.rs @@ -22,7 +22,7 @@ use radroots_app_ui_primitives::{ #[derive(Clone)] struct RadrootsAppUiDialogContext { - open: Signal<bool>, + open: ReadSignal<bool>, set_open: Callback<bool>, dismiss: Callback<RadrootsAppUiDismissableReason>, modal: bool, @@ -51,8 +51,8 @@ pub fn RadrootsAppUiDialogRoot( let open_prop = open; let is_controlled = open_prop.is_some(); let open_signal = match open_prop { - Some(open) => open.into(), - None => open_state.into(), + Some(open) => open, + None => open_state.read_only(), }; let on_open_change = on_open_change.clone(); let set_open = Callback::new(move |value| { @@ -125,14 +125,14 @@ pub fn RadrootsAppUiDialogTrigger( pub fn RadrootsAppUiDialogPortal(children: ChildrenFn) -> impl IntoView { let context = use_context::<RadrootsAppUiDialogContext>() .expect("dialog context"); - let present = context.open; + let present = Signal::derive(move || context.open.get()); let children = StoredValue::new(children); view! { - <RadrootsAppUiPresence present=present> - <RadrootsAppUiPortal> + <RadrootsAppUiPortal> + <RadrootsAppUiPresence present=present> {(children.get_value())()} - </RadrootsAppUiPortal> - </RadrootsAppUiPresence> + </RadrootsAppUiPresence> + </RadrootsAppUiPortal> } }