app

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

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:
Mapp/app.css | 1+
Acrates/ui-components/assets/nav.css | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/ui-components/src/lib.rs | 2++
Acrates/ui-components/src/nav_header.rs | 44++++++++++++++++++++++++++++++++++++++++++++
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> + } +}