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 action
s 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 :/.