From 596f648f31682e9328e61669647406f297675b45 Mon Sep 17 00:00:00 2001 From: Andy Chan Date: Wed, 20 Nov 2024 15:54:54 -0500 Subject: [PATCH] Redesign landing page (#44) * Redesign landing page A bit empty right now, functionality non-existent * Create search bar and filter pills Not tied to any functionality at the moment, however * Implement search result component Searching not yet added, sample results added for testing * Move ref in FilterPill to allow closing by clicking pill Clicking on the pill would reopen the dropdown; it closes properly now --- compass/app/home/page.tsx | 56 +--- .../components/resource/LandingSearchBar.tsx | 282 +++++++++++++++--- compass/components/resource/SearchResult.tsx | 44 +++ .../components/resource/sample_results.json | 32 ++ 4 files changed, 328 insertions(+), 86 deletions(-) create mode 100644 compass/components/resource/SearchResult.tsx create mode 100644 compass/components/resource/sample_results.json diff --git a/compass/app/home/page.tsx b/compass/app/home/page.tsx index 86be3ef..c656fd4 100644 --- a/compass/app/home/page.tsx +++ b/compass/app/home/page.tsx @@ -2,60 +2,20 @@ import Callout from "@/components/resource/Callout"; import Card from "@/components/resource/Card"; import { LandingSearchBar } from "@/components/resource/LandingSearchBar"; -import { - BookOpenIcon, - BookmarkIcon, - ClipboardIcon, -} from "@heroicons/react/24/solid"; +import { SearchResult } from "@/components/resource/SearchResult"; import Image from "next/image"; import Link from "next/link"; export default function Page() { return ( -
- {/* icon + title */} -
-
- Compass Center logo. -

- Compass Center Advocate Landing Page -

-
- - 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. - - {" "} - 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. - - -
-
- {/* link to different pages */} -
- - } text="Resources" /> - - - } text="Services" /> - - - } text="Training Manuals" /> - -
- {/* search bar */} - +
+
+

+ Good evening! +

+ +
); } diff --git a/compass/components/resource/LandingSearchBar.tsx b/compass/components/resource/LandingSearchBar.tsx index afce772..399a71d 100644 --- a/compass/components/resource/LandingSearchBar.tsx +++ b/compass/components/resource/LandingSearchBar.tsx @@ -1,17 +1,41 @@ +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"; +import { SearchResult } from "./SearchResult"; + +// TODO: Actually implement search. +import sampleResults from "./sample_results.json"; 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) => { setSearchTerm(event.target.value); }; @@ -21,53 +45,235 @@ export const LandingSearchBar: React.FC = () => { }; return ( -
+
{/* searchbar */} -
-
- -
- {/* input */} - {searchTerm && ( - + )} + {/* Filter button */} + - )} -
- - - - {showFilterBox && } -
+ + {/* Search filters */} +
+ + +
+ {/* search results, for now since it's empty this is the default screen */} -
+
0 ? " hidden" : "") + } + > Landing illustration -

- Need to find something? Use the links or the search bar - above to get your results. -

+

+ Need to find something? Use the search bar above to get your + results. +

+
+ +
0 ? "" : " hidden") + } + > + {sampleResults.map((result, i) => ( + + ))} +
+
+ ); +}; + +// Closes the filter dropdown when the user clicks outside the dropdown. +const useFilterPillDropdown = ( + ref: React.RefObject, + 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]); +}; + +// Props for the filter pill... +interface FilterPillProps { + icon: React.ForwardRefExoticComponent< + Omit, "ref"> + >; + name: string; + searchBar?: boolean; + options: string[]; + selectedOptions: boolean[]; + setSelectedOptions: React.Dispatch>; +} + +// The filter pill (visible when filter button active, contains dropdown) +const FilterPill: React.FC = ({ + icon, + name, + options, + selectedOptions, + setSelectedOptions, + searchBar = false, +}) => { + const Icon = icon; + const [showDropdown, setShowDropdown] = useState(false); + const [isActive, setIsActive] = useState(false); + const dropdownRef = useRef(null); + + const handleCheck = ( + e: React.ChangeEvent, + 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 ( +
+ {/* The filter pill */} + + + {/* The filter option selection dropdown */} +
+ + {options.map((item, index) => { + return ( + + ); + })}
); diff --git a/compass/components/resource/SearchResult.tsx b/compass/components/resource/SearchResult.tsx new file mode 100644 index 0000000..42b1b9e --- /dev/null +++ b/compass/components/resource/SearchResult.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { + BookmarkIcon, + ClipboardIcon, + QuestionMarkCircleIcon, + ArrowUturnRightIcon, +} from "@heroicons/react/24/solid"; + +interface SearchResultProps { + type: "resource" | "service" | string; + name: string; + description: string; +} + +export const SearchResult: React.FC = ({ + type, + name, + description, +}) => { + const Icon: React.ForwardRefExoticComponent< + Omit, "ref"> + > = + type === "resource" + ? BookmarkIcon + : type === "service" + ? ClipboardIcon + : QuestionMarkCircleIcon; // Unknown type + + return ( +
+ {/* Left side of the item */} +
+ + + {name} + + + {description} + +
+ +
+ ); +}; diff --git a/compass/components/resource/sample_results.json b/compass/components/resource/sample_results.json new file mode 100644 index 0000000..275250c --- /dev/null +++ b/compass/components/resource/sample_results.json @@ -0,0 +1,32 @@ +[ + { + "type": "resource", + "name": "example name", + "description": "example description" + }, + { + "type": "service", + "name": "example name", + "description": "example description" + }, + { + "type": "resource", + "name": "National Domestic Violence Hotline", + "description": "24/7 confidential support for victims of domestic violence" + }, + { + "type": "resource", + "name": "Legal Aid Society", + "description": "Free legal assistance for low-income individuals" + }, + { + "type": "service", + "name": "Crisis Hotline", + "description": "24/7 support for individuals in crisis" + }, + { + "type": "unknown", + "name": "unknown thing with a really long name", + "description": "and let's also type out a really long description to see how it handles overflow and all that anyways" + } +]