Containers, Blocks et Components
Comprendre l'utilisation des composants UI dans Foundation Builder
Containers, Blocks et Components
Foundation Builder utilise une architecture de composants UI organisée en deux niveaux principaux : les blocks (sections de page) et les components (éléments d'interface). Cette organisation permet une réutilisation optimale et une maintenance facilitée.
Vue d'ensemble
Architecture des composants
foundation.ui (registry shadcn) → Composants de base
↓
components/ → Éléments UI réutilisables
↓
blocks/ → Sections de page complètes
↓
index.tsx → Logique métier et orchestrationTypes de composants
- UI Components (
ui.tsx) : Composants d'interface pure, téléchargés depuis le registry shadcn - Container Components (
index.tsx) : Composants avec logique métier et orchestration - Blocks : Sections complètes de page (Hero, WithWithout, etc.)
- Components : Éléments d'interface composant les blocks
Structure des composants
Fichiers ui.tsx
Les fichiers ui.tsx contiennent les composants d'interface pure, généralement téléchargés depuis le registry shadcn de foundation.ui ou d'autres registries.
// Exemple : components/ui/button.tsx
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva("inline-flex items-center justify-center rounded-md text-sm font-medium", {
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
}
);
Button.displayName = "Button";
export { Button, buttonVariants };Fichiers index.tsx
Les fichiers index.tsx contiennent la logique métier et l'orchestration des composants UI.
// Exemple : blocks/hero/index.tsx
"use client";
import { Hero } from "./ui";
import { useAuth } from "@/hooks/use-auth";
import { Button } from "@/components/ui/button";
interface HeroContainerProps {
title: string;
description: string;
ctaText?: string;
ctaHref?: string;
}
export function HeroContainer({ title, description, ctaText = "Commencer", ctaHref = "/sign-up" }: HeroContainerProps) {
const { user, isLoading } = useAuth();
const handleCtaClick = () => {
if (user) {
// Logique pour utilisateur connecté
window.location.href = "/dashboard";
} else {
// Logique pour utilisateur non connecté
window.location.href = ctaHref;
}
};
return (
<Hero
title={title}
description={description}
cta={
<Button onClick={handleCtaClick} disabled={isLoading} size="lg">
{isLoading ? "Chargement..." : ctaText}
</Button>
}
/>
);
}Organisation des composants
Dossier blocks/
Les blocks représentent les sections complètes d'une page, généralement utilisées dans les landing pages.
blocks/
├── hero/
│ ├── index.tsx # HeroContainer - Logique métier
│ └── ui.tsx # Hero - Interface pure
├── with-without/
│ ├── index.tsx # WithWithoutContainer
│ └── ui.tsx # WithWithout
└── features/
├── index.tsx # FeaturesContainer
└── ui.tsx # FeaturesExemples de blocks
- Hero : Section principale d'accueil
- WithWithout : Comparaison avant/après
- Features : Liste des fonctionnalités
- Pricing : Section de tarification
- Testimonials : Témoignages clients
Dossier components/
Les components sont des éléments d'interface réutilisables qui composent les blocks.
components/
├── ui/ # Composants shadcn
│ ├── button.tsx
│ ├── card.tsx
│ └── input.tsx
├── layout/ # Composants de mise en page
│ ├── header.tsx
│ ├── footer.tsx
│ └── navigation.tsx
└── forms/ # Composants de formulaire
├── login-form.tsx
└── contact-form.tsxExemples de components
- Button : Bouton réutilisable
- Card : Carte de contenu
- Input : Champ de saisie
- Header : En-tête de page
- Navigation : Menu de navigation
Utilisation dans le rendu dynamique
Configuration des blocks
Dans page-render-config.tsx, utilisez toujours les Container (index.tsx) :
export const pageRenderConfig: SectionConfig[] = [
{
type: "section",
components: [
{
id: "hero",
component: "HeroContainer", // ✅ Container avec logique
props: {
title: "Votre titre",
description: "Votre description",
ctaText: "Commencer",
ctaHref: "/sign-up",
},
},
],
},
{
type: "section",
components: [
{
id: "features",
component: "FeaturesContainer", // ✅ Container avec logique
props: {
features: [
{ title: "Fonctionnalité 1", description: "..." },
{ title: "Fonctionnalité 2", description: "..." },
],
},
},
],
},
];Règles importantes
- Toujours utiliser les Container dans la configuration
- Ne jamais utiliser les UI directs dans le rendu dynamique
- Les Container gèrent la logique métier et l'état
- Les UI sont des composants purs sans logique
Bonnes pratiques
Séparation des responsabilités
- ui.tsx : Interface pure, props simples, pas de logique métier
- index.tsx : Logique métier, gestion d'état, orchestration des services
Réutilisabilité
- Components : Réutilisables dans tous les contextes
- Blocks : Réutilisables dans les landing pages
- UI : Réutilisables partout dans l'application
Maintenance
- Modifications UI : Toujours dans les fichiers
ui.tsx - Modifications logique : Toujours dans les fichiers
index.tsx - Nouveaux composants : Commencer par
ui.tsx, puisindex.tsx
Naming conventions
- Container :
[Name]Container(ex:HeroContainer) - UI :
[Name](ex:Hero) - Fichiers :
index.tsxetui.tsx
Intégration avec les services
Les Container peuvent intégrer les services de l'architecture hexagonale :
// Exemple d'intégration avec les services
export function PricingContainer({ plans }: PricingContainerProps) {
const { user } = useAuth();
const { createCheckoutSession } = usePayment();
const handleSubscribe = async (planId: string) => {
if (!user) {
// Rediriger vers la connexion
window.location.href = "/sign-in";
return;
}
try {
const session = await createCheckoutSession(planId);
window.location.href = session.url;
} catch (error) {
console.error("Erreur lors de la création de la session", error);
}
};
return <Pricing plans={plans} onSubscribe={handleSubscribe} isAuthenticated={!!user} />;
}Cette architecture permet une séparation claire entre l'interface utilisateur et la logique métier, facilitant la maintenance et l'évolution des composants.