Skip to content

Extract duplicate code: Create reusable Button component #896

Description

@jeromehardaway

Description

Create a reusable Button component to reduce code duplication across the application.

Problem

Button styling and logic is duplicated across multiple components. This leads to:

  • Inconsistent button styles
  • Duplicated code
  • Harder maintenance
  • Inconsistent accessibility

Solution

Create a shared Button component with variants.

Files to Create

  • src/components/ui/button/index.tsx
  • src/components/ui/button/button.module.css (or use Tailwind)

Suggested Implementation

import React from 'react';

export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
  fullWidth?: boolean;
  children: React.ReactNode;
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  (
    { 
      variant = 'primary', 
      size = 'md', 
      isLoading = false,
      fullWidth = false,
      disabled,
      className = '',
      children,
      ...props 
    },
    ref
  ) => {
    const baseClasses = 'tw-font-medium tw-rounded tw-transition-colors';
    
    const variantClasses = {
      primary: 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700',
      secondary: 'tw-bg-gray-600 tw-text-white hover:tw-bg-gray-700',
      outline: 'tw-border-2 tw-border-gray-600 tw-text-gray-600 hover:tw-bg-gray-50',
      ghost: 'tw-text-gray-600 hover:tw-bg-gray-100',
      danger: 'tw-bg-red-600 tw-text-white hover:tw-bg-red-700'
    };
    
    const sizeClasses = {
      sm: 'tw-px-3 tw-py-1.5 tw-text-sm',
      md: 'tw-px-4 tw-py-2 tw-text-base',
      lg: 'tw-px-6 tw-py-3 tw-text-lg'
    };
    
    const classes = `
      ${baseClasses}
      ${variantClasses[variant]}
      ${sizeClasses[size]}
      ${fullWidth ? 'tw-w-full' : ''}
      ${(disabled || isLoading) ? 'tw-opacity-50 tw-cursor-not-allowed' : ''}
      ${className}
    `.trim().replace(/\s+/g, ' ');

    return (
      <button
        ref={ref}
        className={classes}
        disabled={disabled || isLoading}
        {...props}
      >
        {isLoading ? (
          <>
            <span className="tw-mr-2">Loading...</span>
            {children}
          </>
        ) : (
          children
        )}
      </button>
    );
  }
);

Button.displayName = 'Button';

Usage Examples

// Primary button
<Button variant="primary" onClick={handleClick}>
  Submit
</Button>

// Loading state
<Button variant="primary" isLoading>
  Submitting...
</Button>

// Full width
<Button variant="primary" fullWidth>
  Sign Up
</Button>

// Danger action
<Button variant="danger" onClick={handleDelete}>
  Delete Account
</Button>

Tasks

  • Create Button component file
  • Implement variants (primary, secondary, outline, ghost, danger)
  • Implement sizes (sm, md, lg)
  • Add loading state with spinner
  • Add fullWidth option
  • Export from src/components/ui/index.ts
  • Create Storybook stories (if applicable)
  • Write component tests
  • Update 3-5 existing components to use new Button
  • Document props in JSDoc

Learning Outcomes

  • Understanding component composition
  • Learning about React.forwardRef
  • Practice with TypeScript generics
  • Understanding design systems
  • Learning about component variants

Estimated Time

2-3 hours

Priority

Medium - Reduces code duplication

Difficulty

Intermediate

Metadata

Metadata

Labels

UI/UXenhancementintermediateRequires moderate codebase familiarity; multi-file or design judgmentrefactoringCode refactoring

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions