How to create components

Components should be...

  • Pure and have only internal UI state. All other state is external.
  • Always use inline styling with tailwind
  • Each component should have its own folder, with index.tsx as the entry point
  • Export typed definitions of props

Examples

Task Card Component

import React, { useState } from 'react';
import { CheckCircle, Clock, AlertCircle } from 'lucide-react';

/**
 * Represents the priority level of a task
 * @readonly
 */
export type TaskPriority = 'low' | 'medium' | 'high';

/**
 * Represents the current status of a task
 * @readonly
 */
export type TaskStatus = 'pending' | 'in-progress' | 'completed';

/**
 * Configuration options for the TaskCard component
 * @interface
 */
export interface TaskCardProps {
  /**
   * The unique identifier for the task
   */
  id: string;
  
  /**
   * The main title/description of the task
   */
  title: string;
  
  /**
   * The priority level of the task
   * @default 'medium'
   */
  priority?: TaskPriority;
  
  /**
   * The initial status of the task
   * @default 'pending'
   */
  initialStatus?: TaskStatus;
  
  /**
   * Optional due date for the task
   */
  dueDate?: Date;
  
  /**
   * Callback function triggered when task status changes
   * @param newStatus - The new status value
   * @param taskId - The ID of the task that changed
   */
  onStatusChange?: (newStatus: TaskStatus, taskId: string) => void;
}

/**
 * A rich task card component that displays task information and allows status updates
 * 
 * @description
 * TaskCard is a self-contained component that manages its own state while still  (ONLY UI STATE, all other state should be external)
 * allowing parent components to track status changes. It includes visual indicators
 * for priority levels and status, with a clean, accessible design.
 * 
 * @example
 * <TaskCard
 *   id="task-1"
 *   title="Complete project documentation"
 *   priority="high"
 *   dueDate={new Date('2025-03-01')}
 *   onStatusChange={(status, id) => console.log(`Task ${id} changed to ${status}`)}
 * />
 */
const TaskCard: React.FC<TaskCardProps> = ({
  id,
  title,
  priority = 'medium',
  initialStatus = 'pending',
  dueDate,
  onStatusChange
}) => {
  const [status, setStatus] = useState<TaskStatus>(initialStatus);

  // Map priority levels to Tailwind classes
  const priorityClasses = {
    low: 'bg-blue-100 text-blue-800',
    medium: 'bg-yellow-100 text-yellow-800',
    high: 'bg-red-100 text-red-800'
  };

  // Map status to icons
  const statusIcons = {
    pending: <Clock className="w-5 h-5 text-gray-500" />,
    'in-progress': <AlertCircle className="w-5 h-5 text-yellow-500" />,
    completed: <CheckCircle className="w-5 h-5 text-green-500" />
  };

  /**
   * Handles status updates and triggers the callback if provided
   */
  const handleStatusChange = (newStatus: TaskStatus) => {
    setStatus(newStatus);
    onStatusChange?.(newStatus, id);
  };

  return (
    <div className="rounded-lg border border-gray-200 p-4 shadow-sm hover:shadow-md transition-shadow">
      <div className="flex items-start justify-between">
        <div className="flex-1">
          <h3 className="text-lg font-semibold text-gray-900">{title}</h3>
          
          <div className="mt-2 flex items-center gap-2">
            <span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${priorityClasses[priority]}`}>
              {priority}
            </span>
            
            {dueDate && (
              <span className="text-sm text-gray-500">
                Due: {dueDate.toLocaleDateString()}
              </span>
            )}
          </div>
        </div>
        
        <div className="flex items-center gap-2">
          {statusIcons[status]}
          <select
            value={status}
            onChange={(e) => handleStatusChange(e.target.value as TaskStatus)}
            className="block rounded-md border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
          >
            <option value="pending">Pending</option>
            <option value="in-progress">In Progress</option>
            <option value="completed">Completed</option>
          </select>
        </div>
      </div>
    </div>
  );
};

export default TaskCard;

Counter Component

import React, { useState } from 'react';
import { Plus, Minus } from 'lucide-react';

/**
 * Props for the CounterButton component
 */
export interface CounterButtonProps {
  /** Initial value for the counter */
  initialValue?: number;
  /** Maximum allowed value */
  max?: number;
  /** Minimum allowed value */
  min?: number;
}

/**
 * A simple counter button that allows incrementing and decrementing a value
 * within specified bounds.
 */
const CounterButton: React.FC<CounterButtonProps> = ({
  initialValue = 0,
  max = 10,
  min = 0
}) => {
  const [count, setCount] = useState(initialValue);

  const increment = () => {
    if (count < max) {
      setCount(count + 1);
    }
  };

  const decrement = () => {
    if (count > min) {
      setCount(count - 1);
    }
  };

  return (
    <div className="flex items-center gap-3 rounded-lg border border-gray-200 p-2 w-40">
      <button
        onClick={decrement}
        disabled={count <= min}
        className="p-1 text-gray-600 hover:text-gray-900 disabled:opacity-50"
      >
        <Minus size={20} />
      </button>
      
      <span className="flex-1 text-center font-medium">
        {count}
      </span>
      
      <button
        onClick={increment}
        disabled={count >= max}
        className="p-1 text-gray-600 hover:text-gray-900 disabled:opacity-50"
      >
        <Plus size={20} />
      </button>
    </div>
  );
};

export default CounterButton;