From 9ca5fe55a6129b796fd182aed48748fa75ab2c6b Mon Sep 17 00:00:00 2001 From: Andrew Hobson Date: Thu, 9 Jul 2020 10:41:58 -0400 Subject: [PATCH] Support styling custom link components --- src/components/Link/Link.test.tsx | 2 + src/components/Link/Link.tsx | 67 ++++++++++++++++++------------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/components/Link/Link.test.tsx b/src/components/Link/Link.test.tsx index 79fba82cde..103d4a73f0 100644 --- a/src/components/Link/Link.test.tsx +++ b/src/components/Link/Link.test.tsx @@ -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', () => { diff --git a/src/components/Link/Link.tsx b/src/components/Link/Link.tsx index 842a2ab15c..db7527c043 100644 --- a/src/components/Link/Link.tsx +++ b/src/components/Link/Link.tsx @@ -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 & + JSX.IntrinsicElements['a'] type AsCustomProps = { asCustom: keyof JSX.IntrinsicElements | React.FunctionComponent -} & React.PropsWithChildren +} & StyledLinkProps & + React.PropsWithChildren type PossibleLinkProps = LinkProps | AsCustomProps @@ -19,41 +22,49 @@ function isAsCustomProps( return (props as AsCustomProps).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( props: PossibleLinkProps ): 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(props)) { - const { asCustom, children, ...remainingProps } = props + const { variant, className, asCustom, children, ...remainingProps } = props // 1. We know props is AsCustomProps // 2. We know AsCustomProps 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 ( {children}