commit 3821b4ff14060740ad3ddcade961c17a1b8301ea
parent 9ba7ed427d539732a18b1e541b6bc448352416ce
Author: triesap <tyson@radroots.org>
Date: Wed, 28 Jan 2026 13:51:30 +0000
ui: Refine setup layout and button interactions
- Center key choice layout and profile title
- Normalize view sizing and transitions for auth screens
- Update button pair behavior and back offset
- Adjust compact breakpoint and bold button sizing
Diffstat:
4 files changed, 115 insertions(+), 120 deletions(-)
diff --git a/app/app.css b/app/app.css
@@ -11,6 +11,9 @@
@import "./stylesheets/styles-superellipse.css";
@import "../crates/ui-components/assets/list.css";
+@custom-variant h-compact "@media (max-height: 540px)";
+@custom-variant se-compact "@media (max-width: 375px) and (max-height: 540px)";
+
@source "./index.html";
@source "./src/**/*.rs";
@source "../crates/**/*.rs";
@@ -56,6 +59,11 @@
min-height: var(--size-line-button);
}
+@utility h-bold_button {
+ height: var(--height-bold_button);
+ min-height: var(--height-bold_button);
+}
+
@layer base {
html {
font-family: var(--font-sans);
@@ -128,6 +136,8 @@
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
+ height: 100dvh;
+ min-height: 100dvh;
overscroll-behavior: none;
}
@@ -149,11 +159,15 @@
}
.app-view {
- will-change: transform, opacity;
+ will-change: opacity;
+ position: relative;
+ width: 100%;
+ flex: 1 1 auto;
+ min-height: 0;
}
.app-view-enter {
- animation: app-view-enter 360ms var(--ease-ios) both;
+ animation: app-view-enter 200ms cubic-bezier(.2,.8,.2,1) both;
}
@media (prefers-reduced-motion: reduce) {
@@ -290,16 +304,10 @@
}
@keyframes app-view-enter {
- 0% {
+ from {
opacity: 0;
- transform: translateY(10px) scale(0.98);
- }
- 60% {
- opacity: 1;
- transform: translateY(-2px) scale(1.005);
}
- 100% {
+ to {
opacity: 1;
- transform: translateY(0) scale(1);
}
}
diff --git a/app/src/app.rs b/app/src/app.rs
@@ -12,17 +12,6 @@ use radroots_app_ui_components::{
RadrootsAppUiButtonLayoutAction,
RadrootsAppUiButtonLayoutBackAction,
RadrootsAppUiButtonLayoutPair,
- RadrootsAppUiList,
- RadrootsAppUiListIcon,
- RadrootsAppUiListItem,
- RadrootsAppUiListItemKind,
- RadrootsAppUiListLabel,
- RadrootsAppUiListLabelText,
- RadrootsAppUiListLabelValue,
- RadrootsAppUiListLabelValueKind,
- RadrootsAppUiListTouch,
- RadrootsAppUiListTouchEnd,
- RadrootsAppUiListView,
};
use crate::{
@@ -78,23 +67,18 @@ fn health_result_label(result: &RadrootsAppHealthCheckResult) -> String {
}
}
-fn setup_label(value: &str) -> RadrootsAppUiListLabelValue {
- RadrootsAppUiListLabelValue {
- classes_wrap: None,
- hide_truncate: false,
- value: RadrootsAppUiListLabelValueKind::Text(RadrootsAppUiListLabelText {
- value: value.to_string(),
- classes: Some("capitalize".to_string()),
- }),
- }
-}
-
fn setup_touch_callback(action: &'static str) -> Callback<MouseEvent> {
Callback::new(move |_| {
let _ = app_log_debug_emit("log.app.setup.choice", action, None);
})
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum RadrootsAppSetupKeyChoice {
+ Generate,
+ AddExisting,
+}
+
fn active_key_label(value: Option<String>) -> String {
let Some(value) = value else {
return "missing".to_string();
@@ -224,61 +208,9 @@ fn SetupPage() -> impl IntoView {
let navigate_guard = navigate.clone();
let navigate_home = navigate.clone();
let setup_step = RwSignal::new_local(app_setup_step_default());
- let key_choice_list = RadrootsAppUiList {
- id: Some("setup-key-choice".to_string()),
- view: Some("setup".to_string()),
- classes: None,
- title: None,
- default_state: None,
- list: Some(vec![
- Some(RadrootsAppUiListItem {
- kind: RadrootsAppUiListItemKind::Touch(RadrootsAppUiListTouch {
- label: RadrootsAppUiListLabel {
- left: vec![setup_label("generate new key")],
- right: Vec::new(),
- },
- display: None,
- end: Some(RadrootsAppUiListTouchEnd {
- icon: RadrootsAppUiListIcon {
- key: "caret-right".to_string(),
- class: None,
- },
- on_click: None,
- }),
- on_click: Some(setup_touch_callback("generate_key")),
- }),
- loading: false,
- hide_active: false,
- hide_field: false,
- full_rounded: false,
- offset: None,
- }),
- Some(RadrootsAppUiListItem {
- kind: RadrootsAppUiListItemKind::Touch(RadrootsAppUiListTouch {
- label: RadrootsAppUiListLabel {
- left: vec![setup_label("add existing key")],
- right: Vec::new(),
- },
- display: None,
- end: Some(RadrootsAppUiListTouchEnd {
- icon: RadrootsAppUiListIcon {
- key: "caret-right".to_string(),
- class: None,
- },
- on_click: None,
- }),
- on_click: Some(setup_touch_callback("add_key")),
- }),
- loading: false,
- hide_active: false,
- hide_field: false,
- full_rounded: false,
- offset: None,
- }),
- ]),
- hide_offset: false,
- styles: None,
- };
+ let setup_key_choice = RwSignal::new_local(None::<RadrootsAppSetupKeyChoice>);
+ let on_generate_key = setup_touch_callback("generate_key");
+ let on_add_key = setup_touch_callback("add_key");
Effect::new(move || {
if setup_required.get() == Some(false) {
navigate_guard("/", Default::default());
@@ -294,12 +226,16 @@ fn SetupPage() -> impl IntoView {
};
let rewind_step: Callback<MouseEvent> = {
let setup_step = setup_step.clone();
+ let setup_key_choice = setup_key_choice.clone();
Callback::new(move |_| {
setup_step.update(|step| {
*step = step.prev();
});
+ setup_key_choice.set(None);
})
};
+ let on_generate_key = on_generate_key.clone();
+ let on_add_key = on_add_key.clone();
view! {
<main
id="app-setup"
@@ -371,34 +307,87 @@ fn SetupPage() -> impl IntoView {
RadrootsAppSetupStep::KeyChoice => view! {
<section
id="app-setup-key-choice"
- class="app-view app-view-enter flex flex-col w-full gap-4 px-6 pt-10 pb-16"
+ class="app-view app-view-enter flex flex-col w-full px-6 pt-10 pb-16"
+ on:click=move |_| {
+ setup_key_choice.set(None);
+ }
>
- <header id="app-setup-key-choice-header" class="flex flex-col gap-2">
- <p class="font-sans text-sm uppercase tracking-[0.14em] text-ly0-gl-label">
- "Setup"
- </p>
- <h2 class="font-sans font-[600] text-2xl text-ly0-gl">
- "Choose your key"
- </h2>
- <p class="font-sans text-line_d_e text-ly0-gl-label">
- "Select how you want to add your Nostr key."
- </p>
- </header>
- <div id="app-setup-key-choice-list" class="w-full">
- <RadrootsAppUiListView basis=key_choice_list.clone() />
+ <div
+ id="app-setup-key-choice-body"
+ class="flex flex-1 w-full flex-col justify-center items-center gap-8"
+ >
+ <div
+ id="app-setup-key-choice-title"
+ class="flex flex-row w-full justify-center items-center"
+ >
+ <p class="font-sans font-[600] text-ly0-gl text-3xl">
+ "Configure Device"
+ </p>
+ </div>
+ <div
+ id="app-setup-key-choice-actions"
+ class="flex flex-col w-full gap-6 justify-center items-center"
+ >
+ <button
+ id="app-setup-key-choice-generate"
+ type="button"
+ class=move || {
+ if setup_key_choice.get()
+ == Some(RadrootsAppSetupKeyChoice::Generate)
+ {
+ "flex flex-col h-bold_button w-lo_ios0 ios1:w-lo_ios1 justify-center items-center rounded-touch ly1-apply-active ly1-raise-apply ly1-ring-apply el-re"
+ } else {
+ "flex flex-col h-bold_button w-lo_ios0 ios1:w-lo_ios1 justify-center items-center rounded-touch bg-ly1 el-re"
+ }
+ }
+ on:click=move |ev| {
+ ev.stop_propagation();
+ setup_key_choice.set(Some(RadrootsAppSetupKeyChoice::Generate));
+ on_generate_key.run(ev);
+ }
+ >
+ <span class="font-sans font-[600] text-ly0-gl text-xl">
+ "Create new keypair"
+ </span>
+ </button>
+ <button
+ id="app-setup-key-choice-add"
+ type="button"
+ class=move || {
+ if setup_key_choice.get()
+ == Some(RadrootsAppSetupKeyChoice::AddExisting)
+ {
+ "flex flex-col h-bold_button w-lo_ios0 ios1:w-lo_ios1 justify-center items-center rounded-touch ly1-apply-active ly1-raise-apply ly1-ring-apply el-re"
+ } else {
+ "flex flex-col h-bold_button w-lo_ios0 ios1:w-lo_ios1 justify-center items-center rounded-touch bg-ly1 el-re"
+ }
+ }
+ on:click=move |ev| {
+ ev.stop_propagation();
+ setup_key_choice.set(Some(RadrootsAppSetupKeyChoice::AddExisting));
+ on_add_key.run(ev);
+ }
+ >
+ <span class="font-sans font-[600] text-ly0-gl text-xl">
+ "Use existing keypair"
+ </span>
+ </button>
+ </div>
</div>
</section>
}.into_any(),
}}
<footer
id="app-setup-actions"
- class="z-10 absolute bottom-4 left-0 flex flex-col w-full justify-center items-center"
+ class="z-10 absolute bottom-4 left-0 flex flex-col w-full justify-center items-center se-compact:bottom-0"
>
{move || {
let step = setup_step.get();
+ let continue_disabled = matches!(step, RadrootsAppSetupStep::KeyChoice)
+ && setup_key_choice.get().is_none();
let continue_action = RadrootsAppUiButtonLayoutAction {
label: "Continue".to_string(),
- disabled: step.is_terminal(),
+ disabled: continue_disabled,
loading: false,
on_click: advance_step.clone(),
};
diff --git a/crates/ui-components/src/button_layout.rs b/crates/ui-components/src/button_layout.rs
@@ -97,8 +97,7 @@ pub fn RadrootsAppUiButtonLayoutPair(
.map(|value| value.visible)
.unwrap_or(false);
let wrapper_class = radroots_app_ui_button_class_merge(&[
- Some("flex flex-col gap-1 justify-center items-center el-re"),
- if back_visible { Some("-translate-y-8") } else { None },
+ Some("flex flex-col gap-1 justify-center items-center"),
class.as_deref(),
]);
view! {
@@ -111,19 +110,24 @@ pub fn RadrootsAppUiButtonLayoutPair(
/>
{back.map(|back_action| {
view! {
- <div class="flex flex-col justify-center items-center el-re">
- {if back_action.visible {
+ <div class="flex flex-col justify-center items-center">
+ {{
let back_label = back_action.label.clone().unwrap_or_default();
let back_disabled = back_action.disabled;
let back_on_click = back_action.on_click.clone();
+ let back_visible = back_action.visible;
let back_text_class = radroots_app_ui_button_class_merge(&[
- Some("font-sans font-[600] tracking-wide text-ly1-gl-shade el-re"),
+ Some("font-sans font-[600] tracking-wide text-ly1-gl-shade"),
if back_disabled { None } else { Some("group-active:text-ly1-gl/40") },
]);
+ let back_button_class = radroots_app_ui_button_class_merge(&[
+ Some("group flex flex-row h-12 w-lo_ios0 ios1:w-lo_ios1 justify-center items-center -translate-y-[2px] transition-opacity duration-[160ms] ease-[cubic-bezier(.2,.8,.2,1)]"),
+ if back_visible { Some("opacity-100") } else { Some("opacity-0 pointer-events-none") },
+ ]);
view! {
<button
type="button"
- class="group flex flex-row h-12 w-lo_ios0 ios1:w-lo_ios1 justify-center items-center fade-in el-re"
+ class=back_button_class
disabled=back_disabled
on:click=move |ev| {
ev.stop_propagation();
@@ -136,12 +140,6 @@ pub fn RadrootsAppUiButtonLayoutPair(
<span class=back_text_class>{back_label}</span>
</button>
}.into_any()
- } else {
- view! {
- <div class="flex flex-row h-4 w-full justify-start items-center">
- <div class="flex-fluid"></div>
- </div>
- }.into_any()
}}
</div>
}
diff --git a/crates/ui-tokens/assets/themes/layout.css b/crates/ui-tokens/assets/themes/layout.css
@@ -1,5 +1,5 @@
@theme {
- --height-bold_button: 4.25rem;
+ --height-bold_button: 3.75rem;
--height-entry_line: 48px;
--height-ios0: 340px;
--height-ios1: 345px;
@@ -16,7 +16,7 @@
--height-nav_tabs_ios0: 80px;
--height-nav_tabs_ios1: 120px;
--height-touch_guide: 3.4rem;
- --max-height-bold_button: 4.25rem;
+ --max-height-bold_button: 3.75rem;
--max-height-entry_line: 48px;
--max-height-ios0: 340px;
--max-height-ios1: 345px;
@@ -43,7 +43,7 @@
--max-width-lo_textdesc_ios1: 312px;
--max-width-trellis_display: 286px;
--max-width-trellis_value: 180px;
- --min-height-bold_button: 4.25rem;
+ --min-height-bold_button: 3.75rem;
--min-height-entry_line: 48px;
--min-height-ios0: 340px;
--min-height-ios1: 345px;
@@ -72,7 +72,7 @@
--min-width-trellis_value: 180px;
--padding-dim_ios0: 340px;
--padding-dim_ios1: 345px;
- --padding-h_bold_button: 4.25rem;
+ --padding-h_bold_button: 3.75rem;
--padding-h_entry_line: 48px;
--padding-h_ios0: 340px;
--padding-h_ios1: 345px;
@@ -103,7 +103,7 @@
--spacing-dim_ios1: 345px;
--spacing-edge: 2px;
--spacing-line: 1px;
- --translate-h_bold_button: 4.25rem;
+ --translate-h_bold_button: 3.75rem;
--translate-h_entry_line: 48px;
--translate-h_ios0: 340px;
--translate-h_ios1: 345px;