Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@
"peerDependencies": {
"@cfpb/cfpb-design-system": "5.3.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router": "^7.14.0"
"react-dom": "^19.2.4"
},
"devDependencies": {
"@cfpb/browserslist-config": "^0.0.6",
Expand Down
10 changes: 5 additions & 5 deletions src/components/Breadcrumb/breadcrumb.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('<Breadcrumb />', () => {
<Breadcrumb
crumbs={[
{
href: '/home',
to: '/home',
label: 'Home',
},
]}
Expand All @@ -32,8 +32,8 @@ describe('<Breadcrumb />', () => {
render(
<Breadcrumb
crumbs={[
{ href: '/home', label: 'Home' },
{ href: '/section', label: 'Section' },
{ to: '/home', label: 'Home' },
{ to: '/section', label: 'Section' },
]}
/>,
);
Expand All @@ -49,8 +49,8 @@ describe('<Breadcrumb />', () => {
render(
<Breadcrumb
crumbs={[
{ href: '/home', label: 'Home' },
{ href: '/current', label: 'Current', isCurrent: true },
{ to: '/home', label: 'Home' },
{ to: '/current', label: 'Current', isCurrent: true },
]}
/>,
);
Expand Down
9 changes: 5 additions & 4 deletions src/components/Breadcrumb/breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import type { HTMLAttributes } from 'react';
import { Fragment } from 'react';
import './breadcrumb.scss';
import { JSXElement } from '../../types/jsx-element';
import Link from '../Link/link';

export interface BreadcrumbCrumb {
href: string;
to: string;
label: string;
isCurrent?: boolean;
}
Expand Down Expand Up @@ -34,16 +35,16 @@ export const Breadcrumb = ({
>
<nav className='m-breadcrumbs' aria-label={ariaLabel}>
{crumbs.map((crumb) => (
<Fragment key={`${crumb.href}-${crumb.label}`}>
<Fragment key={`${crumb.to}-${crumb.label}`}>
{` / `}
{crumb.isCurrent ? (
<span className='m-breadcrumbs__crumb' aria-current='page'>
{` ${crumb.label} `}
</span>
) : (
<a className='m-breadcrumbs__crumb' href={crumb.href}>
<Link className='m-breadcrumbs__crumb' to={crumb.to}>
{` ${crumb.label} `}
</a>
</Link>
)}
</Fragment>
))}
Expand Down
10 changes: 5 additions & 5 deletions src/components/Header/responsive-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function CfpbLogo({
return (
<Link
data-testid='CfpbLogoLink'
href={href}
to={href}
title='Home'
aria-label='Home'
className='o-header__logo'
Expand Down Expand Up @@ -140,18 +140,18 @@ export default function ResponsiveMenu({
);
}

export const ExampleLinks: ReactNode[] = [
<Link key='home' href='/' label='Home' />,
export const ExampleLinks: React.ReactNode[] = [
<Link key='home' to='/' label='Home' />,
<Link
key='filing'
className='nav-item active'
href='/filing'
to='/filing'
label='Filing'
/>,
<Link
key='profile'
className='nav-item profile'
href='/profile'
to='/profile'
label='John Sample'
/>,
<Button
Expand Down
20 changes: 20 additions & 0 deletions src/components/Link/base-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ReactNode } from "react";
import type { JSXElement } from "../../types/jsx-element";

export interface BaseLinkProperties {
to: string | undefined;
children: ReactNode;
[key: string]: unknown;
}

export const BaseLink = ({
to,
children,
...others
}: BaseLinkProperties): JSXElement | null => {
return (
<a href={to} {...others}>
{children}
</a>
);
};
82 changes: 53 additions & 29 deletions src/components/Link/link.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { BrowserRouter } from 'react-router';
import { expect, within } from 'storybook/test';
import { Heading, Link, List, ListLink } from '~/src/index';
import type { JSXElement } from "../../types/jsx-element";
import { DSRContext, Heading, Link, List, ListLink, type BaseLinkProperties } from '~/src/index';

const meta: Meta<typeof Link> = {
title: 'Components (Verified)/Links',
tags: ['autodocs'],
component: Link,
excludeStories: ['CustomLinkComponent']
};

export default meta;
Expand All @@ -15,27 +16,27 @@ type Story = StoryObj<typeof meta>;

const DefaultArguments = {
args: {
href: '#',
to: '#',
children: 'Link Text',
},
};

export const Inline: Story = {
render: () => (
<p>
Here&apos;s the default <Link href='/#' label='inline link' /> style.
Here&apos;s the default <Link to='/#' label='inline link' /> style.
</p>
),
};

export const Standalone: Story = {
render: (arguments_) => (
<Link {...arguments_} href='/#' isJump label='Standalone link' />
<Link {...arguments_} to='/#' isJump label='Standalone link' />
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const link = canvas.getByRole('link', { name: /standalone link/i });
await expect(link).toHaveAttribute('href', '/#');
await expect(link).toHaveAttribute('to', '/#');
},
};

Expand All @@ -50,13 +51,13 @@ export const WithIcon: Story = {
<p>
The document icon should emphasize a link that contains a{' '}
<Link
href={DefaultArguments.args.href}
to={DefaultArguments.args.to}
label='file or document'
iconRight='download'
/>
. The external link icon is used to emphasize a link to a{' '}
<Link
href={DefaultArguments.args.href}
to={DefaultArguments.args.to}
label='non-CFPB webpage'
iconRight='external-link'
/>
Expand All @@ -66,31 +67,31 @@ export const WithIcon: Story = {
<p>
<Link
isJump
href='https://www.example.com'
to='https://www.example.com'
iconLeft='left'
label='Go back'
/>
</p>
<p>
<Link
isJump
href='https://www.example.com'
to='https://www.example.com'
label='Continue'
iconRight='right'
/>
</p>
<p>
<Link
isJump
href='https://www.example.com'
to='https://www.example.com'
label='External link'
iconRight='external-link'
/>
</p>
<p>
<Link
isJump
href='https://www.example.com'
to='https://www.example.com'
label='Document or file'
iconRight='document'
/>
Expand All @@ -106,9 +107,9 @@ export const Listlink: Story = {
},
render: () => (
<List isLinks>
<ListLink href='/#' label='List item 1' />
<ListLink href='/#' label='List item 2' />
<ListLink href='/#' label='List item 3' />
<ListLink to='/#' label='List item 1' />
<ListLink to='/#' label='List item 2' />
<ListLink to='/#' label='List item 3' />
</List>
),
};
Expand All @@ -117,22 +118,45 @@ export const Destructive: Story = {
args: {
...DefaultArguments.args,
},
render: () => <Link href='/#' type='destructive' label='Destructive link' />,
render: () => <Link to='/#' type='destructive' label='Destructive link' />,
};

export const LinkWithReactRouterLink: Story = {
name: 'Link using React Router Link',
parameters: {
docs: {
description: {
story:
'See [React Router Link docs](https://reactrouter.com/api/components/Link) for usage information',
},
},
},
const CustomLinkComponent = ({
to,
children,
...others
}: BaseLinkProperties): JSXElement | null => {
return (
<a href={to} {...others} data-link-component='custom'>
{children}
</a>
);
};

/**
* You can configure the DSR to use a router library's link component by wrapping your app
* in the DSRContext provider and setting a `LinkComponent` value.
* Your custom link component will be output instead of the default anchor element
* everywhere the DSR's Link component is used.
*
* Example usage:
*
* \<DSRContext value={{LinkComponent: YourRouterLinkComponent}} >
* App content
* \</DSRContext>
*/
export const LinkWithCustomLinkComponent: Story = {
name: 'Link using custom component',
decorators: [
(Story) => (
<DSRContext value={{ LinkComponent: CustomLinkComponent }}>
<Story />
</DSRContext>
),
],
render: () => (
<BrowserRouter>
<Link href='/#' label='Link using React Router Link' isRouterLink />
</BrowserRouter>
<Link to='/#' label='Link using custom link component' />
),
};


Loading