Skip to content

Commit

Permalink
refactor: moar abstraction (#3078)
Browse files Browse the repository at this point in the history
* refactor: moar abstraction

* tiny nits, update tests

* fix test

---------

Co-authored-by: angelathe <angela.the@skylight.digital>
  • Loading branch information
mcmcgrath13 and angelathe authored Dec 19, 2024
1 parent d81ba81 commit bfd8fbf
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 200 deletions.
321 changes: 158 additions & 163 deletions containers/ecr-viewer/src/app/components/Filters.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"use client";

import React, { useCallback, useEffect, useState } from "react";
import React, {
ComponentType,
ReactNode,
useCallback,
useEffect,
useState,
} from "react";
import { Button, Icon } from "@trussworks/react-uswds";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import {
Expand Down Expand Up @@ -37,7 +43,6 @@ const Filters = () => {
*/
const FilterReportableConditions = () => {
const { searchParams, updateQueryParam } = useQueryParam();
const [isFilterBoxOpen, setIsFilterBoxOpen] = useState(false);
const [filterConditions, setFilterConditions] = useState<{
[key: string]: boolean;
}>({});
Expand Down Expand Up @@ -108,108 +113,66 @@ const FilterReportableConditions = () => {
};

return (
<div>
<div className="position-relative display-flex flex-column">
<Button
className={`margin-right-0 ${
isAllSelected ? "filter-button" : "filters-applied"
}`}
id="button-filter-conditions"
aria-label="Filter by reportable condition"
aria-haspopup="listbox"
aria-expanded={isFilterBoxOpen}
onClick={() => {
if (isFilterBoxOpen) {
resetFilterConditions(Object.keys(filterConditions));
}
setIsFilterBoxOpen(!isFilterBoxOpen);
}}
type="button"
<Filter
type="Reportable Condition"
btnClass={isAllSelected ? "filter-button" : "filters-applied"}
resetHandler={() => resetFilterConditions(Object.keys(filterConditions))}
icon={Icon.Coronavirus}
tag={
Object.keys(filterConditions).filter(
(key) => filterConditions[key] === true,
).length
}
submitHandler={() =>
updateQueryParam("condition", filterConditions, isAllSelected)
}
>
{/* Select All checkbox */}
<div className="display-flex flex-column">
<div
className="checkbox-color usa-checkbox padding-bottom-1 padding-x-105"
key={"all"}
>
<span className="square-205 usa-icon">
<Icon.Coronavirus aria-hidden className="square-205" />
</span>
<span className="text-ink"> Reportable Condition </span>
<span
className="usa-tag padding-05 bg-base-darker radius-md"
data-testid="filter-conditions-tag"
<input
id="condition-all"
className="usa-checkbox__input"
type="checkbox"
value="all"
onChange={handleSelectAll}
checked={isAllSelected}
/>
<label
className="line-height-sans-6 font-sans-xs margin-y-0 usa-checkbox__label"
htmlFor={"condition-all"}
>
{
Object.keys(filterConditions).filter(
(key) => filterConditions[key] === true,
).length
}
</span>
</Button>
{isAllSelected ? "Deselect all" : "Select all"}
</label>
</div>
<div className="border-top-1px border-base-lighter margin-x-105"></div>

{isFilterBoxOpen && (
<div className="usa-combo-box top-full left-0">
<form
onSubmit={(e) => {
e.preventDefault();
updateQueryParam("condition", filterConditions, isAllSelected);
setIsFilterBoxOpen(false);
}}
>
<fieldset className="usa-combo-box border-0 padding-0 margin-top-1 bg-white position-absolute radius-md shadow-2 z-top maxh-6205 width-4305">
{<FilterLegend title="Filter by Reportable Condition" />}

{/* Select All checkbox */}
<div className="display-flex flex-column">
<div
className="checkbox-color usa-checkbox padding-bottom-1 padding-x-105"
key={"all"}
>
<input
id="condition-all"
className="usa-checkbox__input"
type="checkbox"
value="all"
onChange={handleSelectAll}
checked={isAllSelected}
/>
<label
className="line-height-sans-6 font-sans-xs margin-y-0 usa-checkbox__label"
htmlFor={"condition-all"}
>
{isAllSelected ? "Deselect all" : "Select all"}
</label>
</div>
<div className="border-top-1px border-base-lighter margin-x-105"></div>

{/* (Scroll) Filter Conditions checkboxes */}
<div className="position-relative bg-white overflow-y-auto maxh-38 display-flex flex-column gap-1 padding-y-1 padding-x-105">
{Object.keys(filterConditions).map((condition) => (
<div
className="checkbox-color usa-checkbox"
key={condition}
>
<input
id={`condition-${condition}`}
className="usa-checkbox__input"
type="checkbox"
value={condition}
onChange={handleCheckboxChange}
checked={filterConditions[condition]}
/>
<label
className="line-height-sans-6 font-sans-xs margin-y-0 usa-checkbox__label"
htmlFor={`condition-${condition}`}
>
{condition}
</label>
</div>
))}
</div>
</div>

{<ApplyFilterButton ariaLabel="Apply Filter for Conditions" />}
</fieldset>
</form>
</div>
)}
{/* (Scroll) Filter Conditions checkboxes */}
<div className="position-relative bg-white overflow-y-auto maxh-38 display-flex flex-column gap-1 padding-y-1 padding-x-105">
{Object.keys(filterConditions).map((condition) => (
<div className="checkbox-color usa-checkbox" key={condition}>
<input
id={`condition-${condition}`}
className="usa-checkbox__input"
type="checkbox"
value={condition}
onChange={handleCheckboxChange}
checked={filterConditions[condition]}
/>
<label
className="line-height-sans-6 font-sans-xs margin-y-0 usa-checkbox__label"
htmlFor={`condition-${condition}`}
>
{condition}
</label>
</div>
))}
</div>
</div>
</div>
</Filter>
);
};

Expand All @@ -222,7 +185,6 @@ const FilterReportableConditions = () => {
const FilterByDate = () => {
const { searchParams, updateQueryParam } = useQueryParam();

const [isFilterBoxOpen, setIsFilterBoxOpen] = useState(false);
const [filterDateOption, setFilterDateOption] = useState("");

useEffect(() => {
Expand Down Expand Up @@ -261,8 +223,16 @@ const FilterByDate = () => {
}
};

const FilterDateOptions = () => {
return (
return (
<Filter
type="Received Date"
resetHandler={resetFilterDate}
icon={Icon.Event}
title={dateRangeLabels[filterDateOption as DateRangeOptions] || ""}
submitHandler={() =>
updateQueryParam("dateRange", filterDateOption, isFilterDateDefault)
}
>
<div className="display-flex flex-column">
{Object.values(DateRangeOptions).map((option) => (
<div
Expand All @@ -288,57 +258,7 @@ const FilterByDate = () => {
))}
<div className="border-top-1px border-base-lighter margin-x-105"></div>
</div>
);
};

return (
<div>
<div className="position-relative display-flex flex-column">
<Button
className={"margin-right-0 filters-applied"}
id="button-filter-date"
aria-label="Filter by Date"
aria-haspopup="listbox"
aria-expanded={isFilterBoxOpen}
onClick={() => {
if (isFilterBoxOpen) {
resetFilterDate();
}
setIsFilterBoxOpen(!isFilterBoxOpen);
}}
type="button"
>
<span className="square-205 usa-icon">
<Icon.Event aria-hidden className="square-205" />
</span>
<span className="text-ink">
{dateRangeLabels[filterDateOption as DateRangeOptions] || ""}
</span>
</Button>

{isFilterBoxOpen && (
<div className="usa-combo-box top-full left-0">
<form
onSubmit={(e) => {
e.preventDefault();
updateQueryParam(
"dateRange",
filterDateOption,
isFilterDateDefault,
);
setIsFilterBoxOpen(false);
}}
>
<fieldset className="usa-combo-box border-0 padding-0 margin-top-1 bg-white position-absolute radius-md shadow-2 z-top maxh-6205 width-28">
{<FilterLegend title="Filter by Received Date" />}
<FilterDateOptions />
{<ApplyFilterButton ariaLabel="Apply Filter for Date" />}
</fieldset>
</form>
</div>
)}
</div>
</div>
</Filter>
);
};

Expand Down Expand Up @@ -407,34 +327,109 @@ const useQueryParam = () => {
return { searchParams, updateQueryParam };
};

const Filter = ({
btnClass = "filters-applied",
type,
title = "",
icon: IconTag,
tag = "",
resetHandler,
submitHandler,
children,
}: {
btnClass?: string;
type: string;
title?: string;
icon: ComponentType<{ className?: string }>;
tag?: ReactNode;
resetHandler: () => void;
submitHandler: () => void;
children: ReactNode;
}) => {
const [isFilterBoxOpen, setIsFilterBoxOpen] = useState(false);
const openBtnRef = React.useRef<HTMLElement | null>(null);

return (
<div>
<div className="position-relative display-flex flex-column">
<Button
className={`margin-right-0 ${btnClass}`}
aria-label={`Filter by ${type}`}
aria-haspopup="listbox"
aria-expanded={isFilterBoxOpen}
onClick={() => {
if (isFilterBoxOpen) {
resetHandler();
}
setIsFilterBoxOpen(!isFilterBoxOpen);
}}
type="button"
>
<span ref={openBtnRef} className="square-205 usa-icon">
<IconTag aria-hidden className="square-205" />
</span>
<span className="text-ink">{title || type}</span>
{tag && (
<span
className="usa-tag padding-05 bg-base-darker radius-md"
data-testid="filter-tag"
>
{tag}
</span>
)}
</Button>

{isFilterBoxOpen && (
<div className="usa-combo-box top-full left-0">
<form
onSubmit={(e) => {
e.preventDefault();
submitHandler();
setIsFilterBoxOpen(false);
openBtnRef?.current?.parentElement?.focus();
}}
>
<fieldset className="usa-combo-box border-0 padding-0 margin-top-1 bg-white position-absolute radius-md shadow-2 z-top maxh-6205 width-4305">
<FilterLegend type={type} />
{children}
<ApplyFilterButton type={type} />
</fieldset>
</form>
</div>
)}
</div>
</div>
);
};

/**
* A component to render a filter legend (title).
* @param props - The component props containing legend title.
* @param props.title The title to display in the legend.
* @param props - React props
* @param props.type - The type of filter
* @returns - The rendered legend element
*/
const FilterLegend = ({ title }: { title: string }) => {
const FilterLegend = ({ type }: { type: string }) => {
return (
<legend className="line-height-sans-6 text-bold font-sans-xs bg-white width-full padding-y-1 padding-x-105">
{title}
Filter by {type}
</legend>
);
};

/**
* A button component for applying a filter.
* @param props - The component props containing id and ariaLabel
* @param props.ariaLabel - The aria label for the button
* @returns The rendered button element
* @param props - React props
* @param props.type - The type of filter
* @returns - The rendered button element
*/
const ApplyFilterButton = ({ ariaLabel }: { ariaLabel: string }) => {
const ApplyFilterButton = ({ type }: { type: string }) => {
return (
<div className="display-flex flex-column flex-stretch padding-x-105">
<div className="border-top-1px border-base-lighter margin-x-neg-105"></div>
<Button
type="submit"
className="margin-y-1 margin-x-0 padding-y-1 padding-x-205 flex-fill"
aria-label={ariaLabel}
aria-label={`Apply Filter for ${type}`}
>
Apply Filter
</Button>
Expand Down
Loading

0 comments on commit bfd8fbf

Please sign in to comment.