1

I'm in the process of creating a button component for our design system. I want to be able to switch it between certain actions; a button, a link and a few other specifics.

What I want to do is get typescript to throw an error if I use a prop that isn't supported by that type of button.

For example:

// Typescript should error because the action prop makes the underlying element a button
// and a href isn't a valid attribute
<Button action="default" href="/">Foo</Button>

So far I have a solution but it feels a bit clunky

type ButtonOptions = HTMLAnchorElement | HTMLButtonElement | RemixLinkProps;

interface SharedProps {
    children: ReactNode;
    className?: string;
    size?: 'sm' | 'md' | 'lg' | 'full';
    variant?: 'solid' | 'outline';
    isDisabled?: boolean;
    isLoading?: boolean;
    loadingText?: string;
}

interface DefaultProps extends ButtonHTMLAttributes<ButtonOptions> {
    action?: 'default';
    type?: 'submit' | 'reset' | 'button';

    // Error on these types
    textToCopy?: never;
    href?: never;
    target?: never;
    to?: never;
}

interface AnchorProps extends AnchorHTMLAttributes<ButtonOptions> {
    action: 'anchor';
    href: string;
    target?: string;

    // Error on these types
    textToCopy?: never;
    isDisabled?: never;
    isLoading?: never;
    loadingText?: never;
    to?: never;
}

interface LinkProps extends AnchorHTMLAttributes<ButtonOptions> {
    action: 'link';
    to: string;

    // Error on these types
    textToCopy?: never;
    isDisabled?: never;
    isLoading?: never;
    loadingText?: never;
    target?: never;
    href?: never;
}

export type Props = SharedProps & (DefaultProps | AnchorProps | LinkProps);

export const Button: FC<Props> = (props: Props) => {
// etc.
};

While this is working as expected it's really not very extendable and I have other actions to add.

I'm wondering if a generic might be what I'm after but I'm fairly new to typescript and struggling to get it to work exactly as I'd like and keep ending up with circular references :/.

0 Answers0