This repository has been archived by the owner on Sep 18, 2024. It is now read-only.
forked from HHS/simpler-grants-gov
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add funding filter and update search filters
- Loading branch information
Showing
11 changed files
with
968 additions
and
14 deletions.
There are no files selected for viewing
148 changes: 148 additions & 0 deletions
148
frontend/src/app/[locale]/look/SearchFilterAccordion/SearchFilterAccordion.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import { Accordion } from "@trussworks/react-uswds"; | ||
import { QueryParamKey } from "src/types/search/searchResponseTypes"; | ||
import SearchFilterCheckbox from "./SearchFilterCheckbox"; | ||
import SearchFilterSection from "./SearchFilterSection/SearchFilterSection"; | ||
import SearchFilterToggleAll from "./SearchFilterToggleAll"; | ||
import { QueryContext } from "src/app/[locale]/look/QueryProvider"; | ||
import { useSearchParamUpdater2 } from "src/hooks/useSearchParamUpdater"; | ||
import { useContext } from "react"; | ||
|
||
export interface AccordionItemProps { | ||
title: React.ReactNode | string; | ||
content: React.ReactNode; | ||
expanded: boolean; | ||
id: string; | ||
headingLevel: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; | ||
className?: string; | ||
// handleToggle?: (event: React.MouseEvent<HTMLButtonElement>) => void; | ||
} | ||
|
||
export interface FilterOption { | ||
id: string; | ||
label: string; | ||
value: string; | ||
isChecked?: boolean; | ||
children?: FilterOption[]; | ||
} | ||
|
||
interface SearchFilterAccordionProps { | ||
options: FilterOption[]; | ||
title: string; // Title in header of accordion | ||
query: Set<string>; | ||
queryParamKey: QueryParamKey; // Ex - In query params, search?{key}=first,second,third | ||
} | ||
|
||
export function SearchFilterAccordion({ | ||
options, | ||
title, | ||
queryParamKey, | ||
query, | ||
}: SearchFilterAccordionProps) { | ||
const { queryTerm } = useContext(QueryContext); | ||
const { updateQueryParams } = useSearchParamUpdater2(); | ||
const totalCheckedCount = query.size | ||
|
||
|
||
const getAccordionTitle = () => ( | ||
<> | ||
{title} | ||
{!!totalCheckedCount && ( | ||
<span className="usa-tag usa-tag--big radius-pill margin-left-1"> | ||
{totalCheckedCount} | ||
</span> | ||
)} | ||
</> | ||
); | ||
|
||
// This should just get the current params and push the update like everything else | ||
const toggleSelectAll = (all: boolean) => { | ||
// TODO: need to clear this. | ||
if (all) { | ||
|
||
} | ||
else { | ||
|
||
} | ||
|
||
} | ||
|
||
const isSectionAllSelected = (options: FilterOption[], params: Set<string>): boolean => { | ||
return false; | ||
} | ||
|
||
const isSectionNoneSelected = (options: FilterOption[], params: Set<string>): boolean => { | ||
return false; | ||
} | ||
|
||
const toggleOptionChecked = (value: string, isChecked: boolean) => { | ||
const updated = new Set(query) | ||
isChecked | ||
? updated.add(value) | ||
: updated.delete(value); | ||
const key = queryParamKey; | ||
updateQueryParams(updated, key, queryTerm); | ||
} | ||
|
||
const isAllSelected = false; | ||
const isNoneSelected = true; | ||
|
||
const getAccordionContent = () => ( | ||
<> | ||
<SearchFilterToggleAll | ||
onSelectAll={() => toggleSelectAll(true)} | ||
onClearAll={() => toggleSelectAll(false)} | ||
isAllSelected={isAllSelected} | ||
isNoneSelected={isNoneSelected} | ||
/> | ||
|
||
<ul className="usa-list usa-list--unstyled"> | ||
{options.map((option) => ( | ||
<li key={option.id}> | ||
{/* If we have children, show a "section" dropdown, otherwise show just a checkbox */} | ||
{option.children ? ( | ||
// SearchFilterSection will map over all children of this option | ||
<SearchFilterSection | ||
option={option} | ||
query={query} | ||
updateCheckedOption={toggleOptionChecked} | ||
toggleSelectAll={toggleSelectAll} | ||
accordionTitle={title} | ||
// TODO: determine from vars | ||
isSectionAllSelected={isSectionAllSelected(option.children, query)} | ||
isSectionNoneSelected={isSectionNoneSelected(option.children, query)} | ||
/> | ||
) : ( | ||
<SearchFilterCheckbox | ||
option={option} | ||
query={query} | ||
updateCheckedOption={toggleOptionChecked} | ||
accordionTitle={title} | ||
/> | ||
)} | ||
</li> | ||
))} | ||
</ul> | ||
</> | ||
); | ||
|
||
const accordionOptions: AccordionItemProps[] = [ | ||
{ | ||
title: getAccordionTitle(), | ||
content: getAccordionContent(), | ||
expanded: false, | ||
id: `funding-instrument-filter-${queryParamKey}`, | ||
headingLevel: "h2", | ||
}, | ||
]; | ||
|
||
return ( | ||
<Accordion | ||
bordered={true} | ||
items={accordionOptions} | ||
multiselectable={true} | ||
className="margin-top-4" | ||
/> | ||
); | ||
} | ||
|
||
export default SearchFilterAccordion; |
40 changes: 40 additions & 0 deletions
40
frontend/src/app/[locale]/look/SearchFilterAccordion/SearchFilterCheckbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
"use client"; | ||
|
||
import FilterCheckbox from "src/components/FilterCheckbox"; | ||
import { FilterOption } from "./SearchFilterAccordion"; | ||
|
||
interface SearchFilterCheckboxProps { | ||
option: FilterOption; | ||
updateCheckedOption: (optionId: string, isChecked: boolean) => void; | ||
accordionTitle: string; | ||
query: Set<string>; | ||
|
||
} | ||
|
||
const SearchFilterCheckbox: React.FC<SearchFilterCheckboxProps> = ({ | ||
option, | ||
updateCheckedOption, | ||
accordionTitle, | ||
query, | ||
}) => { | ||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const checked = event.target.checked; | ||
updateCheckedOption(event.target.value, checked); | ||
}; | ||
|
||
const getNameAttribute = () => | ||
accordionTitle === "Agency" ? `agency-${option.id}` : option.id; | ||
|
||
return ( | ||
<FilterCheckbox | ||
id={option.id} | ||
label={option.label} | ||
name={getNameAttribute()} // value passed to server action {name: "{option.label}", value: "on" } (if no value provided) | ||
onChange={handleChange} | ||
checked={query.has(option.value)} | ||
value={option.value} | ||
/> | ||
); | ||
}; | ||
|
||
export default SearchFilterCheckbox; |
109 changes: 109 additions & 0 deletions
109
...d/src/app/[locale]/look/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
"use client"; | ||
|
||
import { useEffect, useState } from "react"; | ||
|
||
import { FilterOption } from "../SearchFilterAccordion"; | ||
import SearchFilterCheckbox from "../SearchFilterCheckbox"; | ||
import SearchFilterToggleAll from "../SearchFilterToggleAll"; | ||
import SectionLinkCount from "./SectionLinkCount"; | ||
import SectionLinkLabel from "./SectionLinkLabel"; | ||
|
||
interface SearchFilterSectionProps { | ||
option: FilterOption; | ||
updateCheckedOption: (optionId: string, isChecked: boolean) => void; | ||
toggleSelectAll: (isSelected: boolean, sectionId: string) => void; | ||
accordionTitle: string; | ||
isSectionAllSelected: boolean; | ||
isSectionNoneSelected: boolean; | ||
query: Set<string> | ||
} | ||
|
||
const SearchFilterSection: React.FC<SearchFilterSectionProps> = ({ | ||
option, | ||
updateCheckedOption, | ||
toggleSelectAll, | ||
accordionTitle, | ||
query, | ||
isSectionAllSelected, | ||
isSectionNoneSelected, | ||
}) => { | ||
const [childrenVisible, setChildrenVisible] = useState<boolean>(false); | ||
|
||
// TODO: Set this number per state/query params | ||
const [sectionCount, setSectionCount] = useState<number>(0); | ||
|
||
const handleSelectAll = () => { | ||
toggleSelectAll(true, option.id); | ||
}; | ||
|
||
const handleClearAll = () => { | ||
toggleSelectAll(false, option.id); | ||
}; | ||
|
||
useEffect(() => { | ||
if (option.children) { | ||
const newCount = option.children.filter( | ||
(child) => child.isChecked, | ||
).length; | ||
setSectionCount(newCount); | ||
} | ||
}, [option.children]); | ||
|
||
const getHiddenName = (name: string) => | ||
accordionTitle === "Agency" ? `agency-${name}` : name; | ||
|
||
return ( | ||
<div> | ||
<button | ||
className="usa-button usa-button--unstyled width-full border-bottom-2px border-base-lighter" | ||
onClick={(event) => { | ||
event.preventDefault(); | ||
setChildrenVisible(!childrenVisible); | ||
}} | ||
> | ||
<span className="grid-row flex-align-center margin-left-neg-1"> | ||
<SectionLinkLabel childrenVisible={childrenVisible} option={option} /> | ||
<SectionLinkCount sectionCount={sectionCount} /> | ||
</span> | ||
</button> | ||
{childrenVisible ? ( | ||
<div className="padding-y-1"> | ||
<SearchFilterToggleAll | ||
onSelectAll={handleSelectAll} | ||
onClearAll={handleClearAll} | ||
isAllSelected={isSectionAllSelected} | ||
isNoneSelected={isSectionNoneSelected} | ||
/> | ||
<ul className="usa-list usa-list--unstyled margin-left-4"> | ||
{option.children?.map((child) => ( | ||
<li key={child.id}> | ||
<SearchFilterCheckbox | ||
option={child} | ||
query={query} | ||
updateCheckedOption={updateCheckedOption} | ||
accordionTitle={accordionTitle} | ||
/> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
) : ( | ||
// Collapsed sections won't send checked values to the server action. | ||
// So we need hidden inputs. | ||
option.children?.map((child) => | ||
child.isChecked ? ( | ||
<input | ||
key={child.id} | ||
type="hidden" | ||
// name={child.value} | ||
name={getHiddenName(child.id)} | ||
value="on" | ||
/> | ||
) : null, | ||
) | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default SearchFilterSection; |
15 changes: 15 additions & 0 deletions
15
...tend/src/app/[locale]/look/SearchFilterAccordion/SearchFilterSection/SectionLinkCount.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export default function SectionLinkCount({ | ||
sectionCount, | ||
}: { | ||
sectionCount: number; | ||
}) { | ||
return ( | ||
<span className="grid-col flex-auto"> | ||
{!!sectionCount && ( | ||
<span className="usa-tag usa-tag--big radius-pill margin-left-1"> | ||
{sectionCount} | ||
</span> | ||
)} | ||
</span> | ||
); | ||
} |
34 changes: 34 additions & 0 deletions
34
...tend/src/app/[locale]/look/SearchFilterAccordion/SearchFilterSection/SectionLinkLabel.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { FilterOption } from "../SearchFilterAccordion"; | ||
import { Icon } from "@trussworks/react-uswds"; | ||
|
||
export default function SectionLinkLabel({ | ||
childrenVisible, | ||
option, | ||
}: { | ||
childrenVisible: boolean; | ||
option: FilterOption; | ||
}) { | ||
// When the arrow is down, the section is collapsed, and we can expand the section | ||
// When the arrow is up, the section is expanded, and we can collapse the section | ||
const ariaLabel = childrenVisible ? "Collapse section" : "Expand section"; | ||
|
||
return ( | ||
<span className="grid-col flex-fill"> | ||
{childrenVisible ? ( | ||
<Icon.ArrowDropUp | ||
size={5} | ||
className="text-middle" | ||
aria-label={ariaLabel} | ||
/> | ||
) : ( | ||
<Icon.ArrowDropDown | ||
size={5} | ||
className="text-middle" | ||
aria-label={ariaLabel} | ||
/> | ||
)} | ||
{option.label}{" "} | ||
{/* Assuming you want to display the option's label instead of its ID */} | ||
</span> | ||
); | ||
} |
48 changes: 48 additions & 0 deletions
48
frontend/src/app/[locale]/look/SearchFilterAccordion/SearchFilterToggleAll.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"use client"; | ||
|
||
interface SearchFilterToggleAllProps { | ||
isAllSelected: boolean; | ||
isNoneSelected: boolean; | ||
onSelectAll?: () => void; | ||
onClearAll?: () => void; | ||
} | ||
|
||
const SearchFilterToggleAll: React.FC<SearchFilterToggleAllProps> = ({ | ||
onSelectAll, | ||
onClearAll, | ||
isAllSelected, | ||
isNoneSelected, | ||
}) => { | ||
return ( | ||
<div className="grid-row"> | ||
<div className="grid-col-fill"> | ||
<button | ||
className="usa-button usa-button--unstyled font-sans-xs" | ||
onClick={(event) => { | ||
// form submission is done in useSearchFilter, so | ||
// prevent the onClick from submitting here. | ||
event.preventDefault(); | ||
onSelectAll?.(); | ||
}} | ||
disabled={isAllSelected} | ||
> | ||
Select All | ||
</button> | ||
</div> | ||
<div className="grid-col-fill text-right"> | ||
<button | ||
className="usa-button usa-button--unstyled font-sans-xs" | ||
onClick={(event) => { | ||
event.preventDefault(); | ||
onClearAll?.(); | ||
}} | ||
disabled={isNoneSelected} | ||
> | ||
Clear All | ||
</button> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default SearchFilterToggleAll; |
Oops, something went wrong.