I've been digging around SO and the web at large for a solution, but I can't seem to nail it.
I have two components, Link
and Button
. Long story short: they are wrappers for <a>
and <button>
elements, but with the added options such as chevrons on the right-side, icons on the left-side, full-width mode, etc.
Here is what I have so far (and here's the code running on typescriptlang.org/play):
type Variant = "primary" | "secondary" | "tertiary";
interface CommonProps {
variant?: Variant;
showChevron?: boolean;
icon?: IconDefinition;
fullWidth?: boolean;
small?: boolean;
}
interface LinkOnlyProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
interface ButtonOnlyProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick: React.MouseEventHandler<HTMLButtonElement>;
}
export type LinkProps = CommonProps & LinkOnlyProps;
export type ButtonProps = CommonProps & ButtonOnlyProps;
export const Link = (props: LinkProps) => {
const {
children,
showChevron,
icon,
fullWidth,
variant,
small,
className,
...rest
} = props;
const { classes } = initButton(props);
return (
<a className={classes} {...rest}>
<Content {...props} />
</a>
);
};
export const Button = (props: ButtonProps) => {
const {
children,
showChevron,
icon,
fullWidth,
variant,
small,
className,
...rest
} = props;
const { classes } = initButton(props);
return (
<button className={classes} {...rest}>
<Content {...props} />
</button>
);
};
I've tried extracting the common logic for the Link
and Button
components into a single Component
, however when I spread the ...rest
props I get TypeScript yelling at me. From the error, it seems because I haven't been able to account for the possibility of <a>
props being spread on to a <button>
element and vice-versa.
I wanted to keep Link
and Button
as separate components, rather than specifying the type as a prop, so that the intentionality of the developer is clear when the components are being implemented.
Is there any possibility of extracting that common logic into a central component that both Link
and Button
can simply act as wrappers for? For example:
export const Link = (props: LinkProps) => {
return <Component element="a" {...props} />;
}
export const Button = (props: ButtonProps) => {
return <Component element="button" {...props} />;
}