mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-21 18:59:49 -04:00
Compare commits
No commits in common. "b2ffd46a79c431f166cc549c85ccf7555a4aa7bf" and "cb54c9829d39874ba4aabefebd9ef15087f7ffc1" have entirely different histories.
b2ffd46a79
...
cb54c9829d
|
@ -2,18 +2,60 @@
|
||||||
import Callout from "@/components/resource/Callout";
|
import Callout from "@/components/resource/Callout";
|
||||||
import Card from "@/components/resource/Card";
|
import Card from "@/components/resource/Card";
|
||||||
import { LandingSearchBar } from "@/components/resource/LandingSearchBar";
|
import { LandingSearchBar } from "@/components/resource/LandingSearchBar";
|
||||||
|
import {
|
||||||
|
BookOpenIcon,
|
||||||
|
BookmarkIcon,
|
||||||
|
ClipboardIcon,
|
||||||
|
} from "@heroicons/react/24/solid";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col items-center">
|
<div className="min-h-screen flex flex-col">
|
||||||
<div className="flex justify-center p-14">
|
{/* icon + title */}
|
||||||
<h1 className="font-bold text-4xl text-purple-800">
|
<div className="pt-16 px-8 pb-4 flex-row">
|
||||||
Good evening!
|
<div className="mb-4 flex items-center space-x-4">
|
||||||
</h1>
|
<Image
|
||||||
|
src="/logo.png"
|
||||||
|
alt="Compass Center logo."
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
/>
|
||||||
|
<h1 className="font-bold text-2xl text-purple-800">
|
||||||
|
Compass Center Advocate Landing Page
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<Callout>
|
||||||
|
Welcome! Below you will find a list of resources for the
|
||||||
|
Compass Center's trained advocates. These materials
|
||||||
|
serve to virtually provide a collection of advocacy,
|
||||||
|
resource, and hotline manuals and information.
|
||||||
|
<b>
|
||||||
|
{" "}
|
||||||
|
If you are an advocate looking for the contact
|
||||||
|
information of a particular Compass Center employee,
|
||||||
|
please directly contact your staff back-up or the person
|
||||||
|
in charge of your training.
|
||||||
|
</b>
|
||||||
|
</Callout>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 flex-grow border-t border-gray-200 bg-gray-50">
|
||||||
|
{/* link to different pages */}
|
||||||
|
<div className="grid grid-cols-3 gap-6 pb-6">
|
||||||
|
<Link href="/resource">
|
||||||
|
<Card icon={<BookmarkIcon />} text="Resources" />
|
||||||
|
</Link>
|
||||||
|
<Link href="/service">
|
||||||
|
<Card icon={<ClipboardIcon />} text="Services" />
|
||||||
|
</Link>
|
||||||
|
<Link href="/training-manual">
|
||||||
|
<Card icon={<BookOpenIcon />} text="Training Manuals" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
{/* search bar */}
|
||||||
|
<LandingSearchBar />
|
||||||
</div>
|
</div>
|
||||||
<LandingSearchBar />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,17 @@
|
||||||
import { FunnelIcon as FunnelIconOutline } from "@heroicons/react/24/outline";
|
|
||||||
import {
|
import {
|
||||||
ArchiveBoxIcon,
|
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
FunnelIcon,
|
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
TagIcon,
|
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/solid";
|
} from "@heroicons/react/24/solid";
|
||||||
import React, {
|
import React, { useState } from "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);
|
||||||
};
|
};
|
||||||
|
@ -42,74 +21,41 @@ export const LandingSearchBar: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-9/12">
|
<div className="max-w mx-auto">
|
||||||
{/* searchbar */}
|
{/* searchbar */}
|
||||||
<div className="flex items-center bg-white border border-gray-200 shadow rounded-md px-4 py-2">
|
<div className="flex items-center bg-white border border-gray-200 shadow rounded-md">
|
||||||
{/* Left side: magnifying glass icon and input */}
|
<div className="flex-grow">
|
||||||
<MagnifyingGlassIcon
|
<input
|
||||||
className="h-5 w-5 text-gray-500"
|
className="sm:text-sm text-gray-800 w-full px-6 py-3 rounded-md focus:outline-none"
|
||||||
aria-hidden="true"
|
type="text"
|
||||||
/>
|
placeholder="Search..."
|
||||||
|
value={searchTerm}
|
||||||
<input
|
onChange={handleSearchChange}
|
||||||
className="sm:text-sm flex-grow text-gray-800 w-full px-3 rounded-md focus:outline-none"
|
/>
|
||||||
type="text"
|
</div>
|
||||||
placeholder="Search…"
|
{/* input */}
|
||||||
value={searchTerm}
|
{searchTerm && (
|
||||||
onChange={handleSearchChange}
|
<button onClick={clearSearch}>
|
||||||
/>
|
<XMarkIcon
|
||||||
|
className="h-5 w-5 text-gray-500"
|
||||||
{/* Right side icons */}
|
aria-hidden="true"
|
||||||
<div className="flex gap-1">
|
/>
|
||||||
{/* If search bar is not empty, include clear icon */}
|
|
||||||
{searchTerm && (
|
|
||||||
<button
|
|
||||||
onClick={clearSearch}
|
|
||||||
className="hover:bg-purple-100 rounded-md p-1"
|
|
||||||
>
|
|
||||||
<XMarkIcon
|
|
||||||
className="h-5 w-5 text-gray-500"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{/* Filter button */}
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
"hover:bg-purple-100 rounded-md p-1 " +
|
|
||||||
(showFilters ? "bg-purple-100" : "")
|
|
||||||
}
|
|
||||||
onClick={() => setShowFilters(!showFilters)}
|
|
||||||
>
|
|
||||||
{!showFilters && (
|
|
||||||
<FunnelIconOutline className="h-5 w-5 text-gray-500" />
|
|
||||||
)}
|
|
||||||
{showFilters && (
|
|
||||||
<FunnelIcon className="h-5 w-5 text-purple-800" />
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-row space-x-1 p-3">
|
||||||
|
<span>
|
||||||
|
<ChevronDownIcon
|
||||||
|
className="h-5 w-5 text-gray-500"
|
||||||
|
onClick={toggleFilterBox}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
{showFilterBox && <FilterBox className="relative top-50" />}
|
||||||
|
<MagnifyingGlassIcon
|
||||||
|
className="h-5 w-5 text-gray-500"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</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
|
||||||
|
@ -118,136 +64,10 @@ export const LandingSearchBar: React.FC = () => {
|
||||||
width={250}
|
width={250}
|
||||||
height={250}
|
height={250}
|
||||||
/>
|
/>
|
||||||
<p className="font-medium text-medium text-gray-800">
|
<h2 className="font-medium text-medium text-gray-800">
|
||||||
Need to find something? Use the search bar above to get your
|
Need to find something? Use the links or the search bar
|
||||||
results.
|
above to get your results.
|
||||||
</p>
|
</h2>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user