Skip to content

Commit

Permalink
feat(add-breadcrumbs): Add Bread Crumbs to the Mako Web Application (#…
Browse files Browse the repository at this point in the history
…162)

* initial markup for the breadcrumb component

* add unit tests

* add initial breadcrumbs with light styles

* rough draft breadcrumbs

* update typescript in ui service

* remove tsdoc support. nice to have, but doesn't belong in this effort

* remove state from router. this isn't needed currently

* remove breadcrumbs from welcome page (this was for testing before)

* fix tests

* add a comment as a reminder

* add breadcrumbs to the various landing pages

* change config to address verbiage issues

* add breadcrumbs to package detail pages

* fix deploy error with typescript

* fix breadcrumb verbiage to match titles for landing pages
  • Loading branch information
13bfrancis authored Oct 17, 2023
1 parent f6f4174 commit d57f2df
Show file tree
Hide file tree
Showing 11 changed files with 1,394 additions and 1,133 deletions.
7 changes: 4 additions & 3 deletions src/services/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@
"aws-amplify": "^5.2.5",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"shared-types": "*",
"shared-utils": "*",
"date-fns": "^2.30.0",
"export-to-csv": "^0.2.1",
"file-saver": "^2.0.5",
Expand All @@ -61,6 +59,8 @@
"react-loader-spinner": "^5.3.4",
"react-router-dom": "^6.10.0",
"react-select": "^5.7.4",
"shared-types": "*",
"shared-utils": "*",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uuid": "^9.0.0",
Expand All @@ -71,6 +71,7 @@
"@tailwindcss/typography": "^0.5.10",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^20.4.2",
"@types/react": "^18.0.28",
Expand All @@ -88,7 +89,7 @@
"postcss": "^8.4.31",
"serverless-s3-sync": "^3.1.0",
"tailwindcss": "^3.3.1",
"typescript": "^4.9.3",
"typescript": "^5.2.0",
"vite": "^4.2.0",
"vitest": "^0.30.1"
}
Expand Down
70 changes: 70 additions & 0 deletions src/services/ui/src/components/BreadCrumb/BreadCrumb.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, test, expect, beforeAll, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { BreadCrumb, BreadCrumbBar } from "./BreadCrumb";
import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom";

export const LocationDisplay = () => {
const location = useLocation();

return <div data-testid="location-display">{location.pathname}</div>;
};

describe("Bread Crumb Tests", () => {
describe("Bread Crumb Routing", () => {
test("Sucessfully navigate using breadcrumbs", async () => {
render(
<>
<Routes>
<Route path="/" />
<Route path="/test" />
<Route path="/test/:id" />
</Routes>
<BreadCrumbBar>
<BreadCrumb to="/test">Click Me</BreadCrumb>
</BreadCrumbBar>
<LocationDisplay />
</>,
{ wrapper: BrowserRouter }
);

const user = userEvent.setup();

await user.click(screen.getByText(/click me/i));
expect(screen.getByText("/test")).toBeInTheDocument();
});
});

describe("Bread Crumb Interations", async () => {
beforeEach(() => {
render(
<BreadCrumbBar>
<BreadCrumb data-testid="home" to="/">
Home
</BreadCrumb>
<BreadCrumb data-testid="dashboard" to="/test">
Test Dashboard
</BreadCrumb>
<BreadCrumb data-testid="item" to="/test/:id" active={false}>
Test Item
</BreadCrumb>
</BreadCrumbBar>,
{
wrapper: BrowserRouter,
}
);
});

test("active element is styled different", async () => {
const homeBreadCrumb = screen.getByText("Home");
const dashboardBreadCrumb = screen.getByText("Test Dashboard");
const itemBreadCrumb = screen.getByText("Test Item");

expect(homeBreadCrumb.classList.contains("underline")).toBeTruthy();
expect(dashboardBreadCrumb.classList.contains("underline")).toBeTruthy();
expect(itemBreadCrumb.classList.contains("underline")).toBeFalsy();
});
});

// TODO: Write a test to test the functionality of the BreadCrumbs component with a test config passed in
});
80 changes: 80 additions & 0 deletions src/services/ui/src/components/BreadCrumb/BreadCrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Link } from "react-router-dom";
import { type ReactNode } from "react";
import { ChevronRight } from "lucide-react";
import { BreadCrumbConfig } from "./bread-crumb-config";

type BreadCrumbsProps = {
options: BreadCrumbConfig[];
};

export const BreadCrumbs = ({ options }: BreadCrumbsProps) => {
const defaultBreadCrumb = options.find((option) => option.default);

return (
<BreadCrumbBar>
{defaultBreadCrumb && (
<BreadCrumb to={defaultBreadCrumb.to} showSeperator={false}>
{defaultBreadCrumb.displayText}
</BreadCrumb>
)}
{/* After this we map over the config and check to see if the breadcrumb needs to be displayed. Proper route paths are important here. It should be hierarchical */}
{options
.filter((option) => !option.default)
.filter((option) => window.location.href.includes(option.to))
.toSorted((option, prevOption) => option.order - prevOption.order)
.map(({ displayText, to }, index, optionsArray) => {
return (
<BreadCrumb
key={displayText}
to={to}
active={index !== optionsArray.length - 1}
>
{displayText}
</BreadCrumb>
);
})}
</BreadCrumbBar>
);
};

type BreadCrumbProps = {
to: string;
active?: boolean;
showSeperator?: boolean;
seperator?: ReactNode;
};

export const BreadCrumb = ({
to,
seperator = <BreadCrumbSeperator />,
showSeperator = true,
active = true,
children,
}: React.PropsWithChildren<BreadCrumbProps>) => {
return (
<li className="flex items-center">
{showSeperator && <span>{seperator}</span>}

{active && (
<Link to={to} className="underline text-sky-600 hover:text-sky-800">
{children}
</Link>
)}
{!active && <span aria-disabled>{children}</span>}
</li>
);
};

export const BreadCrumbSeperator = () => <ChevronRight className="w-5 h-5" />;

export const BreadCrumbBar = ({ children }: React.PropsWithChildren) => {
return (
<nav
role="navigation"
aria-label="breadcrumbs for spa or waiver choices"
className="mt-4"
>
<ul className="flex gap-1">{children}</ul>
</nav>
);
};
90 changes: 90 additions & 0 deletions src/services/ui/src/components/BreadCrumb/bread-crumb-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { ROUTES } from "@/routes";

export type BreadCrumbConfig = {
default?: boolean;
order: number;
to: string;
displayText: string;
};

export const BREAD_CRUMB_CONFIG_NEW_SUBMISSION: BreadCrumbConfig[] = [
{
default: true,
displayText: "Dashboard",
to: ROUTES.DASHBOARD,
order: 1,
},
{
displayText: "Submission Type",
to: ROUTES.NEW_SUBMISSION_OPTIONS,
order: 2,
},
{
displayText: "SPA Type",
to: ROUTES.SPA_SUBMISSION_OPTIONS,
order: 3,
},
{
displayText: "Waiver Type",
to: ROUTES.WAIVER_SUBMISSION_OPTIONS,
order: 3,
},
{
displayText: "1915(b) Waiver Type",
to: ROUTES.B_WAIVER_SUBMISSION_OPTIONS,
order: 4,
},
{
displayText: "Medicaid SPA Type",
to: ROUTES.MEDICAID_SPA_SUB_OPTIONS,
order: 4,
},
{
displayText: "CHIP SPA Type",
to: ROUTES.CHIP_SPA_SUB_OPTIONS,
order: 4,
},
{
displayText: "CHIP Eligibility SPAs",
to: ROUTES.CHIP_ELIGIBILITY_LANDING,
order: 5,
},
{
displayText:
"Medicaid Alternative Benefits Plans (ABP), and Medicaid Premiums and Cost Sharing",
to: ROUTES.MEDICAID_ABP_LANDING,
order: 5,
},
{
displayText:
"Medicaid Eligibility, Enrollment, Administration, and Health Homes",
to: ROUTES.MEDICAID_ELIGIBILITY_LANDING,
order: 5,
},
{
displayText: "1915(b)(4) FFS Selective Contracting Waiver Types",
to: ROUTES.B4_WAIVER_OPTIONS,
order: 5,
},
{
displayText: "1915(b) Comprehensive (Capitated) Waiver Authority Types",
to: ROUTES.BCAP_WAIVER_OPTIONS,
order: 5,
},
];

export const BREAD_CRUMB_CONFIG_PACKAGE_DETAILS = (data: {
id: string;
}): BreadCrumbConfig[] => [
{
displayText: "Dashboard",
order: 1,
default: true,
to: ROUTES.DASHBOARD,
},
{
displayText: `${data.id}`,
order: 2,
to: ROUTES.DETAILS,
},
];
1 change: 1 addition & 0 deletions src/services/ui/src/components/BreadCrumb/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./BreadCrumb";
3 changes: 3 additions & 0 deletions src/services/ui/src/pages/create/create-options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
WAIVER_OPTIONS,
} from "@/pages/create/options";
import { SimplePageContainer } from "@/components";
import { BreadCrumbs } from "@/components/BreadCrumb";
import { BREAD_CRUMB_CONFIG_NEW_SUBMISSION } from "@/components/BreadCrumb/bread-crumb-config";

/** Can be removed once page title bar with back nav is integrated */
export const SimplePageTitle = ({ title }: { title: string }) => (
Expand All @@ -32,6 +34,7 @@ type OptionsPageProps = {
const OptionsPage = ({ options, title, fieldsetLegend }: OptionsPageProps) => {
return (
<SimplePageContainer>
<BreadCrumbs options={BREAD_CRUMB_CONFIG_NEW_SUBMISSION} />
<SimplePageTitle title={title} />
<OptionFieldset legend={fieldsetLegend}>
{options.map((opt, idx) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { PropsWithChildren, ReactElement } from "react";
import { SimplePageTitle } from "@/pages/create/create-options";
import { SimplePageContainer } from "@/components";
import { FAQ_SECTION, ROUTES } from "@/routes";
import { BreadCrumbs } from "@/components/BreadCrumb";
import { BREAD_CRUMB_CONFIG_NEW_SUBMISSION } from "@/components/BreadCrumb/bread-crumb-config";
export enum EXTERNAL_APP {
MAC_PRO = "https://www.medicaid.gov/resources-for-states/medicaid-and-chip-program-macpro-portal/index.html#MACPro",
MMDL = "https://wms-mmdl.cms.gov/MMDL/faces/portal.jsp",
Expand Down Expand Up @@ -53,6 +55,7 @@ const ExternalAppLandingPage = ({
}: ExternalAppLandingPageConfig) => {
return (
<SimplePageContainer>
<BreadCrumbs options={BREAD_CRUMB_CONFIG_NEW_SUBMISSION} />
{/* TODO: Replace simple page title bar with breadcrumbs */}
<SimplePageTitle title={pageTitle} />
<div className="flex flex-col items-center justify-center m-4 pt-4 pb-12">
Expand Down
8 changes: 6 additions & 2 deletions src/services/ui/src/pages/detail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { OsHit, OsMainSourceItem } from "shared-types";
import { useQuery } from "@/hooks";
import { useGetItem } from "@/api";
import { DetailNav } from "./detailNav";
import { BreadCrumbs } from "@/components/BreadCrumb";
import { BREAD_CRUMB_CONFIG_PACKAGE_DETAILS } from "@/components/BreadCrumb/bread-crumb-config";

export const DetailsContent = ({
data,
Expand Down Expand Up @@ -82,6 +84,7 @@ export const Details = () => {
const query = useQuery();
const id = query.get("id") as string;
const { data, isLoading, error } = useGetItem(id);

if (isLoading) {
return <LoadingSpinner />;
}
Expand All @@ -91,8 +94,9 @@ export const Details = () => {

return (
<>
<DetailNav id={id} type={data?._source.planType} />
<div className="max-w-screen-xl mx-auto py-8 px-4 lg:px-8">
{/* <DetailNav id={id} type={data?._source.planType} /> */}
<div className="max-w-screen-xl mx-auto py-1 px-4 lg:px-8 flex flex-col gap-4">
<BreadCrumbs options={BREAD_CRUMB_CONFIG_PACKAGE_DETAILS({ id })} />
<DetailsContent data={data} />
</div>
</>
Expand Down
6 changes: 3 additions & 3 deletions src/services/ui/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export enum ROUTES {
B_WAIVER_SUBMISSION_OPTIONS = "/new-submission/waiver/b",
B4_WAIVER_OPTIONS = "/new-submission/waiver/b/b4",
BCAP_WAIVER_OPTIONS = "/new-submission/waiver/b/capitated",
MEDICAID_ABP_LANDING = "/landing/medicaid-abp",
MEDICAID_ELIGIBILITY_LANDING = "/landing/medicaid-eligibility",
CHIP_ELIGIBILITY_LANDING = "/landing/chip-eligibility",
MEDICAID_ABP_LANDING = "/new-submission/spa/medicaid/landing/medicaid-abp",
MEDICAID_ELIGIBILITY_LANDING = "/new-submission/spa/medicaid/landing/medicaid-eligibility",
CHIP_ELIGIBILITY_LANDING = "/new-submission/spa/chip/landing/chip-eligibility",
CREATE = "/create",
}

Expand Down
2 changes: 1 addition & 1 deletion src/services/ui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"lib": ["DOM", "DOM.Iterable", "ESNext", "ES2023"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
Expand Down
Loading

0 comments on commit d57f2df

Please sign in to comment.