Skip to content

Commit

Permalink
refactor(polymorphism): 🔍 stronly typed the button based on 'as' prop
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchelvanbever authored Nov 7, 2021
1 parent ed05def commit eb8411c
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 30 deletions.
56 changes: 27 additions & 29 deletions src/components/common/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, {
Ref,
forwardRef,
ElementType,
ForwardedRef,
ElementRef,
ReactNode,
DetailedHTMLProps,
ButtonHTMLAttributes,
AnchorHTMLAttributes,
} from 'react';
import Link from 'next/link';
import type { PolymorphicForwardRefExoticComponent } from 'react-polymorphic-types';
import styled from 'styled-components';
import { variant } from 'styled-system';

Expand Down Expand Up @@ -118,14 +117,27 @@ const variants = {
},
};

type CustomProps = SystemProps & {
type ButtonType = DetailedHTMLProps<
ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>;
type AnchorType = DetailedHTMLProps<
AnchorHTMLAttributes<HTMLAnchorElement>,
HTMLAnchorElement
>;

type CustomProps = {
isLoading?: boolean;
to?: string | undefined;
variant?: keyof typeof variants;
buttonSize?: keyof typeof sizes;
disabled?: boolean;
children: ReactNode;
};
} & SystemProps;

type ButtonProps =
| ({ as?: never } & ButtonType & CustomProps)
| ({ as: 'a' } & AnchorType & CustomProps);

const StyledButton = styled(Box).withConfig({
shouldForwardProp: (prop, defaultValidatorFn) =>
Expand Down Expand Up @@ -171,21 +183,15 @@ const StyledButton = styled(Box).withConfig({
${variant({ prop: 'buttonSize', variants: sizes })}
`;

export const Button: PolymorphicForwardRefExoticComponent<
CustomProps,
typeof _defaultElement
> &
StyledConfigType = forwardRef(
<AsElement extends ElementType = typeof _defaultElement>(
props: CustomProps,
ref: ForwardedRef<ElementRef<AsElement>>,
) => {
export const Button = forwardRef(
(props: ButtonProps, ref: ForwardedRef<any>) => {
if (props?.isLoading) {
return (
// @ts-expect-error
<StyledButton
as={_defaultElement}
{...props}
ref={ref as Ref<HTMLDivElement> & Ref<HTMLButtonElement>}
ref={ref}
position="relative"
data-is-loading
aria-disabled
Expand All @@ -209,26 +215,18 @@ export const Button: PolymorphicForwardRefExoticComponent<
if (props?.to) {
return (
<Link href={props.to} passHref>
<StyledButton
as="a"
{...props}
ref={ref as Ref<HTMLDivElement> & Ref<HTMLAnchorElement>}
/>
{/* @ts-expect-error */}
<StyledButton as="a" {...props} ref={ref} />
</Link>
);
}

return (
<StyledButton
as={_defaultElement}
{...props}
ref={ref as Ref<HTMLDivElement> & Ref<HTMLButtonElement>}
/>
);
// @ts-expect-error
return <StyledButton as={_defaultElement} {...props} ref={ref} />;
},
);

Button.config = {
(Button as StyledConfigType).config = {
variant: variants,
buttonSize: sizes,
};
16 changes: 16 additions & 0 deletions src/components/common/Flex/flex.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import React from 'react';

import { Flex } from '~components';
import { styled } from '~lib';

export default {
component: Flex,
title: 'components/Flex',
argTypes: {},
};

const ExtendedFlex = styled(Flex, {
variants: {
variant: {
center: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
},
},
});

export const Basic = (args) => <Flex {...args} />;
export const Extended = (args) => (
<ExtendedFlex {...args} as="a" href="/link" />
);
3 changes: 2 additions & 1 deletion src/lib/styled.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ describe('extend component with existing variations', () => {
'data-is-loading',
'true',
);

expect(screen.getByTestId('link-button')).toHaveAttribute('href', '/link');
expect(screen.getByTestId('next-button')).toHaveAttribute('href', '/link');
userEvent.click(screen.getByTestId('click-button'));
expect(mockFn).toHaveBeenCalled();
});
Expand Down

1 comment on commit eb8411c

@vercel
Copy link

@vercel vercel bot commented on eb8411c Nov 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.