Skip to content

Commit

Permalink
feat(Link): add a sbl-frontend Link that uses react-router when needed (
Browse files Browse the repository at this point in the history
#201)

This integrates cfpb/design-system-react#294
into the app, adding a little composition to the DSR component to
automatically switch from normal anchor links to `react-router` links
depending on the `href`.

I wanted to make the transition as easy as possible: it's the same props
as the DSR `Link`. As long as you're importing the Link from the app
instead of the `DSR`, you don't have to worry about figuring out which
links should have `isRouterLink` or not.

If we like this approach, I could see us raising this up into the DSR as
a "SBL-specific component" in a later PR.

**Note: don't merge this in until
cfpb/design-system-react#294 is merged and [this
line](https://github.com/cfpb/sbl-frontend/compare/198-integrate-react-router-links?expand=1#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519R37)
that temporarily updates the DSR is reverted**

## Changes

- adds new `Link` and `ListLink` components to the App that switch
between `a` and `RouterLink` depending on what `href` is given
- updates all instances of `Link` / `ListLink` across the app to use the
new components
- updates usage of plain `<a>` anchors to use `Link`
- dedupes `react-router-dom` in the vite config that works with this
change in the DSR to allow for `react-router` enabled `Links` to work
within the same instance of `react-router` (what a pain this was)

## How to test this PR

1. Do external and internal links still work?
2. Do internal links (particularly breadcrumbs) seem faster with less
page jank?

## Screenshots

https://github.com/cfpb/sbl-frontend/assets/19983248/a239a808-b200-4244-b910-b6f9141ba244
  • Loading branch information
billhimmelsbach authored Jan 30, 2024
1 parent a704ad3 commit d0a7142
Show file tree
Hide file tree
Showing 28 changed files with 253 additions and 147 deletions.
218 changes: 109 additions & 109 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import type { UserProfileObject } from 'api/fetchUserProfile';
import fetchUserProfile from 'api/fetchUserProfile';
import useSblAuth from 'api/useSblAuth';
import classNames from 'classnames';
import { Link } from 'components/Link';
import LoadingOrError from 'components/LoadingOrError';
import { Button, FooterCfGov, Link, PageHeader } from 'design-system-react';
import ScrollToTop from 'components/ScrollToTop';
import { Button, FooterCfGov, PageHeader } from 'design-system-react';
import 'design-system-react/style.css';
import Error500 from 'pages/Error/Error500';
import { NotFound404 } from 'pages/Error/NotFound404';
Expand Down Expand Up @@ -223,6 +225,7 @@ export default function App(): ReactElement {

return (
<BrowserRouter>
<ScrollToTop />
<Suspense fallback={<LoadingOrError />}>
<Routes>
<Route path='/' element={<BasicLayout />}>
Expand Down
3 changes: 2 additions & 1 deletion src/components/AssociatedInstitution.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Icon, Link, ListItem } from 'design-system-react';
import { Link } from 'components/Link';
import { Icon, ListItem } from 'design-system-react';
import type { InstitutionDetailsApiType } from 'pages/Filing/InstitutionDetails/institutionDetails.type';

export function AssociatedInstitution({
Expand Down
2 changes: 1 addition & 1 deletion src/components/CommonLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Link } from 'design-system-react';
import { Link } from 'components/Link';
import type { ReactElement } from 'react';

function GLIEF(): ReactElement {
Expand Down
6 changes: 4 additions & 2 deletions src/components/ExternalLink.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Icon } from 'design-system-react';

import { Link } from 'components/Link';

interface ExternalLinkProperties {
href: string;
label: string;
Expand All @@ -9,9 +11,9 @@ export function ExternalLink({
label,
}: ExternalLinkProperties): JSX.Element {
return (
<a href={href}>
<Link href={href}>
{label} <Icon name='external-link' />
</a>
</Link>
);
}

Expand Down
76 changes: 76 additions & 0 deletions src/components/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// this component could potentially be lifted with others into the DSR in a later refactor
// see: https://github.com/cfpb/sbl-frontend/issues/200

import type { LinkProperties as DesignSystemReactLinkProperties } from 'design-system-react';
import {
Link as DesignSystemReactLink,
ListLink as DesignSystemReactListLink,
} from 'design-system-react';

const getIsLinkExternal = (url: string | undefined): boolean => {
if (url === undefined) {
return false;
}
return url.startsWith('http');
};

const getIsRouterUsageInferred = (
href: string | undefined,
isRouterLink: boolean | undefined,
): boolean => isRouterLink === undefined && !getIsLinkExternal(href);

const getIsRouterLink = (
href: string | undefined,
isRouterLink: boolean | undefined,
): boolean => {
const isRouterUsageInferred = getIsRouterUsageInferred(href, isRouterLink);
if (isRouterUsageInferred) {
return true;
}
if (isRouterLink === undefined) {
return false;
}
return isRouterLink;
};

interface LinkProperties extends DesignSystemReactLinkProperties {
// design system react's Link component correctly allows undefined values without defaultProps
/* eslint-disable react/require-default-props */
href?: string | undefined;
isRouterLink?: boolean | undefined;
/* eslint-enable react/require-default-props */
}

export function Link({
children,
href,
isRouterLink,
...others
}: LinkProperties): JSX.Element {
return (
<DesignSystemReactLink
href={href}
isRouterLink={getIsRouterLink(href, isRouterLink)}
{...others}
>
{children}
</DesignSystemReactLink>
);
}

export function ListLink({
href,
isRouterLink,
children,
...others
}: LinkProperties): JSX.Element {
return (
<DesignSystemReactListLink
href={href}
isRouterLink={getIsRouterLink(href, isRouterLink)}
{...others}
>
{children}
</DesignSystemReactListLink>
);
}
12 changes: 12 additions & 0 deletions src/components/ScrollToTop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

export default function ScrollToTop(): null {
const { pathname } = useLocation();

useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);

return null;
}
4 changes: 3 additions & 1 deletion src/pages/AuthenticatedLanding/FileHmda.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Link, List, ListItem } from 'design-system-react';
import { List, ListItem } from 'design-system-react';

import { Link } from 'components/Link';
import { SubsectionWrapper } from './SubsectionWrapper';

export function FileHmda(): JSX.Element {
Expand Down
3 changes: 2 additions & 1 deletion src/pages/AuthenticatedLanding/FileSbl.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Heading, List, ListLink, Paragraph } from 'design-system-react';
import { ListLink } from 'components/Link';
import { Heading, List, Paragraph } from 'design-system-react';
import { SubsectionWrapper } from './SubsectionWrapper';

export function FileSbl(): JSX.Element {
Expand Down
3 changes: 2 additions & 1 deletion src/pages/AuthenticatedLanding/MailingListSignup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button, Label, Link, TextInput } from 'design-system-react';
import { Link } from 'components/Link';
import { Button, Label, TextInput } from 'design-system-react';
import './MailingListSignup.less';

export function MailingListSignup(): JSX.Element {
Expand Down
3 changes: 2 additions & 1 deletion src/pages/AuthenticatedLanding/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import './Landing.less';

import AdditionalResources from 'components/AdditionalResources';
import { Divider, Hero, Layout, ListLink } from 'design-system-react';
import { ListLink } from 'components/Link';
import { Divider, Hero, Layout } from 'design-system-react';
import type { ReactElement } from 'react';
import { FileSbl } from './FileSbl';
import { ReviewInstitutions } from './ReviewInstitutions';
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Error/Error500.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Expandable, Heading, Link, Paragraph } from 'design-system-react';
import { Link } from 'components/Link';
import { Expandable, Heading, Paragraph } from 'design-system-react';
import type { ReactElement } from 'react';
import { useLocation } from 'react-router-dom';
import { sblHelpLink } from 'utils/common';
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Error/NotFound404.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Heading, Link, Paragraph } from 'design-system-react';
import { Link } from 'components/Link';
import { Heading, Paragraph } from 'design-system-react';
import type { ReactElement } from 'react';
import './error.less';

Expand Down
3 changes: 1 addition & 2 deletions src/pages/Filing/FilingHome.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import useSblAuth from 'api/useSblAuth';
import AdditionalResources from 'components/AdditionalResources';
import { Link, ListLink } from 'components/Link';
import {
Button,
Divider,
Heading,
Hero,
Layout,
Link,
List,
ListLink,
Paragraph,
WellContent,
} from 'design-system-react';
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Filing/InstitutionDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { fetchInstitutionDetails } from 'api/fetchInstitutionDetails';
import useSblAuth from 'api/useSblAuth';
import CrumbTrail from 'components/CrumbTrail';
import { Link } from 'components/Link';
import { Grid } from 'design-system-react';
import { useParams } from 'react-router-dom';
import { AffiliateInformation } from './AffiliateInformation';
Expand All @@ -27,9 +28,9 @@ function InstitutionDetails(): JSX.Element {
<Grid.Column width={8}>
<main id='main-content' className='mb-[2.813rem] mt-[1.875rem]'>
<CrumbTrail>
<a href='/landing' key='home'>
<Link href='/landing' key='home'>
Platform home
</a>
</Link>
</CrumbTrail>
<PageIntro />
<FinancialInstitutionDetails data={data} />
Expand Down
6 changes: 3 additions & 3 deletions src/pages/Filing/PaperworkNotice.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CrumbTrail from 'components/CrumbTrail';
import { Layout, Link, TextIntroduction } from 'design-system-react';
import { Link } from 'components/Link';
import { Layout, TextIntroduction } from 'design-system-react';
import type { ReactElement } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import './Notices.less';

function PaperworkNotice(): ReactElement {
Expand All @@ -10,7 +10,7 @@ function PaperworkNotice(): ReactElement {
<Layout.Wrapper>
<Layout.Content flushBottom>
<CrumbTrail>
<RouterLink to='/'>Platform home</RouterLink>
<Link href='/'>Platform home</Link>
</CrumbTrail>
<TextIntroduction
heading='Paperwork Reduction Act statement'
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Filing/PrivacyNotice.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CrumbTrail from 'components/CrumbTrail';
import { Link } from 'components/Link';
import {
Layout,
List,
Expand All @@ -7,7 +8,6 @@ import {
TextIntroduction,
} from 'design-system-react';
import type { ReactElement } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import './Notices.less';

function PrivacyNotice(): ReactElement {
Expand All @@ -16,7 +16,7 @@ function PrivacyNotice(): ReactElement {
<Layout.Wrapper>
<Layout.Content flushBottom>
<CrumbTrail>
<RouterLink to='/'>Platform home</RouterLink>
<Link href='/'>Platform home</Link>
</CrumbTrail>
<TextIntroduction
heading='Privacy Act notice'
Expand Down
9 changes: 2 additions & 7 deletions src/pages/Filing/ViewUserProfile/AssociatedInstitutions.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import {
Heading,
Link,
List,
Paragraph,
WellContainer,
} from 'design-system-react';
import { Link } from 'components/Link';
import { Heading, List, Paragraph, WellContainer } from 'design-system-react';
import type { InstitutionDetailsApiType } from 'pages/Filing/InstitutionDetails/institutionDetails.type';
import { AssociatedInstitution } from '../../../components/AssociatedInstitution';

Expand Down
3 changes: 2 additions & 1 deletion src/pages/Filing/ViewUserProfile/UserInformation.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UserProfileObject } from 'api/fetchUserProfile';
import { Heading, Link, Paragraph, WellContainer } from 'design-system-react';
import { Link } from 'components/Link';
import { Heading, Paragraph, WellContainer } from 'design-system-react';
import { DisplayField } from '../InstitutionDetails/DisplayField';

export default function UserInformation({
Expand Down
7 changes: 4 additions & 3 deletions src/pages/Filing/ViewUserProfile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import fetchAssociatedInstitutions from 'api/fetchAssociatedInstitutions';
import fetchUserProfile from 'api/fetchUserProfile';
import { Link, ListLink } from 'components/Link';
import LoadingOrError from 'components/LoadingOrError';
import { Grid, List, ListLink, TextIntroduction } from 'design-system-react';
import { Grid, List, TextIntroduction } from 'design-system-react';
import useSblAuth from '../../../api/useSblAuth';
import CrumbTrail from '../../../components/CrumbTrail';
import AssociatedInstitutions from './AssociatedInstitutions';
Expand Down Expand Up @@ -46,9 +47,9 @@ export default function ViewUserProfile(): JSX.Element {
<Grid.Column width={8}>
<main id='main-content' className='mb-[2.813rem] mt-[1.875rem]'>
<CrumbTrail>
<a href='/landing' key='home'>
<Link href='/landing' key='home'>
Platform home
</a>
</Link>
</CrumbTrail>
<TextIntroduction
heading='View your user profile'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Checkbox, Link, Paragraph } from 'design-system-react';
import FormParagraph from 'components/FormParagraph';
import InputErrorMessage from 'components/InputErrorMessage';
import { Link } from 'components/Link';
import { Checkbox, Paragraph } from 'design-system-react';
import { sblHelpLink } from 'utils/common';

import type { InstitutionDetailsApiCheckedType } from 'pages/ProfileForm/types';
Expand Down
3 changes: 2 additions & 1 deletion src/pages/ProfileForm/Step1Form/Step1Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import FormParagraph from 'components/FormParagraph';
import FieldGroup from 'components/FieldGroup';
import SectionIntro from 'components/SectionIntro';

import { Button, Link, Paragraph, Heading } from 'design-system-react';
import { Link } from 'components/Link';
import { Button, Paragraph, Heading } from 'design-system-react';

import { fiOptions, fiData } from 'pages/ProfileForm/ProfileForm.data';
import type {
Expand Down
3 changes: 2 additions & 1 deletion src/pages/ProfileForm/Step1Form/Step1FormHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link, TextIntroduction } from 'design-system-react';
import { Link } from 'components/Link';
import { TextIntroduction } from 'design-system-react';
import { gleifLink } from 'utils/common';

/**
Expand Down
3 changes: 2 additions & 1 deletion src/pages/ProfileForm/Step2Form/Step2Form.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useEffect } from 'react';
import useProfileForm from 'store/useProfileForm';

import { Link, Alert } from 'design-system-react';
import { Link } from 'components/Link';
import { Alert } from 'design-system-react';
import Step2FormHeader from './Step2FormHeader';

import { Step2FormHeaderMessages } from './Step2FormHeader.data';
Expand Down
3 changes: 2 additions & 1 deletion src/pages/ProfileForm/Step2Form/Step2FormHeader.data.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link } from 'design-system-react';
import { Link } from 'components/Link';
import type { ReactNode } from 'react';

export enum ScenarioHeader {
Error = 'Unable to complete your user profile',
Expand Down
3 changes: 3 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export default async ({ mode }) => {
optimizeDeps: {
exclude: [],
},
resolve: {
dedupe: ["react-router-dom"],
},
test: {
css: false,
include: ['src/**/__tests__/*'],
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4995,7 +4995,7 @@ __metadata:

"design-system-react@https://github.com/cfpb/design-system-react":
version: 0.0.0
resolution: "design-system-react@https://github.com/cfpb/design-system-react.git#commit=a59765ea64c48d61bc6a3dff83b21aef9569a0db"
resolution: "design-system-react@https://github.com/cfpb/design-system-react.git#commit=d3888b71ed68e21901f52096d0270c97de6a8c00"
dependencies:
"@cfpb/cfpb-design-system": ^0.35.0
"@cfpb/cfpb-expandables": ^0.35.0
Expand All @@ -5007,7 +5007,7 @@ __metadata:
react-dom: 18.2.0
react-router-dom: 6.3.0
react-select: ^5.7.2
checksum: b6b47c84d51a5547996aef1a8d678d7bccd677d38db7aa28f53005c9e34f172dbe893a524ebfb29b9f3150fafc96bedcbd7706dbb14ce3a88f320a962f4b3dd5
checksum: 695d77f2502ed018f5fd21c2ec65855f3308b7b597d10cb43f4227089b5913d9d2ed692845cfc70b3b9c01bf3c97fd11f6c1a8819d2fb3155472e84ff45997b4
languageName: node
linkType: hard

Expand Down

0 comments on commit d0a7142

Please sign in to comment.