---
source_hash: "299541f7"
title: "Components and Pages"
weight: 100
---

# Components and Pages

[React Documentation](https://react.dev/)

Components are the basic building blocks of a React project. Each represents a reusable and maintainable part of the user interface.

## Component Organization

### Recommended Structure

```
src/components/
├── app/                    # Infrastructure Components
│   ├── Provider/
│   │   └── index.jsx
│   └── Router/
│       ├── index.jsx
│       └── Guards/
│           └── index.jsx
├── pages/                  # Application Pages
│   ├── public/             # Pages without authentication
│   │   ├── LoginPage/
│   │   └── WelcomePage/
│   ├── private/            # Authenticated Pages
│   │   ├── HomePage/
│   │   └── SettingsPage/
│   └── errors/             # Error Pages
│       └── Error404Page/
├── forms/                  # Form Components
│   ├── LoginForm/
│   └── ItemForm/
├── ui/                     # Reusable UI Components
│   ├── Card/
│   ├── Modal/
│   └── Header/
└── layouts/                # Page Layouts
    ├── MainLayout/
    └── AuthLayout/
```

### Naming Convention
- **Folder per component**: Each component in its own folder
- **index.jsx**: Component's main file
- **PascalCase**: Component names in PascalCase

```
src/components/ui/Card/
├── index.jsx           # Main component
├── Card.module.css     # Styles (optional)
└── Card.test.jsx       # Tests (optional)
```

## Creating a Component

### Simple Component

```javascript
// src/components/ui/Card/index.jsx

export const Card = ({ children, className = '' }) => {
  return (
    <div className={`bg-white rounded-lg shadow-md p-4 ${className}`}>
      {children}
    </div>
  );
};
```

### Component with Props

```javascript
// src/components/ui/Button/index.jsx

export const Button = ({
  children,
  variant = 'primary',
  size = 'md',
  loading = false,
  disabled = false,
  onClick,
  type = 'button',
  className = '',
}) => {
  const variants = {
    primary: 'bg-primary text-white hover:bg-primary-dark',
    secondary: 'bg-secondary text-white hover:bg-secondary-dark',
    outline: 'border-2 border-primary text-primary hover:bg-primary/10',
    ghost: 'text-primary hover:bg-primary/10',
  };

  const sizes = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2',
    lg: 'px-6 py-3 text-lg',
  };

  return (
    <button
      type={type}
      onClick={onClick}
      disabled={disabled || loading}
      className={`
        inline-flex items-center justify-center
        rounded-md font-medium transition-all
        disabled:opacity-50 disabled:cursor-not-allowed
        ${variants[variant]}
        ${sizes[size]}
        ${className}
      `}
    >
      {loading && (
        <span className="mr-2 animate-spin">⟳</span>
      )}
      {children}
    </button>
  );
};
```

### Custom Input Component

```javascript
// src/components/form/Input/index.jsx

export const Input = (props) => {
  const { label, id, error, ...inputProps } = props;

  return (
    <div className="flex flex-col gap-2">
      {label && (
        <label
          htmlFor={id}
          className="text-sm font-medium text-gray-700"
        >
          {label}
        </label>
      )}
      <input
        id={id}
        className={`
          bg-gray-100 p-4 rounded-lg
          outline-none focus:ring-2 focus:ring-primary
          ${error ? 'ring-2 ring-red-500' : ''}
        `}
        {...inputProps}
      />
      {error && (
        <span className="text-sm text-red-500">{error}</span>
      )}
    </div>
  );
};
```

## Page Structure

### Simple Page

```javascript
// src/components/pages/private/HomePage/index.jsx

import { useGlobalStates, useNavigation } from '@cap-rel/smartcommon';

export const HomePage = () => {
  const nav = useNavigation();
  const gst = useGlobalStates();
  const session = gst.get('session');

  return (
    <div className="min-h-screen bg-gray-100">
      {/* Header */}
      <header className="bg-white shadow p-4">
        <h1 className="text-xl font-bold">Home</h1>
      </header>

      {/* Content */}
      <main className="p-4">
        <p>Welcome {session?.user?.name}</p>
      </main>

      {/* Navigation */}
      <nav className="fixed bottom-0 left-0 right-0 bg-white shadow-lg">
        <div className="flex justify-around p-2">
          <button onClick={() => nav.navigate('/')}>Home</button>
          <button onClick={() => nav.navigate('/settings')}>Settings</button>
        </div>
      </nav>
    </div>
  );
};
```

### Page with Data Loading

```javascript
// src/components/pages/private/ItemsPage/index.jsx

import { useApi, useGlobalStates, useNavigation } from '@cap-rel/smartcommon';
import { useEffect, useState } from 'react';
import { Card } from '../../ui/Card';

export const ItemsPage = () => {
  const api = useApi();
  const nav = useNavigation();
  const gst = useGlobalStates();
  const items = gst.get('items') ?? [];
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchItems = async () => {
      setLoading(true);
      setError(null);

      try {
        const data = await api.get('items');
        gst.set('items', data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchItems();
  }, []);

  if (loading) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <span className="animate-spin text-4xl">⟳</span>
      </div>
    );
  }

  if (error) {
    return (
      <div className="p-4 text-center text-red-500">
        Error: {error}
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-gray-100 p-4">
      <h1 className="text-2xl font-bold mb-4">My Items</h1>

      <div className="space-y-4">
        {items.map((item) => (
          <Card
            key={item.id}
            onClick={() => nav.navigate(`/items/${item.id}`)}
            className="cursor-pointer hover:shadow-lg transition-shadow"
          >
            <h2 className="font-semibold">{item.label}</h2>
            <p className="text-gray-600 text-sm">{item.description}</p>
          </Card>
        ))}
      </div>
    </div>
  );
};
```

## Using Layouts

### Creating a Layout

```javascript
// src/components/layouts/MainLayout/index.jsx

import { useNavigation } from '@cap-rel/smartcommon';

export const MainLayout = ({ children, title, showBack = true }) => {
  const nav = useNavigation();

  return (
    <div className="min-h-screen bg-gray-100 flex flex-col">
      {/* Header */}
      <header className="bg-white shadow p-4 flex items-center gap-4">
        {showBack && (
          <button onClick={() => nav.navigate(-1)} className="text-primary">
            ← Back
          </button>
        )}
        <h1 className="text-xl font-bold">{title}</h1>
      </header>

      {/* Content */}
      <main className="flex-1 p-4">
        {children}
      </main>

      {/* Bottom navigation */}
      <nav className="bg-white shadow-lg p-2">
        <div className="flex justify-around">
          <button onClick={() => nav.navigate('/')}>Home</button>
          <button onClick={() => nav.navigate('/search')}>Search</button>
          <button onClick={() => nav.navigate('/profile')}>Profile</button>
        </div>
      </nav>
    </div>
  );
};
```

### Using a Layout

```javascript
// src/components/pages/private/SettingsPage/index.jsx

import { MainLayout } from '../../layouts/MainLayout';
import { useGlobalStates } from '@cap-rel/smartcommon';
import { useTranslation } from 'react-i18next';

export const SettingsPage = () => {
  const { t, i18n } = useTranslation();
  const gst = useGlobalStates();
  const theme = gst.get('settings.theme');

  return (
    <MainLayout title={t('settings.title')}>
      <div className="space-y-6">
        {/* Language */}
        <section className="bg-white rounded-lg p-4">
          <h2 className="font-semibold mb-2">{t('settings.language')}</h2>
          <select
            value={i18n.language}
            onChange={(e) => i18n.changeLanguage(e.target.value)}
            className="w-full p-2 border rounded"
          >
            <option value="fr">Français</option>
            <option value="en">English</option>
          </select>
        </section>

        {/* Theme */}
        <section className="bg-white rounded-lg p-4">
          <h2 className="font-semibold mb-2">{t('settings.theme')}</h2>
          <div className="flex gap-4">
            <button
              onClick={() => gst.local.set('settings.theme', 'light')}
              className={`p-4 rounded ${theme === 'light' ? 'ring-2 ring-primary' : ''}`}
            >
              Light
            </button>
            <button
              onClick={() => gst.local.set('settings.theme', 'dark')}
              className={`p-4 rounded ${theme === 'dark' ? 'ring-2 ring-primary' : ''}`}
            >
              Dark
            </button>
          </div>
        </section>
      </div>
    </MainLayout>
  );
};
```

## Common Patterns

### Form

```javascript
import { useForm, useApi, useNavigation } from '@cap-rel/smartcommon';
import { Form, Input, Button } from '@cap-rel/smartcommon';

const ContactForm = () => {
  const api = useApi();
  const nav = useNavigation();
  const form = useForm({ defaultValues: { name: '', email: '' } });

  const handleSubmit = async (data) => {
    await api.post('contacts', { json: data });
    nav.navigate('/contacts');
  };

  return (
    <Form form={form} onSubmit={handleSubmit} className="space-y-4">
      <Input name="name" label="Name" />
      <Input name="email" type="email" label="Email" />
      <Button type="submit" loading={form.isFormSubmitting}>
        Submit
      </Button>
    </Form>
  );
};
```

### Modal/Drawer with Animation

```javascript
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';

const Modal = ({ isOpen, onClose, children }) => {
  return (
    <AnimatePresence>
      {isOpen && (
        <>
          {/* Backdrop */}
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            onClick={onClose}
            className="fixed inset-0 bg-black/50 z-40"
          />

          {/* Content */}
          <motion.div
            initial={{ y: '100%' }}
            animate={{ y: 0 }}
            exit={{ y: '100%' }}
            className="fixed bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4 z-50"
          >
            {children}
          </motion.div>
        </>
      )}
    </AnimatePresence>
  );
};
```

### List with Empty State

```javascript
const ItemsList = ({ items }) => {
  if (items.length === 0) {
    return (
      <div className="text-center py-12 text-gray-500">
        <p className="text-4xl mb-4">📭</p>
        <p>No items yet</p>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      {items.map(item => (
        <ItemCard key={item.id} item={item} />
      ))}
    </div>
  );
};
```

## Using SmartCommon

Prefer SmartCommon components to custom components when possible:

```javascript
import {
  Form,
  Input,
  Select,
  Checkbox,
  Button,
  Card,
  Modal,
} from '@cap-rel/smartcommon';
```

See [SmartCommon](/front/smartcommon) for the full list.

## See Also
- [SmartCommon](/front/smartcommon) - Ready-to-use Components
- [Hooks](/front/hooks) - Available Hooks
- [Routing](/front/routage) - Navigation Between Pages
- [Animations](/front/animations) - Page Transitions
