Skip to content

Commit 76e4d78

Browse files
densumeshskeptrunedev
authored andcommitted
feature: add filter form builder for the search page
1 parent e6f22c8 commit 76e4d78

15 files changed

Lines changed: 614 additions & 284 deletions

File tree

clients/search-component/src/TrieveModal/FilterSidebarComponents.tsx

Lines changed: 60 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -63,60 +63,84 @@ export const ActiveFilterPills = () => {
6363
return count;
6464
}, [selectedSidebarFilters]);
6565

66+
const handleRemove = (sectionKey: string, tagToRemove: string) => {
67+
const sectionToModify = selectedSidebarFilters.find(
68+
({ section }) => section.key === sectionKey,
69+
)?.section;
70+
if (sectionToModify?.selectionType === "single") {
71+
setSelectedSidebarFilters((prev) => {
72+
return prev.filter(({ section }) => section.key !== sectionKey);
73+
});
74+
} else if (sectionToModify?.selectionType === "multiple") {
75+
setSelectedSidebarFilters((prev) => {
76+
return prev.map((filter) =>
77+
filter.section.key === sectionKey
78+
? {
79+
...filter,
80+
tags: filter.tags?.filter((tag) => tag !== tagToRemove),
81+
}
82+
: filter,
83+
);
84+
});
85+
} else if (sectionToModify?.selectionType === "range") {
86+
setSelectedSidebarFilters((prev) => {
87+
return prev.filter(({ section }) => section.key !== sectionKey);
88+
});
89+
}
90+
};
91+
92+
// If no filters are selected, don't render anything
93+
if (numberOfSelectedFilters === 0) return null;
94+
6695
return (
67-
<div
68-
className="trieve-active-filter-pills-container"
69-
data-number-selected-filters={numberOfSelectedFilters}
70-
>
71-
<div className="trieve-all-active-filters">
96+
<div className="tv-py-2 tv-px-4 tv-bg-white tv-border-b tv-border-zinc-200 tv-flex tv-flex-wrap tv-items-center tv-justify-between tv-w-full">
97+
<div className="tv-flex tv-flex-wrap tv-gap-2">
7298
{activeTagFilters.map(({ sectionKey, tags }) =>
7399
tags?.map((tag) => (
74100
<button
75-
className="trieve-active-filter-pill"
101+
className="tv-inline-flex tv-items-center tv-px-3 tv-py-1.5 tv-rounded-md tv-bg-gray-100 hover:tv-bg-gray-200 tv-text-sm tv-transition-colors"
76102
key={tag}
77103
onClick={() => {
78104
setSelectedSidebarFilters((prev) =>
79105
prev.filter(({ section }) => section.key !== sectionKey),
80106
);
81107
}}
82108
>
83-
<span>{tag}</span>
84-
<i
85-
className="trieve-active-filter-pill-remove-icon"
109+
<span className="tv-text-gray-800">{tag}</span>
110+
<span
111+
className="tv-ml-2 tv-flex tv-items-center tv-justify-center"
86112
onClick={(e) => {
87113
e.stopPropagation();
88-
setSelectedSidebarFilters((prev) =>
89-
prev.filter(({ section }) => section.key !== sectionKey),
90-
);
114+
handleRemove(sectionKey, tag);
91115
}}
92116
>
93-
<XIcon />
94-
</i>
117+
<XIcon className="tv-w-4 tv-h-4 tv-text-gray-500" />
118+
</span>
95119
</button>
96120
)),
97121
)}
98122
{activeRangeFilters.map(({ sectionKey, range }) => (
99123
<button
100-
className="trieve-active-filter-pill"
124+
className="tv-inline-flex tv-items-center tv-px-3 tv-py-1.5 tv-rounded-md tv-bg-gray-100 hover:tv-bg-gray-200 tv-text-sm tv-transition-colors"
101125
key={`${sectionKey}-${range?.min}-${range?.max}`}
102126
>
103-
{range?.min} - {range?.max}
104-
<i
105-
className="trieve-active-filter-pill-remove-icon"
127+
<span className="tv-text-gray-800">
128+
${range?.min} - ${range?.max}
129+
</span>
130+
<span
131+
className="tv-ml-2 tv-flex tv-items-center tv-justify-center"
106132
onClick={() => {
107-
setSelectedSidebarFilters((prev) =>
108-
prev.filter(({ section }) => section.key !== sectionKey),
109-
);
133+
handleRemove(sectionKey, `${range?.min}-${range?.max}`);
110134
}}
111135
>
112-
<XIcon />
113-
</i>
136+
<XIcon className="tv-w-4 tv-h-4 tv-text-gray-500" />
137+
</span>
114138
</button>
115139
))}
116140
</div>
141+
117142
<button
118-
className="trieve-clear-filters-button"
119-
data-number-selected-filters={numberOfSelectedFilters}
143+
className="tv-px-3 tv-py-1.5 tv-rounded-md tv-text-sm tv-text-gray-800 tv-bg-white tv-border tv-border-gray-300 hover:tv-bg-gray-50 tv-transition-colors"
120144
onClick={() => {
121145
setSelectedSidebarFilters([]);
122146
}}
@@ -126,7 +150,6 @@ export const ActiveFilterPills = () => {
126150
</div>
127151
);
128152
};
129-
130153
export interface AccordionProps {
131154
sectionKey: string;
132155
title: string;
@@ -149,8 +172,9 @@ export const Accordion = ({
149172
}[] = useMemo(() => {
150173
const filters = selectedSidebarFilters.filter(
151174
({ section }) =>
152-
section.filterType === "match_any" ||
153-
(section.filterType === "match_all" && section.key === sectionKey),
175+
(section.filterType === "match_any" ||
176+
section.filterType === "match_all") &&
177+
section.key === sectionKey,
154178
);
155179
return filters.map(({ section, tags }) => ({
156180
sectionKey: section.key,
@@ -178,7 +202,12 @@ export const Accordion = ({
178202
const [open, setOpen] = useState(defaultOpen);
179203

180204
const numberOfSelectedFilters = useMemo(() => {
181-
return activeTagFilters.length + activeRangeFilters.length;
205+
let count = 0;
206+
for (const item of activeTagFilters) {
207+
count += item.tags?.length ?? 0;
208+
}
209+
count += activeRangeFilters.length;
210+
return count;
182211
}, [activeTagFilters, activeRangeFilters]);
183212

184213
return (
@@ -295,7 +324,7 @@ export const FilterButton = ({
295324
setSelectedSidebarFilters((prev) => {
296325
return prev.map((filter) =>
297326
filter.section.key === sectionKey
298-
? { ...filter, tags: [...(filter.tags ?? []), filterKey] }
327+
? { ...filter, tags: [filterKey] }
299328
: filter,
300329
);
301330
});
@@ -466,51 +495,9 @@ export const FilterButton = ({
466495
);
467496
};
468497

469-
export interface CollapsibleSectionProps {
470-
title: string;
471-
children: React.ReactNode;
472-
defaultOpen?: boolean;
473-
}
474-
475-
export const CollapsibleSection = ({
476-
title,
477-
children,
478-
defaultOpen = false,
479-
}: CollapsibleSectionProps) => {
480-
const [isOpen, setIsOpen] = useState(defaultOpen);
481-
482-
return (
483-
<div
484-
className="trieve-collapsible-section-container"
485-
data-open={isOpen ? "true" : "false"}
486-
>
487-
<div
488-
className="trieve-collapsible-section-header"
489-
data-open={isOpen ? "true" : "false"}
490-
onClick={() => setIsOpen(!isOpen)}
491-
style={{ cursor: "pointer" }}
492-
>
493-
<h4 className="trieve-collapsible-section-title">{title}</h4>
494-
<div className="trieve-collapsible-section-icon-container">
495-
{isOpen ? <ChevronUpicon /> : <ChevronDownIcon />}
496-
</div>
497-
</div>
498-
{isOpen && (
499-
<div
500-
className="trieve-collapsible-section-content-container"
501-
data-open={isOpen ? "true" : "false"}
502-
>
503-
<div className="trieve-collapsible-section-content">{children}</div>
504-
</div>
505-
)}
506-
</div>
507-
);
508-
};
509-
510498
export const FilterSidebar = ({ sections }: FilterSidebarProps) => {
511499
return (
512500
<aside className="trieve-filter-sidebar">
513-
<ActiveFilterPills />
514501
<div className="trieve-filter-sidebar-section">
515502
<div className="">Filters</div>
516503
{sections.map((section) => (
@@ -528,7 +515,7 @@ export const FilterSidebar = ({ sections }: FilterSidebarProps) => {
528515
key={option.tag}
529516
section={section}
530517
filterKey={option.tag}
531-
label={option.label ?? ""}
518+
label={option.label}
532519
type={section.selectionType}
533520
range={option.range}
534521
/>

clients/search-component/src/TrieveModal/Search/SearchInput.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const SearchInput = () => {
1818
imageUrl,
1919
audioBase64,
2020
isRecording,
21+
selectedSidebarFilters,
2122
} = useModalState();
2223

2324
return (
@@ -75,9 +76,9 @@ export const SearchInput = () => {
7576
</div>
7677
</div>
7778
<ImagePreview isUploading={uploadingImage} imageUrl={imageUrl} active />
78-
{props.suggestedQueries && (!query || (query && !results.length)) && (
79-
<SuggestedQueries />
80-
)}
79+
{props.suggestedQueries &&
80+
(!query || (query && !results.length)) &&
81+
selectedSidebarFilters.length === 0 && <SuggestedQueries />}
8182
</div>
8283
);
8384
};

clients/search-component/src/TrieveModal/Search/SearchMode.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { PdfItem } from "./PdfItem";
1313
import { cn } from "../../utils/styles";
1414
import { SearchInput } from "./SearchInput";
1515
import { GoToChatPrompt } from "./GoToChatPrompt";
16-
import { FilterSidebar } from "../FilterSidebarComponents";
16+
import { ActiveFilterPills, FilterSidebar } from "../FilterSidebarComponents";
1717

1818
export const SearchMode = () => {
1919
const {
@@ -27,6 +27,7 @@ export const SearchMode = () => {
2727
mode,
2828
imageUrl,
2929
audioBase64,
30+
selectedSidebarFilters,
3031
} = useModalState();
3132

3233
const getItemComponent = (
@@ -114,6 +115,7 @@ export const SearchMode = () => {
114115
}, [results]);
115116

116117
const hasQuery = imageUrl || query || audioBase64;
118+
const hasActiveFilters = selectedSidebarFilters.length > 0;
117119

118120
return (
119121
<>
@@ -127,6 +129,10 @@ export const SearchMode = () => {
127129
</div>
128130
)}
129131
<SearchInput />
132+
133+
{/* Filter Pills Bar */}
134+
{hasActiveFilters && <ActiveFilterPills />}
135+
130136
<div className="tv-flex tv-flex-grow tv-overflow-y-auto">
131137
<SearchPage />
132138
<ul

clients/search-component/src/TrieveModal/icons.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -222,17 +222,15 @@ export const ShoppingCart = () => {
222222

223223
export const CheckIcon = () => (
224224
<svg
225+
fill="currentColor"
226+
stroke-width="0"
225227
xmlns="http://www.w3.org/2000/svg"
226-
width="14"
227-
height="14"
228-
viewBox="0 0 24 24"
229-
fill="none"
230-
stroke="currentColor"
231-
strokeWidth="2"
232-
strokeLinecap="round"
233-
strokeLinejoin="round"
228+
viewBox="0 0 448 512"
229+
height="1em"
230+
width="1em"
231+
style={{ overflow: "visible", color: "currentcolor" }}
234232
>
235-
<path d="M20 6 9 17l-5-5" />
233+
<path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7l233.4-233.3c12.5-12.5 32.8-12.5 45.3 0z"></path>
236234
</svg>
237235
);
238236

clients/search-component/src/TrieveModal/index.css

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,6 +1408,7 @@ body {
14081408
}
14091409

14101410
.trieve-search-page {
1411+
@apply tv-w-[25%] tv-max-w-[25%] tv-mr-2;
14111412
&[data-display="false"] {
14121413
display: none;
14131414
}
@@ -1417,11 +1418,11 @@ body {
14171418
}
14181419

14191420
.trieve-search-page-main-section {
1420-
@apply tv-flex;
1421+
@apply tv-flex tv-w-full;
14211422
}
14221423

14231424
.trieve-filter-sidebar {
1424-
@apply tv-p-2 tv-border-r tv-min-h-full tv-mr-5 tv-min-w-fit tv-max-w-[30%];
1425+
@apply tv-p-2 tv-border-r tv-min-h-full tv-mr-5 tv-w-full;
14251426
}
14261427

14271428
.trieve-filter-sidebar-section {
@@ -1441,7 +1442,7 @@ body {
14411442
}
14421443

14431444
.trieve-all-active-filters {
1444-
@apply tv-flex tv-gap-2 tv-p-2;
1445+
@apply tv-flex tv-gap-2 tv-p-2 tv-flex-wrap;
14451446

14461447
.trieve-active-filter-pill {
14471448
@apply tv-flex tv-items-center tv-gap-1 tv-cursor-pointer tv-px-2 tv-py-1 tv-rounded-lg tv-bg-zinc-100;
@@ -1584,8 +1585,8 @@ body {
15841585
fill: currentColor;
15851586

15861587
svg {
1587-
width: 100%;
1588-
height: 100%;
1588+
width: 100% !important;
1589+
height: 100% !important;
15891590
}
15901591
}
15911592
}
@@ -1609,7 +1610,7 @@ body {
16091610
}
16101611

16111612
.trieve-filter-main-section {
1612-
@apply tv-w-full tv-max-w-[30%];
1613+
@apply tv-w-full;
16131614
}
16141615

16151616
.trieve-inference-filters-form {

clients/search-component/src/utils/hooks/modal-context.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,8 @@ export interface PagefindOptions {
5252

5353
export interface TagProp {
5454
tag: string;
55-
label?: string;
55+
label: string;
5656
selected?: boolean;
57-
iconClassName?: string;
58-
icon?: () => JSX.Element;
5957
description?: string;
6058
range?: {
6159
min?: number;
@@ -448,6 +446,9 @@ const ModalProvider = ({
448446
) {
449447
const url = new URL(window.location.href);
450448
url.searchParams.set("q", query);
449+
if (selectedSidebarFilters.length > 0) {
450+
url.searchParams.set("filters", JSON.stringify(selectedSidebarFilters));
451+
}
451452
window.history.replaceState({}, "", url.toString());
452453
}
453454

@@ -692,6 +693,10 @@ const ModalProvider = ({
692693
if (initialQuery) {
693694
setQuery(initialQuery);
694695
}
696+
const initialFilters = url.searchParams.get("filters");
697+
if (initialFilters) {
698+
setSelectedSidebarFilters(JSON.parse(initialFilters));
699+
}
695700
}
696701
}, [props.type, props.inline, props.defaultSearchMode]);
697702

0 commit comments

Comments
 (0)