Skip to content

Commit

Permalink
feat(Link): add react-router link integration (#294)
Browse files Browse the repository at this point in the history
Well this took some experimentation, but we got there. Allows `Link` to
use a `isRouterLink` prop to convert them to `react-router` links with
minimal hassle.

## Changes

- add `react-router-dom` as an external dependency in the vite config,
since `react-router` needs its links to share the same instance of the
library to work. Works in concert [with this
change](https://github.com/cfpb/sbl-frontend/compare/198-integrate-react-router-links?expand=1#diff-6a3b01ba97829c9566ef2d8dc466ffcffb4bdac08706d3d6319e42e0aa6890ddR26-R28)
in `sbl-frontend`. What a pain this was. (Also, I added a maybe missing
`react-dom` from the `optimizeDeps.exclude` array for good measure)
- add a `isRouterLink` prop that switches `Link` to using `react-router`
Links
- fixes some Typescript errors
- adds a story for `isRouterLink` prop called "Link using React Router
Link"

## How to test this PR

1. Does the `isRouterLink` story "Link using React Router Link" render
without errors?

## Screenshots
![Screenshot 2024-01-29 at 11 15 16
PM](https://github.com/cfpb/design-system-react/assets/19983248/a3c9e483-945c-423c-9efa-1bf08bd8b5cd)

## Notes

- I could have also added a `NavLink` option, but I don't think we have
a use case for it since we have some similar logic built out already. I
made [a ticket for
it](#293), in case we
want to add it in.
- Enables this PR on sbl-frontend:
cfpb/sbl-frontend#201
  • Loading branch information
billhimmelsbach authored Jan 30, 2024
1 parent a59765e commit d3888b7
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 10 deletions.
22 changes: 22 additions & 0 deletions src/components/Link/Link.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
import { BrowserRouter } from 'react-router-dom';
import {
DestructiveLink,
Icon,
Expand Down Expand Up @@ -137,3 +138,24 @@ export const JumpLinkIconLeft: Story = {
</Link>
)
};

export const LinkWithReactRouterLink: Story = {
name: 'Link using React Router Link',
parameters: {
docs: {
description: {
story:
'See [React Router Link docs](https://reactrouter.com/en/main/components/link) for usage information'
}
}
},
render: () => (
<BrowserRouter>
<p>
<Link href='/#' isRouterLink>
Link using React Router Link
</Link>
</p>
</BrowserRouter>
)
};
35 changes: 28 additions & 7 deletions src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import classnames from 'classnames';
import { Link as RouterLink } from 'react-router-dom';
import type { JSXElement } from '../../types/jsxElement';

import classnames from 'classnames';
import ListItem from '../List/ListItem';

interface LinkProperties extends React.HTMLProps<HTMLAnchorElement> {
type?: 'default' | 'destructive' | 'list';
export interface LinkProperties extends React.HTMLProps<HTMLAnchorElement> {
children?: React.ReactNode;
hasIcon?: boolean;
noWrap?: boolean;
href?: string;
isJump?: boolean;
isJumpLeft?: boolean;
isRouterLink?: boolean;
noWrap?: boolean;
ref?: React.Ref<HTMLAnchorElement>;
type?: 'default' | 'destructive' | 'list';
}

/**
Expand All @@ -17,11 +23,13 @@ interface LinkProperties extends React.HTMLProps<HTMLAnchorElement> {
*/
export default function Link({
children,
type = 'default',
hasIcon = false,
noWrap = false,
href,
isJump = false,
isJumpLeft = false,
isRouterLink = false,
noWrap = false,
type = 'default',
...others
}: LinkProperties): JSXElement {
const cname = [others.className];
Expand All @@ -38,8 +46,21 @@ export default function Link({
if (isJump) cname.push('a-link__jump a-link__icon-after-text');
if (isJumpLeft) cname.push('a-link__jump a-link__icon-before-text');

if (isRouterLink) {
if (!href) {
throw new Error(
'Link component: href is a required attribute when isRouterLink is true'
);
}
return (
<RouterLink to={href} {...others} className={classnames(cname)}>
{children}
</RouterLink>
);
}

return (
<a {...others} className={classnames(cname)}>
<a {...others} className={classnames(cname)} href={href}>
{children}
</a>
);
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export {
LinkText,
ListLink
} from './components/Link/Link';
export type { LinkProperties } from './components/Link/Link';
export { default as List } from './components/List/List';
export {
default as ListItem,
Expand Down
7 changes: 4 additions & 3 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,17 @@ export default defineConfig(() => ({
fileName: (format): string => `${name}.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
external: ['react', 'react-dom', 'react-router-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
'react-dom': 'ReactDOM',
'react-router-dom': 'ReactRouterDOM'
}
}
},
optimizeDeps: {
exclude: ['react']
exclude: ['react', 'react-dom', 'react-router-dom']
},
esbuild: {
minify: true
Expand Down

0 comments on commit d3888b7

Please sign in to comment.