Skip to content

Commit a954ea5

Browse files
committed
add search and filter bar
1 parent fa717c6 commit a954ea5

1 file changed

Lines changed: 143 additions & 33 deletions

File tree

app/publications/page.tsx

Lines changed: 143 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,177 @@
11
"use client";
22

33
import { Link } from "@heroui/link";
4+
import { Input } from "@heroui/input";
5+
import { Chip } from "@heroui/chip";
46
import { useSearchParams } from "next/navigation";
7+
import { useState, useMemo } from "react";
58

6-
import { publications, Tag } from "@/config/publications";
9+
import { publications, Tag, Publication } from "@/config/publications";
710
import { PublicationTags } from "@/components/tag";
811

9-
function Links({ paper, code, page }: { paper: string, code: string | null; page: string | null }) {
12+
const allTags = Object.values(Tag);
13+
14+
function Links({ paper, code }: { paper: string; code: string | null }) {
1015
let links = [];
1116
if (paper !== null) {
12-
links.push(<Link href={paper}>paper</Link>);
17+
links.push(<Link key="paper" href={paper}>paper</Link>);
1318
}
1419
if (code !== null) {
15-
links.push(<Link href={code}>code</Link>)
20+
links.push(<Link key="code" href={code}>code</Link>);
1621
}
17-
return <>{links.reduce((prev, curr) => <>{prev} / {curr}</>)}</>
22+
return <>{links.reduce((prev, curr) => <span key="sep">{prev} / {curr}</span>)}</>;
23+
}
24+
25+
function matchesSearch(publication: Publication, query: string): boolean {
26+
const q = query.toLowerCase();
27+
return (
28+
publication.title.toLowerCase().includes(q) ||
29+
publication.authors.toLowerCase().includes(q) ||
30+
publication.venue.toLowerCase().includes(q) ||
31+
publication.abstract.toLowerCase().includes(q)
32+
);
1833
}
1934

2035
export default function PublicationsPage() {
2136
const searchParams = useSearchParams();
37+
const directionTag = searchParams.get("tag") as Tag | null;
2238

23-
const direction = searchParams.get("tag");
24-
const direction_tag = direction as Tag;
25-
let publications_filtered = publications;
39+
const [searchQuery, setSearchQuery] = useState("");
40+
const [selectedTags, setSelectedTags] = useState<Tag[]>(
41+
directionTag ? [directionTag] : []
42+
);
2643

27-
if (direction !== null) {
28-
publications_filtered = publications.filter((publication) =>
29-
publication.tags.includes(direction_tag),
44+
const toggleTag = (tag: Tag) => {
45+
setSelectedTags((prev) =>
46+
prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]
3047
);
31-
}
48+
};
49+
50+
const clearFilters = () => {
51+
setSearchQuery("");
52+
setSelectedTags([]);
53+
};
54+
55+
const filteredPublications = useMemo(() => {
56+
let result = publications;
57+
58+
if (selectedTags.length > 0) {
59+
result = result.filter((pub) =>
60+
selectedTags.some((tag) => pub.tags.includes(tag))
61+
);
62+
}
63+
64+
if (searchQuery.trim()) {
65+
result = result.filter((pub) => matchesSearch(pub, searchQuery));
66+
}
67+
68+
return result;
69+
}, [searchQuery, selectedTags]);
70+
71+
const hasActiveFilters = searchQuery.trim() !== "" || selectedTags.length > 0;
3272

3373
return (
3474
<div>
35-
<ul>
36-
{publications_filtered.map((publication) => (
37-
<li key={publication.title}>
38-
<div className="my-4">
39-
<div className="flex flex-row gap-4">
40-
<div>
41-
<PublicationTags tags={publication.tags} />
42-
</div>
43-
<div>
75+
{/* Search & Filter Section */}
76+
<div className="mb-8 space-y-4">
77+
<Input
78+
isClearable
79+
placeholder="Search by title, author, venue, or keywords..."
80+
size="lg"
81+
value={searchQuery}
82+
onClear={() => setSearchQuery("")}
83+
onValueChange={setSearchQuery}
84+
startContent={
85+
<svg
86+
className="w-5 h-5 text-default-400 flex-shrink-0"
87+
fill="none"
88+
stroke="currentColor"
89+
strokeWidth={2}
90+
viewBox="0 0 24 24"
91+
>
92+
<path
93+
d="M21 21l-4.35-4.35M11 19a8 8 0 100-16 8 8 0 000 16z"
94+
strokeLinecap="round"
95+
strokeLinejoin="round"
96+
/>
97+
</svg>
98+
}
99+
/>
100+
101+
<div className="flex flex-wrap items-center gap-2">
102+
<span className="text-sm text-default-500 mr-1">Filter by topic:</span>
103+
{allTags.map((tag) => (
104+
<Chip
105+
key={tag}
106+
className="cursor-pointer select-none"
107+
variant={selectedTags.includes(tag) ? "solid" : "bordered"}
108+
color={selectedTags.includes(tag) ? "primary" : "default"}
109+
onClick={() => toggleTag(tag)}
110+
>
111+
{tag}
112+
</Chip>
113+
))}
114+
{hasActiveFilters && (
115+
<Chip
116+
className="cursor-pointer select-none"
117+
variant="light"
118+
color="danger"
119+
onClick={clearFilters}
120+
>
121+
Clear all
122+
</Chip>
123+
)}
124+
</div>
125+
126+
{hasActiveFilters && (
127+
<p className="text-sm text-default-400">
128+
Showing {filteredPublications.length} of {publications.length} publications
129+
</p>
130+
)}
131+
</div>
132+
133+
{/* Publications List */}
134+
{filteredPublications.length === 0 ? (
135+
<div className="text-center py-12 text-default-400">
136+
<p className="text-lg">No publications match your search.</p>
137+
<p className="text-sm mt-2">Try adjusting your filters or search terms.</p>
138+
</div>
139+
) : (
140+
<ul>
141+
{filteredPublications.map((publication) => (
142+
<li key={publication.title}>
143+
<div className="my-4">
144+
<div className="flex flex-row gap-4">
145+
<div>
146+
<PublicationTags tags={publication.tags} />
147+
</div>
148+
<div>
44149
{publication.page ? (
45150
<Link href={`projects/${publication.page}`}>
46151
<h2 className="font-bold text-xl">{publication.title}</h2>
47152
</Link>
48153
) : (
49154
<h2 className="font-bold text-xl">{publication.title}</h2>
50155
)}
156+
</div>
157+
</div>
158+
<div className="flex flex-col mx-4 my-2">
159+
<p className="pb-1 text-sm font-bold">
160+
{publication.venue} &nbsp;&nbsp;&nbsp;{" "}
161+
<Links
162+
paper={publication.paper}
163+
code={publication.code}
164+
/>
165+
</p>
166+
<p className="pb-1 text-sm">{publication.authors}</p>
167+
<p className="pb-1">{publication.abstract}</p>
168+
<p className="pb-1">{publication.impact}</p>
51169
</div>
52170
</div>
53-
<div className="flex flex-col mx-4 my-2">
54-
<p className="pb-1 text-sm font-bold">
55-
{publication.venue} &nbsp;&nbsp;&nbsp; <Links paper={publication.paper} code={publication.code} page={publication.page} />
56-
</p>
57-
<p className="pb-1 text-sm">{publication.authors}</p>
58-
<p className="pb-1">{publication.abstract}</p>
59-
<p className="pb-1">{publication.impact}</p>
60-
</div>
61-
</div>
62-
</li>
63-
))}
64-
</ul>
171+
</li>
172+
))}
173+
</ul>
174+
)}
65175
</div>
66176
);
67177
}

0 commit comments

Comments
 (0)