Carlos Eduardo Gatti Ferreira

UI Base Guidelines

Status: Active
Phase: 2 - UI Base
Date: 2024-01-XX

Overview

The UI Base (components/ui/) is a minimal design system foundation that provides reusable, generic UI primitives for all domains in the monorepo. These components are domain-agnostic, contain no business logic, and can be safely used across QRACK, Discart-me, and future applications.

⚠️ Critical Rule

Existing screens MUST NOT be refactored to use these components yet.

These components are:

Components

Button

File: components/ui/Button.tsx

A generic button component with consistent styling and behavior.

Variants

Sizes

Props

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

Usage

import { Button } from '@/components/ui';

// Basic usage
<Button variant="primary" onClick={handleClick}>
  Click me
</Button>

// With loading state
<Button variant="primary" loading={isLoading}>
  Saving...
</Button>

// Disabled state
<Button variant="danger" disabled={!canDelete} onClick={handleDelete}>
  Delete
</Button>

When to Use

When NOT to Use


Input

File: components/ui/Input.tsx

A generic input component with proper accessibility and error handling.

Features

Props

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  error?: string;
  helperText?: string;
  required?: boolean;
  fullWidth?: boolean;
}

Usage

import { Input } from '@/components/ui';

// Basic input with label
<Input
  label="Email"
  type="email"
  value={email}
  onChange={(e) => setEmail(e.target.value)}
  placeholder="you@example.com"
/>

// With error handling
<Input
  label="Password"
  type="password"
  value={password}
  onChange={(e) => setPassword(e.target.value)}
  error={errors.password}
  required
/>

// With helper text
<Input
  label="Username"
  value={username}
  onChange={(e) => setUsername(e.target.value)}
  helperText="Choose a unique username"
/>

When to Use

When NOT to Use


Loading

File: components/ui/Loading.tsx

A generic loading indicator with spinner and skeleton variants.

Variants

Sizes

Props

interface LoadingProps {
  variant?: 'spinner' | 'skeleton';
  size?: 'sm' | 'md' | 'lg';
  message?: string;
  className?: string;
  lines?: number; // Only for skeleton variant
}

Usage

import { Loading } from '@/components/ui';

// Spinner with message
<Loading variant="spinner" size="md" message="Loading data..." />

// Skeleton loader
<Loading variant="skeleton" lines={3} />

// Full page loading
<div className="flex items-center justify-center min-h-screen">
  <Loading variant="spinner" size="lg" message="Please wait..." />
</div>

When to Use

When NOT to Use


File: components/ui/Modal.tsx

A generic modal component with portal rendering and accessibility support.

Features

Props

interface ModalProps {
  open: boolean;
  onClose: () => void;
  title?: string;
  children: React.ReactNode;
  footer?: React.ReactNode;
  size?: 'sm' | 'md' | 'lg' | 'xl';
  closeOnEscape?: boolean;
  closeOnBackdropClick?: boolean;
  className?: string;
}

Usage

import { Modal, Button } from '@/components/ui';

// Basic modal
<Modal
  open={isOpen}
  onClose={() => setIsOpen(false)}
  title="Confirm Action"
>
  <p>Are you sure you want to proceed?</p>
</Modal>

// With footer actions
<Modal
  open={isOpen}
  onClose={() => setIsOpen(false)}
  title="Delete Item"
  footer={
    <>
      <Button variant="ghost" onClick={() => setIsOpen(false)}>
        Cancel
      </Button>
      <Button variant="danger" onClick={handleDelete}>
        Delete
      </Button>
    </>
  }
>
  <p>This action cannot be undone.</p>
</Modal>

// Large modal
<Modal
  open={isOpen}
  onClose={() => setIsOpen(false)}
  title="Details"
  size="xl"
>
  {/* Large content */}
</Modal>

When to Use

When NOT to Use


Design Principles

1. Domain-Agnostic

All components are designed to work across all domains without domain-specific logic or assumptions.

2. No Business Logic

Components are pure UI primitives. All state and business logic should be handled by parent components or hooks.

3. Accessibility First

All components follow accessibility best practices:

4. Styling Consistency

Components use Tailwind CSS and follow the existing dark theme patterns:

5. Type Safety

All components are fully typed with TypeScript and extend native HTML element props where applicable.


Migration Strategy (Phase 3)

During Phase 3, existing screens can be gradually refactored to use these components:

  1. QRACK Screens: Replace inline button/input styles with UI components
  2. Discart-me Screens: Same approach
  3. Gradual Rollout: Refactor one screen at a time, test thoroughly

Migration Checklist


Adding New Components

When adding new UI primitives:

  1. ✅ Keep it generic and domain-agnostic
  2. ✅ No business logic
  3. ✅ Full TypeScript typing
  4. ✅ Proper accessibility
  5. ✅ Tailwind CSS styling
  6. ✅ Add to components/ui/index.ts
  7. ✅ Update this documentation
  8. ✅ Provide usage examples

Common Patterns

Form with Validation

import { Input, Button } from '@/components/ui';

function MyForm() {
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');

  return (
    <form onSubmit={handleSubmit}>
      <Input
        label="Email"
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        error={error}
        required
      />
      <Button type="submit" variant="primary">
        Submit
      </Button>
    </form>
  );
}

Loading States

import { Loading } from '@/components/ui';

function DataDisplay() {
  const { data, isLoading } = useData();

  if (isLoading) {
    return <Loading variant="spinner" message="Loading..." />;
  }

  return <div>{/* Render data */}</div>;
}

Confirmation Modal

import { Modal, Button } from '@/components/ui';

function DeleteButton() {
  const [showModal, setShowModal] = useState(false);

  return (
    <>
      <Button variant="danger" onClick={() => setShowModal(true)}>
        Delete
      </Button>
      <Modal
        open={showModal}
        onClose={() => setShowModal(false)}
        title="Confirm Deletion"
        footer={
          <>
            <Button variant="ghost" onClick={() => setShowModal(false)}>
              Cancel
            </Button>
            <Button variant="danger" onClick={handleDelete}>
              Delete
            </Button>
          </>
        }
      >
        <p>Are you sure? This action cannot be undone.</p>
      </Modal>
    </>
  );
}

Questions?

If you’re unsure whether to use a UI component:

  1. Is it a new screen? → ✅ Use UI components
  2. Is it an existing screen? → ❌ Wait for Phase 3
  3. Need custom styling? → Consider extending the component or creating a domain-specific wrapper

Status