mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-20 10:30:16 -04:00
Create search bar and filter pills
Not tied to any functionality at the moment, however
This commit is contained in:
parent
0ae107ff1c
commit
b2ffd46a79
|
@ -1,18 +1,38 @@
|
||||||
import { FunnelIcon as FunnelIconOutline } from "@heroicons/react/24/outline";
|
import { FunnelIcon as FunnelIconOutline } from "@heroicons/react/24/outline";
|
||||||
import {
|
import {
|
||||||
|
ArchiveBoxIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
FunnelIcon,
|
FunnelIcon,
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
|
TagIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/solid";
|
} 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 Image from "next/image";
|
||||||
import { FilterBox } from "../FilterBox";
|
import { FilterBox } from "../FilterBox";
|
||||||
|
|
||||||
export const LandingSearchBar: React.FC = () => {
|
export const LandingSearchBar: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [showFilters, setShowFilters] = useState(false);
|
||||||
const [showFilterBox, setShowFilterBox] = useState(false);
|
const [showFilterBox, setShowFilterBox] = useState(false);
|
||||||
const toggleFilterBox = () => setShowFilterBox((prev) => !prev);
|
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>) => {
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearchTerm(event.target.value);
|
setSearchTerm(event.target.value);
|
||||||
};
|
};
|
||||||
|
@ -57,19 +77,39 @@ export const LandingSearchBar: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
"hover:bg-purple-100 rounded-md p-1 " +
|
"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" />
|
<FunnelIconOutline className="h-5 w-5 text-gray-500" />
|
||||||
)}
|
)}
|
||||||
{showFilterBox && (
|
{showFilters && (
|
||||||
<FunnelIcon className="h-5 w-5 text-purple-800" />
|
<FunnelIcon className="h-5 w-5 text-purple-800" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* 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">
|
<div className="flex flex-col pt-16 space-y-2 justify-center items-center">
|
||||||
<Image
|
<Image
|
||||||
|
@ -86,3 +126,129 @@ export const LandingSearchBar: React.FC = () => {
|
||||||
</div>
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user