commit 922c5ac1be67c22e6151b00f65efad0bb3c37315
parent e699c73c9b80439b71429789bd33ed58bd59a6f5
Author: triesap <tyson@radroots.org>
Date: Fri, 6 Feb 2026 13:23:05 +0000
ui: add nav header skeleton
- add nav header component with title and action slot
- add base nav header styles for ios layout
- export nav header from ui-components
- import nav header styles into app css
Diffstat:
4 files changed, 99 insertions(+), 0 deletions(-)
diff --git a/app/app.css b/app/app.css
@@ -10,6 +10,7 @@
@import "./stylesheets/styles-maplibre-gl.css";
@import "./stylesheets/styles-superellipse.css";
@import "../crates/ui-components/assets/list.css";
+@import "../crates/ui-components/assets/nav.css";
@custom-variant h-compact "@media (max-height: 540px)";
@custom-variant se-compact "@media (max-width: 375px) and (max-height: 540px)";
diff --git a/crates/ui-components/assets/nav.css b/crates/ui-components/assets/nav.css
@@ -0,0 +1,52 @@
+.nav-header {
+ position: sticky;
+ top: 0;
+ z-index: 20;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: calc(var(--safe-t) + 6px) 16px 10px;
+ background: transparent;
+}
+
+.nav-header__bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: var(--nav-header-height);
+}
+
+.nav-header__title {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+.nav-header__title-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-start;
+ border: none;
+ background: transparent;
+ padding: 0;
+}
+
+.nav-header__title-text {
+ font-family: var(--font-sansd);
+ font-size: 22px;
+ line-height: 28px;
+ font-weight: 700;
+ letter-spacing: -0.01em;
+ text-transform: capitalize;
+ max-width: min(72vw, 420px);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.nav-header__actions {
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+}
diff --git a/crates/ui-components/src/lib.rs b/crates/ui-components/src/lib.rs
@@ -11,6 +11,7 @@ mod spinner;
mod dialog;
mod sheet;
mod scroll;
+mod nav_header;
pub use button::RadrootsAppUiButton;
pub use button_layout::{
@@ -119,3 +120,4 @@ pub use scroll::{
RadrootsAppUiScrollContainer,
RadrootsAppUiScrollContext,
};
+pub use nav_header::RadrootsAppUiNavHeader;
diff --git a/crates/ui-components/src/nav_header.rs b/crates/ui-components/src/nav_header.rs
@@ -0,0 +1,44 @@
+#![forbid(unsafe_code)]
+
+use leptos::ev::MouseEvent;
+use leptos::prelude::*;
+
+#[component]
+pub fn RadrootsAppUiNavHeader(
+ label: String,
+ #[prop(optional)] on_label_click: Option<Callback<MouseEvent>>,
+ #[prop(optional)] right: Option<AnyView>,
+ #[prop(optional)] id: Option<String>,
+ #[prop(optional)] class: Option<String>,
+) -> impl IntoView {
+ let class_value = match class {
+ Some(value) => format!("nav-header {value}"),
+ None => "nav-header".to_string(),
+ };
+ let title_view: AnyView = if let Some(callback) = on_label_click {
+ let on_click = move |ev: MouseEvent| {
+ callback.run(ev);
+ };
+ view! {
+ <button class="nav-header__title-button" on:click=on_click>
+ <span class="nav-header__title-text">{label}</span>
+ </button>
+ }
+ .into_any()
+ } else {
+ view! { <div class="nav-header__title-text">{label}</div> }.into_any()
+ };
+ view! {
+ <header id=id class=class_value>
+ <div class="nav-header__bar">
+ <div class="nav-header__title">
+ {title_view}
+ </div>
+ {right
+ .map(|view| view! { <div class="nav-header__actions">{view}</div> }.into_any())
+ .unwrap_or_else(|| view! { <></> }.into_any())
+ }
+ </div>
+ </header>
+ }
+}