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;