Skip to main content

Overview

MotorDesk uses a component-driven architecture with React and TypeScript. Components are organized by feature domain and follow consistent patterns for styling, props, and composition.

Component Organization

Components are organized into two main categories:

UI Components

Reusable, presentational components in src/components/ui/These are low-level, generic components used throughout the application.

Feature Components

Domain-specific components organized by featureHigher-level components for specific business domains like customers, vehicles, products.

UI Components

The src/components/ui/ directory contains foundational, reusable components:
ui/
├── Autocomplete.tsx    # Auto-complete input with suggestions
├── Button.tsx          # Primary button component
├── FormField.tsx       # Form field wrapper with label
├── Input.tsx           # Text input component
└── Select.tsx          # Select/dropdown component

Button Component

A versatile button component with multiple variants and states. Location: src/components/ui/Button.tsx
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: "primary" | "secondary" | "success" | "danger" | "ghost" | "outline";
  size?: "sm" | "md" | "lg" | "xl";
  icon?: ReactNode;
  isLoading?: boolean;
  fullWidth?: boolean;
}
Usage Examples:
import { Button } from '@components/ui/Button';
import { Plus } from 'lucide-react';

<Button 
  variant="primary" 
  icon={<Plus />}
  onClick={handleAdd}
>
  Add Customer
</Button>
Variants:

Primary

variant="primary"Blue background, main call-to-action buttons

Secondary

variant="secondary"Light gray, secondary actions

Success

variant="success"Green, confirmation/save actions

Danger

variant="danger"Red, destructive actions

Ghost

variant="ghost"Transparent, subtle actions

Outline

variant="outline"Bordered, alternative secondary style

Input Component

A controlled text input with consistent styling across the application. Location: src/components/ui/Input.tsx
export const Input = forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputElement>>(
  ({ className = "", ...props }, ref) => {
    return (
      <input
        ref={ref}
        className={`w-full border border-slate-300 text-slate-700 text-sm font-semibold rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block px-3 h-[38px] outline-none transition-all disabled:opacity-50 disabled:bg-slate-100 ${props.readOnly ? "bg-slate-50" : "bg-white"} ${className}`}
        {...props}
      />
    );
  }
);
Input.displayName = "Input";
Key Features:
  • Uses forwardRef for ref forwarding (required for form libraries)
  • Consistent height (38px) across all inputs
  • Automatic background color for readonly states
  • Focus ring for accessibility
  • Disabled state styling
Usage:
import { Input } from '@components/ui/Input';

<Input 
  type="email"
  placeholder="Enter email"
  value={email}
  onChange={(e) => setEmail(e.target.value)}
  required
/>

FormField Component

A wrapper component that combines labels with form inputs. Location: src/components/ui/FormField.tsx
interface FormFieldProps {
  label: string;
  children: ReactNode;
  className?: string;
}

export const FormField = ({ label, children, className = "" }: FormFieldProps) => {
  return (
    <div className={className}>
      <label className="block mb-1.5 font-bold text-[11px] text-slate-400 uppercase tracking-wider">
        {label}
      </label>
      {children}
    </div>
  );
};
Usage:
import { FormField } from '@components/ui/FormField';
import { Input } from '@components/ui/Input';

<FormField label="Customer Email">
  <Input 
    type="email"
    value={email}
    onChange={(e) => setEmail(e.target.value)}
  />
</FormField>
Benefits:
  • Consistent label styling across forms
  • Automatic spacing between label and input
  • Accessible label association
  • Composable with any form control

Feature Components

Feature components are organized by business domain:

Component Structure by Domain

components/customers/
├── CustomerDeleteModal.tsx    # Confirmation modal for deletion
├── CustomerDetailsModal.tsx   # View customer details
└── CustomerFormModal.tsx      # Add/edit customer form
Each domain follows the same pattern:
  • FormModal: Add and edit operations
  • DetailsModal: Read-only view
  • DeleteModal: Confirmation dialog
components/vehicles/
├── VehicleDeleteModal.tsx
├── VehicleDetailsModal.tsx
└── VehicleFormModal.tsx
Handles fleet management UI including:
  • License plate entry
  • Owner/driver assignment
  • Vehicle type selection
  • Usage tracking
components/products/
├── ProductDeleteModal.tsx
├── ProductDetailsModal.tsx
└── ProductFormModal.tsx
Manages product catalog:
  • Product information
  • Pricing
  • SUNAT codes
  • Inventory tracking
components/sales/
└── InlineActionButton.tsx
Handles electronic billing:
  • Invoice creation
  • Payment processing
  • Document generation
components/layout/
└── MainLayout.tsx
Application shell and navigation.
components/auth/
└── LogoutConfirmModal.tsx
User authentication flows.

MainLayout Component

The main application layout with sidebar navigation and top bar. Location: src/components/layout/MainLayout.tsx Key Features:
  • Sidebar navigation with icons
  • Branch/location selector
  • User profile display
  • Logout functionality
  • Nested routing with <Outlet />
Structure:
export const MainLayout = () => {
  const {
    user,
    availableBranches,
    selectedBranchId,
    handleBranchChange,
    isLogoutModalOpen,
    requestLogout,
    cancelLogout,
    confirmLogout,
  } = useMainLayout();

  return (
    <div className={styles.layoutContainer}>
      {/* Sidebar with navigation */}
      <aside className={styles.sidebar}>
        <nav className={styles.navLinks}>
          <Link to="/">Inicio</Link>
          <Link to="/vehicles">Vehículos</Link>
          {/* ... more links */}
        </nav>
      </aside>

      {/* Main content area */}
      <main className={styles.mainContent}>
        <header className={styles.topbar}>
          {/* Branch selector and user profile */}
        </header>
        
        <div className={styles.pageContent}>
          <Outlet /> {/* Nested routes render here */}
        </div>
      </main>
    </div>
  );
};
Icons Used (from Lucide React):
  • Home - Dashboard
  • Truck - Vehicles
  • Package - Products
  • Users - Customers
  • FileText - Sales/Invoices
  • BarChart3 - Reports
  • Settings - Configuration
  • LogOut - Logout

Component Patterns

All modals follow a consistent pattern:
interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  item?: Item; // Optional for edit/view, undefined for add
}

export const ItemFormModal = ({ isOpen, onClose, item }: ModalProps) => {
  const [formData, setFormData] = useState(item || defaultValues);
  
  const handleSubmit = async () => {
    // Save logic
    onClose();
  };
  
  if (!isOpen) return null;
  
  return (
    <div className="modal-overlay">
      <div className="modal-content">
        {/* Form fields */}
        <Button onClick={handleSubmit}>Save</Button>
        <Button variant="ghost" onClick={onClose}>Cancel</Button>
      </div>
    </div>
  );
};

Controlled Component Pattern

All form inputs are controlled components:
const [email, setEmail] = useState('');

<Input 
  value={email}
  onChange={(e) => setEmail(e.target.value)}
/>

Composition Pattern

Components compose smaller components:
<FormField label="Customer Name">
  <Input 
    value={name}
    onChange={(e) => setName(e.target.value)}
  />
</FormField>

Styling Approach

MotorDesk uses a hybrid styling approach:
Primary styling method using Tailwind’s utility classes:
<div className="flex items-center gap-4 p-6 bg-white rounded-lg shadow-md">
  <Button className="w-full" />
</div>
Benefits:
  • Rapid development
  • Consistent design system
  • Small bundle size (purged unused classes)
Used for complex layouts and component-scoped styles:
import styles from '@styles/modules/main-layout.module.css';

<div className={styles.layoutContainer}>
  <aside className={styles.sidebar} />
</div>
Benefits:
  • Scoped styles (no naming conflicts)
  • Better for complex layouts
  • CSS features (animations, media queries)
You can combine both methods:
<button className={`${styles.customButton} px-4 py-2 rounded-lg`}>
  Click me
</button>

Icons

The project uses Lucide React for icons:
import { Plus, Edit, Trash, Search } from 'lucide-react';

<Button icon={<Plus className="w-4 h-4" />}>
  Add New
</Button>
Common Icons:
  • Plus - Add actions
  • Edit / Pencil - Edit actions
  • Trash - Delete actions
  • Search - Search functionality
  • X - Close/cancel
  • Check - Confirm/success
  • AlertCircle - Warnings/errors
  • Loader2 - Loading states (animated)

Accessibility

Components follow accessibility best practices:
1

Semantic HTML

Use semantic elements (<button>, <input>, <nav>, etc.)
2

Keyboard Navigation

All interactive elements are keyboard accessible
3

Focus Indicators

Visible focus rings on all focusable elements
focus:ring-2 focus:ring-blue-500
4

ARIA Labels

Use aria-label or title for icon-only buttons
<button title="Delete item" aria-label="Delete">
  <Trash />
</button>
5

Disabled States

Clear visual indication of disabled states
disabled:opacity-50 disabled:cursor-not-allowed

Best Practices

Component Size

Keep components small and focused. If a component exceeds 200 lines, consider splitting it.

Props Interface

Always define TypeScript interfaces for props. Extend HTML attributes when possible.

Default Props

Use default parameters instead of defaultProps:
const Button = ({ variant = "primary" }) => ...

Ref Forwarding

Use forwardRef for form components to support refs.

Memoization

Use React.memo for expensive components that receive stable props.

Key Props

Always provide stable keys in lists:
{items.map(item => <Item key={item.id} {...item} />)}

Testing Components

When testing components, focus on:
  1. User interactions: Clicks, typing, form submission
  2. Rendering: Does it render with correct props?
  3. Accessibility: Keyboard navigation, screen readers
  4. Edge cases: Empty states, loading states, errors
// Example test structure
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders with text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });
  
  it('handles click events', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click</Button>);
    fireEvent.click(screen.getByText('Click'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
  
  it('shows loading state', () => {
    render(<Button isLoading>Save</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

Next Steps

Build docs developers (and LLMs) love