Skip to content

Commit

Permalink
Support styling custom link components
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Hobson committed Jul 9, 2020
1 parent 12e857f commit 9ca5fe5
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 28 deletions.
2 changes: 2 additions & 0 deletions src/components/Link/Link.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ describe('Link component', () => {
'custom-attr',
'customVal'
)
expect(getByTestId('customComponent')).toHaveClass('usa-link')
expect(getByTestId('customComponent')).toHaveClass('custom-class')
})

it('handles unstyled prop', () => {
Expand Down
67 changes: 39 additions & 28 deletions src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React from 'react'
import classnames from 'classnames'

type StyledLinkProps = React.PropsWithChildren<{
type StyledLinkProps = {
variant?: 'external' | 'unstyled'
}>
className?: string
}

type LinkProps = StyledLinkProps & JSX.IntrinsicElements['a']
type LinkProps = React.PropsWithChildren<StyledLinkProps> &
JSX.IntrinsicElements['a']

type AsCustomProps<T> = {
asCustom: keyof JSX.IntrinsicElements | React.FunctionComponent<T>
} & React.PropsWithChildren<T>
} & StyledLinkProps &
React.PropsWithChildren<T>

type PossibleLinkProps<T> = LinkProps | AsCustomProps<T>

Expand All @@ -19,41 +22,49 @@ function isAsCustomProps<T>(
return (props as AsCustomProps<T>).asCustom !== undefined
}

function linkClasses(
variant: 'external' | 'unstyled' | undefined,
className: string | undefined
) {
const unstyled = variant === 'unstyled'
const isExternalLink = variant === 'external'

return unstyled
? className
: classnames(
'usa-link',
{
'usa-link--external': isExternalLink,
},
className
)
}

export function Link<FCProps = LinkProps>(
props: PossibleLinkProps<FCProps>
): React.ReactElement {
// 2 ways to use this
// 1. default element "a", styled using default LinkProps
// 2. asCustom element or FunctionComponent, styled using custom
// FCProps. The custom FCProps might have a `variant` that has a
// different meaning in the custom component, so we have to leave
// styling up to the custom component

if (isAsCustomProps<FCProps>(props)) {
const { asCustom, children, ...remainingProps } = props
const { variant, className, asCustom, children, ...remainingProps } = props
// 1. We know props is AsCustomProps<FCProps>
// 2. We know AsCustomProps<FCProps> is
// FCProps & { children: ..., asCustom: ... }
// 3. Therefore we know that removing those two props leaves us
// FCProps & { variant: ..., className: ..., children: ..., asCustom: ... }
// 3. Therefore we know that removing those props leaves us
// with FCProps
//
// Not sure why TypeScript can't figure that out
const linkProps: FCProps = (remainingProps as unknown) as FCProps
return React.createElement(asCustom, linkProps, children)
const classes = linkClasses(variant, className)
return React.createElement(
asCustom,
{
className: classes,
...linkProps,
},
children
)
} else {
const { children, variant, className, ...linkProps } = props
const unstyled = variant === 'unstyled'
const isExternalLink = variant === 'external'

const classes = unstyled
? className
: classnames(
'usa-link',
{
'usa-link--external': isExternalLink,
},
className
)

const classes = linkClasses(variant, className)
return (
<a className={classes} {...linkProps}>
{children}
Expand Down

0 comments on commit 9ca5fe5

Please sign in to comment.