search page ui fixes

This commit is contained in:
gosangam
2026-05-31 11:09:43 +05:30
parent 489adb0eeb
commit 17fb83087f
4 changed files with 384 additions and 146 deletions
+62 -122
View File
@@ -10,6 +10,8 @@ import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { buildSearchResultUrl } from "@/lib/text-highlight";
import { KnowledgebaseIndex } from "@/components/knowledgebase-index";
import { useMediaQuery } from "@/components/selection-toolbar/shared";
import { SearchFiltersMenu } from "./filters-menu";
import {
Search,
Loader2,
@@ -197,6 +199,9 @@ function SearchInner() {
const searchParams = useSearchParams();
const router = useRouter();
const inputRef = useRef<HTMLInputElement>(null);
// Drives the popover-vs-drawer split inside SearchFiltersMenu —
// same convention as the rest of the app's overflow menus.
const isMobile = useMediaQuery("(max-width: 767px)");
// Read initial state from URL
const initialQuery = searchParams.get("q") || "";
@@ -241,12 +246,8 @@ function SearchInner() {
// When the query is empty, derive these as "no results" during render
// instead of setting state inside an effect.
const effectiveData =
isQueryEmpty || isUserSearch ? null : data;
const hasSearched =
isUserSearch
? !isUserQueryEmpty
: !isQueryEmpty;
const effectiveData = isQueryEmpty || isUserSearch ? null : data;
const hasSearched = isUserSearch ? !isUserQueryEmpty : !isQueryEmpty;
// Sync URL when state changes
const pushUrl = useCallback(
@@ -423,11 +424,6 @@ function SearchInner() {
<h1 className="mt-4 font-[family-name:var(--font-title)] font-semibold text-2xl sm:text-4xl leading-tight text-foreground">
Find a word, a verse, a name.
</h1>
<p className="mt-3 max-w-xl text-base text-muted-foreground leading-relaxed">
Smart search across books, letters, lectures, essays,
journals and magazines. Diacritics are ignored, so
&ldquo;Krsna&rdquo; finds &ldquo;Kṛṣṇa.&rdquo;
</p>
</section>
<main className="max-w-5xl mx-auto px-4 sm:px-6 pb-16">
@@ -473,74 +469,20 @@ function SearchInner() {
</button>
</div>
{/* Content Type Tabs */}
{/* Filters — single trigger that opens a popover (desktop) or
drawer (mobile) of multi-select checkboxes. The trigger
carries a count badge when one or more filters are active.
Users is mutually exclusive with content narrowing inside
the menu. */}
<div className="mt-5 flex flex-wrap gap-2">
{/* Users chip — toggles the "@" prefix on the input. Sits at
the start of the row so its at-sign icon reads as a kind
of "search mode" indicator. */}
<button
type="button"
onClick={toggleUsersChip}
aria-pressed={isUserChipActive}
className={cn(
"inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors",
isUserChipActive
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground",
)}
>
<AtSign className="size-4" />
Users
{userData &&
isUserChipActive &&
userData.results.length > 0 && (
<span
className={cn(
"ml-1 inline-flex items-center justify-center rounded-full px-1.5 py-0.5 text-xs font-semibold leading-none",
"bg-primary-foreground/20 text-primary-foreground",
)}
>
{userData.results.length}
</span>
)}
</button>
{CONTENT_TABS.map((tab) => {
const active = !isUserChipActive && isTypeActive(tab.value);
return (
<button
key={tab.value}
onClick={() => handleTypeChange(tab.value)}
disabled={isUserChipActive}
className={cn(
"inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors",
active
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground",
// When the @ prefix is on, content tabs don't apply —
// grey them out so the chip group reads as "user
// mode is exclusive".
isUserChipActive &&
"opacity-40 cursor-not-allowed hover:bg-muted hover:text-muted-foreground",
)}
>
{tab.label}
{effectiveData &&
active &&
selectedTypes.size <= 1 &&
effectiveData.total > 0 && (
<span
className={cn(
"ml-1 inline-flex items-center justify-center rounded-full px-1.5 py-0.5 text-xs font-semibold leading-none",
"bg-primary-foreground/20 text-primary-foreground",
)}
>
{effectiveData.total.toLocaleString()}
</span>
)}
</button>
);
})}
<SearchFiltersMenu
contentTabs={CONTENT_TABS}
selectedTypes={selectedTypes}
onTypeChange={handleTypeChange}
isUserChipActive={isUserChipActive}
onToggleUsers={toggleUsersChip}
isMobile={isMobile}
/>
</div>
{/* Results Area */}
@@ -633,48 +575,48 @@ function SearchInner() {
!isUserSearch &&
effectiveData &&
effectiveData.results.length > 0 && (
<>
{/* Results count + top pagination */}
<div className="mb-4 flex items-center justify-between flex-wrap gap-3">
<p className="text-sm text-muted-foreground">
{effectiveData.total.toLocaleString()}{" "}
{effectiveData.total === 1 ? "result " : "results "} for
&ldquo;
{effectiveData.query}&rdquo;
</p>
{effectiveData.total_pages > 1 && (
<PaginationControls
page={effectiveData.page}
totalPages={effectiveData.total_pages}
onPageChange={handlePageChange}
compact
/>
)}
</div>
{/* Result list */}
<div className="space-y-3">
{effectiveData.results.map((result, i) => (
<ResultCard
key={`${result.collection}-${result.id || i}`}
result={result}
/>
))}
</div>
{/* Bottom pagination */}
{effectiveData.total_pages > 1 && (
<div className="mt-8">
<PaginationControls
page={effectiveData.page}
totalPages={effectiveData.total_pages}
total={effectiveData.total}
onPageChange={handlePageChange}
/>
<>
{/* Results count + top pagination */}
<div className="mb-4 flex items-center justify-between flex-wrap gap-3">
<p className="text-sm text-muted-foreground">
{effectiveData.total.toLocaleString()}{" "}
{effectiveData.total === 1 ? "result " : "results "} for
&ldquo;
{effectiveData.query}&rdquo;
</p>
{effectiveData.total_pages > 1 && (
<PaginationControls
page={effectiveData.page}
totalPages={effectiveData.total_pages}
onPageChange={handlePageChange}
compact
/>
)}
</div>
)}
</>
)}
{/* Result list */}
<div className="space-y-3">
{effectiveData.results.map((result, i) => (
<ResultCard
key={`${result.collection}-${result.id || i}`}
result={result}
/>
))}
</div>
{/* Bottom pagination */}
{effectiveData.total_pages > 1 && (
<div className="mt-8">
<PaginationControls
page={effectiveData.page}
totalPages={effectiveData.total_pages}
total={effectiveData.total}
onPageChange={handlePageChange}
/>
</div>
)}
</>
)}
</div>
</main>
</div>
@@ -902,9 +844,7 @@ function UserResultCard({ user }: { user: UserResult }) {
<p className="text-sm font-semibold text-foreground truncate">
{user.name}
</p>
<p className="text-xs text-muted-foreground truncate">
@{user.handle}
</p>
<p className="text-xs text-muted-foreground truncate">@{user.handle}</p>
</div>
</Link>
);