Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue 704] Breadcrumbs #776

Merged
merged 12 commits into from
Nov 30, 2023
67 changes: 67 additions & 0 deletions frontend/src/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
Breadcrumb,
BreadcrumbBar,
BreadcrumbLink,
GridContainer,
} from "@trussworks/react-uswds";

export type Breadcrumb = {
title: string;
path: string;
};

export type BreadcrumbList = Breadcrumb[];

type Props = {
breadcrumbList: BreadcrumbList;
};

const Breadcrumbs = ({ breadcrumbList }: Props) => {
const rdfaMetadata = {
ol: {
vocab: "http://schema.org/",
typeof: "BreadcrumbList",
},
li: {
property: "itemListElement",
typeof: "ListItem",
},
a: {
property: "item",
typeof: "WebPage",
},
};

const breadcrumArray = breadcrumbList.map((breadcrumbInfo, i) => {
return (
<Breadcrumb
key={breadcrumbInfo.title + "-crumb"}
current={i + 1 === breadcrumbList.length}
{...rdfaMetadata.li}
>
{i + 1 !== breadcrumbList.length ? (
<BreadcrumbLink href={breadcrumbInfo.path} {...rdfaMetadata.a}>
{}
<span property="name">{breadcrumbInfo.title}</span>
</BreadcrumbLink>
) : (
<span property="name">{breadcrumbInfo.title}</span>
)}
<meta property="position" content={(i + 1).toString()} />
</Breadcrumb>
);
});

return (
<GridContainer
className="padding-y-1 tablet:padding-y-3 desktop-lg:padding-y-6"
data-testid="breadcrumb"
>
<BreadcrumbBar listProps={{ ...rdfaMetadata.ol }}>
{breadcrumArray}
</BreadcrumbBar>
</GridContainer>
);
};

export default Breadcrumbs;
8 changes: 8 additions & 0 deletions frontend/src/constants/breadcrumbs.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we infer hierarchy from routes so we don't have to define the breadcrumb trail for each page? Might there be downsides for doing that? We'd still need a lookup for the title strings. 🤔 Just thinking out loud here. No change required, maybe just food for thought as we get into more complex info architecture and page/route structures.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that too and was considering using https://nextjs.org/docs/app/api-reference/functions/use-pathname to get and and then break up the path. But realized as you mentioned we'd still need to look up the title strings based on the path, so doesn't really save us much and can be harder to track down if it breaks.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Breadcrumb, BreadcrumbList } from "src/components/Breadcrumbs";

const HOME: Breadcrumb = { title: "Home", path: "/" };
const RESEARCH: Breadcrumb = { title: "Research", path: "research/" };
const PROCESS: Breadcrumb = { title: "Process", path: "process/" };

export const RESEARCH_CRUMBS: BreadcrumbList = [HOME, RESEARCH];
export const PROCESS_CRUMBS: BreadcrumbList = [HOME, PROCESS];
3 changes: 3 additions & 0 deletions frontend/src/pages/process.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { GetStaticProps, NextPage } from "next";
import { PROCESS_CRUMBS } from "src/constants/breadcrumbs";
import { ExternalRoutes } from "src/constants/routes";

import { Trans, useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";

import Breadcrumbs from "src/components/Breadcrumbs";
import PageSEO from "src/components/PageSEO";
import FullWidthAlert from "../components/FullWidthAlert";

Expand All @@ -28,6 +30,7 @@ const Process: NextPage = () => {
}}
/>
</FullWidthAlert>
<Breadcrumbs breadcrumbList={PROCESS_CRUMBS} />
Process Placeholder
</>
);
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/pages/research.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { GetStaticProps, NextPage } from "next";
import { RESEARCH_CRUMBS } from "src/constants/breadcrumbs";
import { ExternalRoutes } from "src/constants/routes";

import { Trans, useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";

import Breadcrumbs from "src/components/Breadcrumbs";
import PageSEO from "src/components/PageSEO";
import FullWidthAlert from "../components/FullWidthAlert";

Expand All @@ -28,6 +30,7 @@ const Research: NextPage = () => {
}}
/>
</FullWidthAlert>
<Breadcrumbs breadcrumbList={RESEARCH_CRUMBS} />
Research Placeholder
</>
);
Expand Down
36 changes: 36 additions & 0 deletions frontend/stories/components/Breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Meta } from "@storybook/react";
import { PROCESS_CRUMBS, RESEARCH_CRUMBS } from "src/constants/breadcrumbs";

import Breadcrumbs from "src/components/Breadcrumbs";

const meta: Meta<typeof Breadcrumbs> = {
title: "Components/Breadcrumbs",
component: Breadcrumbs,
};
export default meta;

export const Home = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we'd ever show breadcrumbs on root. Okay to show how it'd work if we did tho.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wasn't sure what to use for the default story. I could change it to be either Research or Process 🤷, let me know if you have a preference

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably fine as-is. We can revisit in the future as our route/page requirements become more complex.

parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/lpKPdyTyLJB5JArxhGjJnE/beta.grants.gov?type=design&node-id=918%3A1698&mode=design&t=rKuHZ4QiepVfLvwq-1",
},
},
args: {
breadcrumbList: [{ title: "Home", path: "/" }],
},
};

export const Research = {
parameters: Home.parameters,
args: {
breadcrumbList: RESEARCH_CRUMBS,
},
};

export const Process = {
parameters: Home.parameters,
args: {
breadcrumbList: PROCESS_CRUMBS,
},
};
12 changes: 12 additions & 0 deletions frontend/tests/components/Breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { render, screen } from "@testing-library/react";
import { RESEARCH_CRUMBS } from "src/constants/breadcrumbs";

import Breadcrumbs from "src/components/Breadcrumbs";

describe("Breadcrumb", () => {
it("Renders without errors", () => {
render(<Breadcrumbs breadcrumbList={RESEARCH_CRUMBS} />);
const bc = screen.getByTestId("breadcrumb");
expect(bc).toBeInTheDocument();
});
});
2 changes: 1 addition & 1 deletion frontend/tests/pages/process.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen, waitFor } from "@testing-library/react";
import { axe } from "jest-axe";
import Process from "src/pages/process";

describe("Index", () => {
describe("Process", () => {
it("renders alert with grants.gov link", () => {
render(<Process />);

Expand Down
2 changes: 1 addition & 1 deletion frontend/tests/pages/research.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen, waitFor } from "@testing-library/react";
import { axe } from "jest-axe";
import Research from "src/pages/research";

describe("Index", () => {
describe("Research", () => {
it("renders alert with grants.gov link", () => {
render(<Research />);

Expand Down