Create search bar and filter pills

Not tied to any functionality at the moment, however
This commit is contained in:
Andy Chan 2024-10-29 20:03:03 -04:00
parent 0ae107ff1c
commit b2ffd46a79

View File

@ -1,18 +1,38 @@
import { FunnelIcon as FunnelIconOutline } from "@heroicons/react/24/outline";
import {
ArchiveBoxIcon,
ChevronDownIcon,
FunnelIcon,
MagnifyingGlassIcon,
TagIcon,
XMarkIcon,
} from "@heroicons/react/24/solid";
import React, { useState } from "react";
import React, {
ReactNode,
SetStateAction,
useState,
useRef,
useEffect,
} from "react";
import Image from "next/image";
import { FilterBox } from "../FilterBox";
export const LandingSearchBar: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [showFilters, setShowFilters] = useState(false);
const [showFilterBox, setShowFilterBox] = useState(false);
const toggleFilterBox = () => setShowFilterBox((prev) => !prev);
const collections = ["Resources", "Services"];
const [selectedCollections, setSelectedCollections] = useState(
new Array(collections.length).fill(false)
);
const tags = ["Food Relief", "Period Poverty", "Nutrition Education"];
const [selectedTags, setSelectedTags] = useState(
new Array(tags.length).fill(false)
);
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};
@ -57,19 +77,39 @@ export const LandingSearchBar: React.FC = () => {
<button
className={
"hover:bg-purple-100 rounded-md p-1 " +
(showFilterBox ? "bg-purple-100" : "")
(showFilters ? "bg-purple-100" : "")
}
onClick={toggleFilterBox}
onClick={() => setShowFilters(!showFilters)}
>
{!showFilterBox && (
{!showFilters && (
<FunnelIconOutline className="h-5 w-5 text-gray-500" />
)}
{showFilterBox && (
{showFilters && (
<FunnelIcon className="h-5 w-5 text-purple-800" />
)}
</button>
</div>
</div>
{/* Search filters */}
<div className={"mt-2 flex " + (showFilters ? "" : "hidden")}>
<FilterPill
icon={ArchiveBoxIcon}
name="In"
options={collections}
selectedOptions={selectedCollections}
setSelectedOptions={setSelectedCollections}
/>
<FilterPill
icon={TagIcon}
name="Tags"
options={tags}
selectedOptions={selectedTags}
setSelectedOptions={setSelectedTags}
searchBar
/>
</div>
{/* search results, for now since it's empty this is the default screen */}
<div className="flex flex-col pt-16 space-y-2 justify-center items-center">
<Image
@ -86,3 +126,129 @@ export const LandingSearchBar: React.FC = () => {
</div>
);
};
// Closes the filter dropdown when the user clicks outside the dropdown.
const useFilterPillDropdown = (
ref: React.RefObject<HTMLDivElement>,
setShowDropdown: Function
) => {
// Close on outside click
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
if (setShowDropdown) setShowDropdown(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () =>
// Unbind the event listener on cleanup.
document.removeEventListener("mousedown", handleClickOutside);
}, [ref, setShowDropdown]);
};
interface FilterPillProps {
icon: React.ForwardRefExoticComponent<
Omit<React.SVGProps<SVGSVGElement>, "ref">
>;
name: string;
searchBar?: boolean;
options: string[];
selectedOptions: boolean[];
setSelectedOptions: React.Dispatch<SetStateAction<boolean[]>>;
}
const FilterPill: React.FC<FilterPillProps> = ({
icon,
name,
options,
selectedOptions,
setSelectedOptions,
searchBar = false,
}) => {
const Icon = icon;
const [showDropdown, setShowDropdown] = useState(false);
const [isActive, setIsActive] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const handleCheck = (
e: React.ChangeEvent<HTMLInputElement>,
item: string
) => {
const selected = selectedOptions.map((o, i) => {
if (i == options.indexOf(item)) {
return e.target.checked;
} else {
return o;
}
});
setSelectedOptions(selected);
setIsActive(selected.includes(true));
};
// Closes dropdown when clicked outside
useFilterPillDropdown(dropdownRef, setShowDropdown);
return (
<div
className={
"border rounded-2xl w-max px-2 py-0.5 text-sm mr-2 relative " +
(isActive
? "border-purple-800 text-purple-800 bg-purple-100"
: "border-gray-400 text-gray-400 hover:bg-gray-100")
}
>
{/* The filter pill */}
<button
className="flex gap-1 items-center"
onClick={() => setShowDropdown(!showDropdown)}
>
<Icon className="h-4 w-4" />
<span className="max-w-36 overflow-hidden whitespace-nowrap text-ellipsis">
{name}
{/* Displaying the selected options */}
{options.reduce(
(a, b, i) =>
a +
(selectedOptions[i] // If selected, append option
? (a === "" ? ": " : ", ") + b // (prepend ": " if first element, ", " if not)
: ""),
""
)}
</span>
<ChevronDownIcon className="h-4 w-4" />
</button>
{/* The filter option selection dropdown */}
<div
ref={dropdownRef}
className={
"absolute top-full mt-0.5 left-0 border border-gray-200 bg-white shadow-lg rounded-md p-1.5 w-48 " +
(showDropdown ? "flex flex-col" : "hidden")
}
>
<input
className="border w-full rounded-md mb-1 text-gray-600 p-1"
type="text"
placeholder={"Search " + name.toLowerCase()}
hidden={!searchBar}
/>
{options.map((item, index) => {
return (
<label className="text-gray-800" key={index}>
<input
type="checkbox"
name="name"
className="mr-1"
checked={selectedOptions[index]}
onChange={(e) => handleCheck(e, item)}
/>
{item}
</label>
);
})}
</div>
</div>
);
};