// Button.types.ts import type { ComponentPropsWithoutRef, ElementType } from 'react'; import type { LinkProps } from 'react-router-dom';
export type ButtonVariant = 'primary' | 'secondary' | 'ghost'; export type ButtonSize = 'sm' | 'md' | 'lg';
interface BaseButtonProps { children: React.ReactNode; variant?: ButtonVariant; size?: ButtonSize; className?: string; }
type PolymorphicComponentProps<T extends ElementType, P> = P & Omit<ComponentPropsWithoutRef<T>, keyof P>;
type ButtonAsButtonProps = PolymorphicComponentProps<'button', BaseButtonProps>; type ButtonAsAnchorProps = PolymorphicComponentProps<'a', BaseButtonProps>; type ButtonAsLinkProps = PolymorphicComponentProps<typeof Link, BaseButtonProps>;
export type ButtonProps<T extends ElementType> = T extends 'button' ? ButtonAsButtonProps : T extends 'a' ? ButtonAsAnchorProps : T extends typeof Link ? ButtonAsLinkProps : PolymorphicComponentProps<'button', BaseButtonProps>;
// Button.tsx import React, { forwardRef } from 'react'; import { Link } from 'react-router-dom'; import type { ElementType } from 'react'; import type { ButtonProps, ButtonVariant, ButtonSize } from './Button.types';
const DEFAULT_VARIANT: ButtonVariant = 'primary'; const DEFAULT_SIZE: ButtonSize = 'md';
const getClasses = (variant: ButtonVariant, size: ButtonSize, className?: string) => { const base = 'inline-flex items-center justify-center font-medium rounded-md transition-colors'; const variants = { primary: 'bg-blue-600 text-white', secondary: 'bg-gray-200 text-gray-800', ghost: 'bg-transparent text-blue-600', }; const sizes = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-base', lg: 'px-5 py-2.5 text-lg', }; return [base, variants[variant], sizes[size], className].filter(Boolean).join(' '); };
export const Button = forwardRef( <T extends ElementType = 'button'>( { as, variant = DEFAULT_VARIANT, size = DEFAULT_SIZE, className, children, ...rest }: ButtonProps<T> & { as?: T }, ref: React.Ref<ElementType> ) => { const Component = as || 'button'; const classes = getClasses(variant, size, className);
return ( <Component ref={ref as any} className={classes} {...rest}> {children} </Component> ); } );
Button.displayName = 'Button';
// Example Usage Snippets // (Assuming Button.tsx and Button.types.ts are in place, and react-router-dom is installed)
// 1. Default button (renders as <button>) <Button onClick={() => alert('Hello!')}> Click Me </Button>
// 2. Button rendering as an <a> tag <Button as="a" href="https://example.com" target="_blank" rel="noopener noreferrer" variant="secondary"> External Link </Button>
// 3. Button rendering as a React Router Link import { Link } from 'react-router-dom'; <Button as={Link} to="/dashboard" variant="primary" size="lg"> Go to Dashboard </Button>
// 4. Disabled button <Button disabled variant="ghost"> Disabled Action </Button>