patx/projectpay
improve search ux
Commit 384fe6d · patx · 2026-07-06T23:41:05-04:00
Comments
No comments yet.
Diff
diff --git a/templates/base.html b/templates/base.html
index 7258cbd..473d2a1 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -468,6 +468,7 @@
.amount { text-align: right; }
@media (max-width: 760px) {
.topbar-inner, .header-row, .nav, .searchbar { align-items: stretch; flex-direction: column; }
+ input, textarea { font-size: 16px; }
.nav .inline, .nav .inline button, .nav .button { width: 100%; }
.grid-2, .stats, .line-item, .project-card-main { grid-template-columns: 1fr; }
.line-item-text { grid-column: auto; }
@@ -478,6 +479,18 @@
.project-card-amount { text-align: left; }
.project-card-footer { justify-content: flex-start; }
.sticky-pay button { width: 100%; }
+ .has-sticky-index.index-search-active {
+ padding-top: calc(92px + env(safe-area-inset-top, 0px));
+ }
+ .has-sticky-index.index-search-active .sticky-index-search {
+ top: 0;
+ bottom: auto;
+ padding-top: max(12px, env(safe-area-inset-top, 0px));
+ box-shadow: 0 8px 18px var(--shadow-color);
+ }
+ .has-sticky-index.index-search-active .floating-new-project {
+ display: none;
+ }
}
@media print {
.sticky-pay {
diff --git a/templates/index.html b/templates/index.html
index 95d6c3c..b01bf09 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -24,13 +24,31 @@
<script>
const list = document.getElementById("project-list");
+ const page = document.querySelector(".has-sticky-index");
const sentinel = document.getElementById("scroll-sentinel");
const searchInput = document.getElementById("project-search");
const clearSearch = document.getElementById("clear-search");
+ const mobileSearchQuery = window.matchMedia("(max-width: 760px)");
let nextPage = {{ page + 1 if has_more else 0 }};
let loading = false;
let searchTimer = null;
let activeRequest = 0;
+ let blurTimer = null;
+
+ function keepSearchResultsVisible() {
+ if (!mobileSearchQuery.matches) return;
+ requestAnimationFrame(() => {
+ window.scrollTo({ top: 0, left: 0, behavior: "auto" });
+ });
+ setTimeout(() => window.scrollTo(0, 0), 80);
+ }
+
+ function setSearchActive(active) {
+ clearTimeout(blurTimer);
+ if (!page) return;
+ page.classList.toggle("index-search-active", active && mobileSearchQuery.matches);
+ if (active) keepSearchResultsVisible();
+ }
function applyRows(html, append) {
const wrapper = document.createElement("div");
@@ -44,6 +62,7 @@
});
nextPage = marker && marker.dataset.hasMore === "1" ? Number(marker.dataset.nextPage) : 0;
sentinel.textContent = nextPage ? "Loading more projects as you scroll" : "";
+ if (!append) keepSearchResultsVisible();
}
async function fetchProjects(page, append) {
@@ -80,12 +99,30 @@
}
searchInput.addEventListener("input", queueSearch);
+ searchInput.addEventListener("focus", () => setSearchActive(true));
+ searchInput.addEventListener("blur", () => {
+ blurTimer = setTimeout(() => setSearchActive(false), 120);
+ });
clearSearch.addEventListener("click", () => {
searchInput.value = "";
searchInput.focus();
queueSearch();
});
updateClearButton();
+ function syncSearchActiveState() {
+ setSearchActive(document.activeElement === searchInput);
+ }
+
+ if (mobileSearchQuery.addEventListener) {
+ mobileSearchQuery.addEventListener("change", syncSearchActiveState);
+ } else if (mobileSearchQuery.addListener) {
+ mobileSearchQuery.addListener(syncSearchActiveState);
+ }
+ if (window.visualViewport) {
+ window.visualViewport.addEventListener("resize", () => {
+ if (document.activeElement === searchInput) keepSearchResultsVisible();
+ });
+ }
if ("IntersectionObserver" in window) {
const observer = new IntersectionObserver((entries) => {