From da5cf4aa57151d570913336ab94dc94bf2ccf47d Mon Sep 17 00:00:00 2001 From: Orual Date: Tue, 23 Jun 2026 21:21:06 -0400 Subject: [PATCH] de-slop the search page somewhat --- assets/styling/home.css | 1 - assets/styling/placeholders.css | 32 ++++++--- src/about.rs | 2 - src/search.rs | 112 +++++++++++++++----------------- src/shell.rs | 15 ++++- src/thing_card.rs | 1 - 6 files changed, 90 insertions(+), 73 deletions(-) diff --git a/assets/styling/home.css b/assets/styling/home.css index 692d9d3..0c5268f 100644 --- a/assets/styling/home.css +++ b/assets/styling/home.css @@ -56,7 +56,6 @@ .hero-band-pitch { margin: 0; - max-width: 60ch; color: var(--color-text); font-size: clamp(1rem, 2vw, 1.15rem); line-height: 1.45; diff --git a/assets/styling/placeholders.css b/assets/styling/placeholders.css index 61be7cf..d5e1562 100644 --- a/assets/styling/placeholders.css +++ b/assets/styling/placeholders.css @@ -9,28 +9,40 @@ max-width: 1180px; margin: 0 auto; padding: clamp(0.75rem, 2vw, 1.75rem) clamp(1rem, 3vw, 2.5rem); - display: flex; - flex-direction: column; - gap: 0.9rem; + display: grid; + grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr); + align-items: start; + gap: 1rem; } .search-intro { display: flex; flex-direction: column; - gap: 0.75rem; - padding: clamp(1.25rem, 3vw, 2rem); + gap: 0.5rem; + padding: 1rem; } .search-intro h1 { margin: 0; color: var(--color-emphasis); letter-spacing: -0.03em; + font-size: 1.75rem; + line-height: 1; } -.search-note { +.search-query { margin: 0; - color: var(--color-muted); - font-size: 0.9rem; + color: var(--color-subtle); + font-family: var(--font-mono); + font-size: 0.85rem; + overflow-wrap: anywhere; +} + +.search-content { + min-width: 0; + display: flex; + flex-direction: column; + gap: 1rem; } .search-results-grid { @@ -59,6 +71,10 @@ } @media (max-width: 820px) { + .search-page { + grid-template-columns: 1fr; + } + .search-results-grid { grid-template-columns: 1fr; } diff --git a/src/about.rs b/src/about.rs index 3e026ca..96e2527 100644 --- a/src/about.rs +++ b/src/about.rs @@ -1,7 +1,5 @@ use dioxus::prelude::*; -use crate::Route; - #[component] pub(crate) fn About() -> Element { rsx! { diff --git a/src/search.rs b/src/search.rs index 0e7d68c..91e83f8 100644 --- a/src/search.rs +++ b/src/search.rs @@ -126,76 +126,68 @@ pub(crate) fn Search(q: String) -> Element { results.read().as_ref(), ); - let page_note = if route_query.is_empty() { - "Catalog search uses the local appview index. Start from the header search field." - } else { - "Catalog search uses the local appview index and returns projects from the Polymodel library." - }; - rsx! { main { class: "search-page", section { class: "search-intro blueprint-panel", aria_label: "Search", span { class: "status-pill status-muted", "Catalog search" } - h1 { "Search" } - if route_query.is_empty() { - p { class: "product-lede", "Search the Polymodel catalog by project name, tags, authors, and indexed description text." } - } else { - p { class: "product-lede", "Results for \"{route_query}\"" } + if !route_query.is_empty() { + p { class: "search-query", "\"{route_query}\"" } } - p { class: "search-note", "{page_note}" } } - match state { - SearchState::Idle => rsx! { - section { class: "state-card empty-state search-state-card", aria_label: "Search instructions", - span { class: "status-pill status-muted", "Ready" } - h2 { "Run a catalog search" } - p { "Use the header search field to look up printable projects in the indexed Polymodel catalog." } - Link { class: "button button-secondary", to: Route::Browse {}, "Browse instead" } - } - }, - SearchState::Loading => rsx! { - section { class: "search-results-grid", aria_label: "Search results loading", - for index in 0..6 { - div { key: "search-skeleton-{index}", class: "state-card loading-state", - span { class: "status-pill", "Searching" } - div { class: "skeleton-line skeleton-wide" } - div { class: "skeleton-line" } - div { class: "skeleton-box" } + div { class: "search-content", + match state { + SearchState::Idle => rsx! { + section { class: "state-card empty-state search-state-card", aria_label: "Search instructions", + span { class: "status-pill status-muted", "Ready" } + h2 { "Search from the header" } + p { "Find indexed projects by name, tag, author, or description." } + Link { class: "button button-secondary", to: Route::Browse {}, "Browse instead" } + } + }, + SearchState::Loading => rsx! { + section { class: "search-results-grid", aria_label: "Search results loading", + for index in 0..6 { + div { key: "search-skeleton-{index}", class: "state-card loading-state", + span { class: "status-pill", "Searching" } + div { class: "skeleton-line skeleton-wide" } + div { class: "skeleton-line" } + div { class: "skeleton-box" } + } } } - } - }, - SearchState::Error(error) => rsx! { - section { class: "state-card error-state search-state-card", aria_label: "Search error", - h2 { "Search failed" } - p { "{error}" } - button { class: "button button-secondary", onclick: move |_| search.restart(), "Retry" } - } - }, - SearchState::Empty => rsx! { - section { class: "state-card empty-state search-state-card", aria_label: "No search results", - span { class: "status-pill status-muted", "No matches" } - h2 { "No projects match \"{route_query}\"" } - p { "Try a broader term, a material, or a project category. The local index answered; it just came back empty." } - Link { class: "button button-secondary", to: Route::Browse {}, "Back to browse" } - } - }, - SearchState::Populated(search_results) => rsx! { - section { class: "search-results-grid", aria_label: "Search results", - for thing in search_results.items.clone() { - SearchThingCard { key: "{thing.uri}", thing } + }, + SearchState::Error(error) => rsx! { + section { class: "state-card error-state search-state-card", aria_label: "Search error", + h2 { "Search failed" } + p { "{error}" } + button { class: "button button-secondary", onclick: move |_| search.restart(), "Retry" } + } + }, + SearchState::Empty => rsx! { + section { class: "state-card empty-state search-state-card", aria_label: "No search results", + span { class: "status-pill status-muted", "No matches" } + h2 { "No projects match \"{route_query}\"" } + p { "Try a broader term, material, or category." } + Link { class: "button button-secondary", to: Route::Browse {}, "Back to browse" } + } + }, + SearchState::Populated(search_results) => rsx! { + section { class: "search-results-grid", aria_label: "Search results", + for thing in search_results.items.clone() { + SearchThingCard { key: "{thing.uri}", thing } + } } - } - {search_pagination( - client.clone(), - route_query.clone(), - results, - search_results.clone(), - search_results.cursor.clone(), - search_results.load_more.clone(), - )} - }, + {search_pagination( + client.clone(), + route_query.clone(), + results, + search_results.clone(), + search_results.cursor.clone(), + search_results.load_more.clone(), + )} + }, + } } } } diff --git a/src/shell.rs b/src/shell.rs index 51cdbc2..494cac8 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -59,7 +59,20 @@ pub(crate) fn AppShell() -> Element { /// `SessionControl`, which issues a real GET to `/oauth/start`). #[component] fn ShellSearch() -> Element { - let mut query = use_signal(String::new); + let route = use_route::(); + let route_query = match route { + Route::Search { q } => Some(q), + _ => None, + }; + let mut query = use_signal(|| route_query.clone().unwrap_or_default()); + let mut synced_route_query = use_signal(|| route_query.clone()); + + if *synced_route_query.read() != route_query { + synced_route_query.set(route_query.clone()); + if let Some(route_query) = route_query { + query.set(route_query); + } + } rsx! { div { class: "app-search", role: "search", diff --git a/src/thing_card.rs b/src/thing_card.rs index be7f0d6..3a1fbbf 100644 --- a/src/thing_card.rs +++ b/src/thing_card.rs @@ -143,7 +143,6 @@ pub fn ThingCard( img { src: "{media.url}", alt: "{media.alt}" } } else { div { class: "thing-card-placeholder blueprint-media", role: "img", aria_label: "Blueprint placeholder for missing preview media", - span { class: "status-pill status-muted", "No preview" } span { class: "blueprint-axis blueprint-axis-x", "X" } span { class: "blueprint-axis blueprint-axis-y", "Y" } span { class: "blueprint-dimension", "preview pending" } -- knotmirror 0.0.1