Aller au contenu

Vue d'ensemble du Frontend

Le frontend de Rebase est un framework React qui affiche votre panneau d’administration. Il lit les définitions de vos collections et génère automatiquement les tables, les formulaires, la navigation et le routage.

Dans la structure par défaut, le panneau d’administration est le frontend : il est servi à la racine de votre URL déployée. Si vous créez plutôt votre propre application produit, vous pouvez monter l’administration sous un préfixe tel que /admin dans le même déploiement — voir Modifier l’URL de base.

Voici frontend/src/App.tsx tel que rebase init l’écrit — l’ensemble du panneau d’administration, quatre déclarations au sein d’un seul provider :

import React from "react";
import { Rebase, RebaseAuth, useRebaseAuthController } from "@rebasepro/app";
import { RebaseCMS, RebaseShell } from "@rebasepro/cms";
import { RebaseStudio } from "@rebasepro/studio";
import { createRebaseClient } from "@rebasepro/client";
import { collections } from "virtual:rebase-collections";
const client = createRebaseClient({
baseUrl: import.meta.env.VITE_API_URL,
auth: { authFlowMode: "cookie" }
});
export function App() {
const authController = useRebaseAuthController({ client });
return (
<Rebase client={client} authController={authController}>
{/* Sign-in screen. Pass `loginView` to replace it. */}
<RebaseAuth/>
<RebaseCMS collections={collections}/>
<RebaseStudio/>
<RebaseShell title="My App"/>
</Rebase>
);
}

Les trois premiers ne font aucun rendu : ils enregistrent la configuration dans le provider. C’est <RebaseShell> qui dessine l’interface — il lit ce registre et construit la navigation, les routes et la mise en page à partir de celui-ci. L’ordre dans lequel ils apparaissent n’a donc pas d’importance, et ajouter une fonctionnalité revient à ajouter un composant, sans avoir à restructurer l’arborescence.

Composant Package Enregistre
<RebaseAuth> @rebasepro/app l’écran de connexion (loginView)
<RebaseCMS> @rebasepro/cms les collections, les vues personnalisées, la page d’accueil, l’éditeur de collection
<RebaseStudio> @rebasepro/studio les outils de développement (SQL, RLS, journaux, sauvegardes…)
<RebaseShell> @rebasepro/cms rien — il affiche l’administration à partir de tout ce qui précède

Supprimez <RebaseStudio> et vous obtenez un CMS dédié uniquement au contenu ; supprimez <RebaseCMS> et vous n’avez que les outils de développement. Pour agencer le shell manuellement, consultez Avancé : mise en page manuelle.

<Rebase> est le provider racine qui rend toutes les fonctionnalités de Rebase disponibles pour les composants enfants via le contexte. Il accepte :

L’intégralité des vingt-trois propriétés — le tableau n’en listait auparavant que dix, dont deux n’étaient même jamais lues par le composant :

Prop Description
children Les composants racines de l’administration — <RebaseCMS>, <RebaseStudio>, <RebaseShell>. Une fonction de rendu sert d’échappatoire pour une mise en page manuelle.
apiUrl URL de base de l’API backend, mise à disposition de chaque hook via useApiConfig()
dateTimeFormat Format d’affichage des dates. Valeur par défaut : MMMM dd, yyyy, HH:mm:ss
locale Langue initiale de l’administration, et locale dans laquelle les dates sont formatées — voir Traductions
client Instance de RebaseClient : la source par défaut pour les données, l’authentification et le stockage
dataSources Sources de données supplémentaires, pour les collections qui en désignent une — voir Sources multiples
authController État et méthodes d’authentification. Remplace purement et simplement la souscription à client.auth
storageSource Source de stockage par défaut, remplaçant client.storage
storageSources Sources de stockage nommées au-delà de la source par défaut
databaseAdmin Opérations administratives sur la base de données (SQL, découverte de schéma). Seul Studio en a besoin
userConfigPersistence Préférences locales de l’interface — largeurs de colonnes, groupes repliés
onAnalyticsEvent Appelé pour chaque événement d’analytics émis par l’administration
entityLinkBuilder Retourne une URL pour le bouton « ouvrir dans votre application » sur un formulaire d’entité
plugins Instances de plugins — voir Plugins
slots Contributions de slots déclarées directement, sans plugin
propertyConfigs Widgets de champs personnalisés, indexés par le nom spécifié par une propriété dans propertyConfig
entityViews Onglets de vue d’entité personnalisés globaux
collectionViews Modes de vue de collection personnalisés, accessibles à toute collection par key
entityActions Actions globales d’entité
effectiveRoleController Simuler un rôle différent lorsque le mode dev est activé
modeController Depuis 0.25 Confier à Rebase le mode clair/sombre de l’application hôte ; Rebase ne touche alors jamais à <html>, à prefers-color-scheme ni à localStorage — voir Styles
translations Remplacer ou étendre n’importe quelle chaîne de l’interface, indexée par locale — voir Traductions
components Remplacer les composants intégrés — voir Remplacements de composants

Les contrôleurs de navigation, d’URL et de registre de collections ne sont pas des props de <Rebase> — ils sont construits par les hooks ci-dessous et consommés dans l’arbre d’administration (<RebaseShell> les connecte pour vous dans la structure par défaut).

Le préfixe d’URL non plus. Lorsque l’administration est montée sous un chemin spécifique, cela se configure sur <RebaseCMS basePath="/admin">, qui est l’élément qui résout les URL en collections — et uniquement lorsque le routeur n’a pas son propre basename. Voir Modifier l’URL de base.

Il existe deux couches de données, et elles ne sont pas interchangeables. Passer l’une là où l’autre est attendue provoque une erreur de type ; il est donc utile de le savoir avant de connecter manuellement un contrôleur.

Structure Où l’obtenir À quoi ressemble une ligne
SDK RebaseSdkData — lignes plates client.data, et context.data dans les callbacks backend row.title
Admin RebaseData — modèle de vue (view-model) Entity useData(), à l’intérieur de l’arbre <Rebase> entity.values.title

La couche SDK est la surface publique et symétrique : identique sur le client frontend et dans les callbacks backend. La couche Entity est le view-model de l’administration — elle ajoute l’enveloppe id / path / values sur laquelle reposent le rendu des vues de collections et les formulaires. CollectionAccessor et FindResponse lui appartiennent et sont marqués @internal pour cette raison.

<Rebase> constitue la frontière entre les deux : il prend votre client.data plat et l’enveloppe avec wrapAsEntityData() avant de le fournir en tant que RebaseData pour l’administration. Vous ne l’appelez jamais vous-même — vous récupérez simplement la structure dont vous avez besoin au bon endroit :

// Flat rows — anywhere, including outside React.
const { data: posts } = await client.data.posts.find();
posts[0].title;
// Entity view-model — inside the <Rebase> tree only.
// `data.posts` also works at runtime; `collection()` is the typed accessor.
const data = useData();
const { data: entities } = await data.collection("posts").find();
entities[0].values.title;

Une collection ou un enregistrement dans une page qui vous appartient

Section intitulée « Une collection ou un enregistrement dans une page qui vous appartient »

Depuis 0.25 En 0.24, CollectionPanel ne prend qu’un path enregistré et affiche « Collection not found » en dehors de l’arbre d’administration complet, et EntityFormPanel et CollectionsProvider n’existent pas.

Vous n’avez pas besoin de toute l’administration pour afficher une seule table. <CollectionPanel> prend la collection comme valeur et fonctionne n’importe où sous <Rebase> et un routeur React Router, sans <RebaseCMS>, <RebaseNavigation> ni <RebaseShell> :

import { Rebase, useRebaseAuthController } from "@rebasepro/app";
import { CollectionPanel } from "@rebasepro/cms";
import { postsCollection } from "../config/collections/posts";
// Plus BrowserRouter, or any other router, from "react-router".
export function App() {
const authController = useRebaseAuthController({ client });
return (
<Rebase client={client} authController={authController}>
<BrowserRouter>
<MyPageLayout>
<CollectionPanel collection={postsCollection}/>
</MyPageLayout>
</BrowserRouter>
</Rebase>
);
}

Un enregistrement s’ouvre par-dessus votre page, dans un panneau latéral ou une boîte de dialogue, et jamais par URL : aucune route d’administration ne sert la page, de sorte que full_screen et split s’ouvrent comme un panneau latéral, et useUrlController().routesMounted vaut false pour vos propres composants à cet endroit. N’importe quel routeur convient : un simple <BrowserRouter>, un <MemoryRouter>, ou un routeur de données (data router). Un routeur reste requis. Un routeur de données permet aussi à un enregistrement comportant des modifications non enregistrées d’interrompre la navigation dans l’application ; avec les autres, seule l’invite de sortie de page du navigateur le protège. Enveloppez le panneau dans <RebaseAuthGate> si la page doit d’abord afficher la connexion de Rebase.

<EntityFormPanel> fait de même pour le formulaire d’un enregistrement. Il charge l’enregistrement par entityId, l’enregistre via la couche de données de <Rebase> comme le fait le formulaire propre à l’administration, et appelle onSaved après chaque enregistrement. Omettez entityId pour créer un enregistrement. onClose est appelé lorsque le formulaire demande à être quitté, par exemple après la suppression de son enregistrement :

<EntityFormPanel
collection={postsCollection}
entityId={postId}
onSaved={({ entity }) => console.log("saved", entity.id)}
onClose={() => navigate("/posts")}/>

Les deux apportent leur propre CollectionsProvider lorsqu’aucun n’existe au-dessus d’eux. C’est l’étape située en dessous, et vous pouvez la monter vous-même pour partager une seule portée entre plusieurs panneaux, ou pour utiliser les pièces de plus bas niveau (CollectionViewBinding, useSidePanel) qui s’y trouvent. À l’intérieur, un panneau peut nommer sa collection par path :

import { CollectionPanel, CollectionsProvider } from "@rebasepro/cms";
const collections = [postsCollection, authorsCollection];
<CollectionsProvider collections={collections}>
<CollectionPanel path="posts"/>
<CollectionPanel path="authors"/>
</CollectionsProvider>

CollectionsProvider contient un registre des collections que vous lui confiez, avec les modifyCollection et injectCollections de vos plugins appliqués comme dans l’administration, ainsi que les panneaux latéraux et boîtes de dialogue dans lesquels s’ouvrent les enregistrements, et la garde contre les modifications non enregistrées. Gardez le tableau collections stable : une constante de module, ou useMemo.

Tout ce qui suit remplace <RebaseShell>. Vous n’en avez besoin que si la mise en page par défaut ne convient pas — un habillage différent autour de l’administration, votre propre arborescence de routes, ou une application où l’administration n’est qu’une page parmi d’autres. Si vous ne remplacez pas la mise en page, vous pouvez passer directement à Vues personnalisées.

<RebaseShell> est un sucre syntaxique pour quatre couches, que vous pouvez utiliser une à une :

<Rebase client={client} authController={authController}>
<RebaseCMS collections={collections}/>
<RebaseStudio/>
{/* login screen until there is a user */}
<RebaseAuthGate>
{/* builds the navigation, URL and collection-registry controllers */}
<RebaseNavigation>
{/* the admin's routes, drawn inside the layout you pass */}
<RebaseRouteDefs layout={<RebaseLayout title="My App"/>}/>
</RebaseNavigation>
</RebaseAuthGate>
</Rebase>

L’ordre est fixe : RebaseAuthGate → RebaseNavigation → RebaseRouteDefs → RebaseLayout. RebaseAuthGate affiche la vue de connexion tant qu’aucun utilisateur n’est connecté, de sorte que rien en dessous ne s’affiche pour un visiteur non authentifié ; RebaseNavigation construit les contrôleurs de navigation, d’URL et de registre de collections lus par RebaseRouteDefs et chaque vue de collection, de sorte qu’utiliser RebaseRouteDefs en dehors lèvera une exception. Depuis 0.25 Pour intégrer une vue de collection ou un formulaire d’enregistrement dans une page qui vous appartient, sans les routes de l’administration, le CollectionsProvider, plus léger, construit ces contrôleurs à la place — voir Une collection ou un enregistrement dans une page qui vous appartient.

Chaque couche est utilisable séparément. <RebaseAuthGate> seul protège votre propre application derrière l’authentification de Rebase. Remplacez <RebaseLayout> par votre propre composant pour conserver le routage tout en changeant l’habillage ; supprimez également <RebaseRouteDefs> et vous construirez vous-même les routes à partir des composants décrits dans Composants de structure (Scaffold).

En dessous de ce niveau, <Rebase> accepte également une render prop au lieu d’enfants, ce qui vous transmet le contexte ainsi que l’indicateur de chargement et vous laisse le contrôle total de l’arbre :

<Rebase client={rebaseClient} authController={authController}>
{({ context, loading }) => (
<Scaffold>
<AppBar/>
<Drawer title="My App"/>
<Outlet/>
<SideDialogs/>
</Scaffold>
)}
</Rebase>

À ce stade, rien n’est connecté pour vous : vous construisez manuellement les contrôleurs ci-dessous et assurez vous-même le rendu des routes.

Les contrôleurs sont des hooks React qui configurent des aspects spécifiques du framework. <RebaseNavigation> les appelle tous pour vous — ne recourez à ceux-ci qu’au sein d’une render prop.

Le contrôleur principal qui relie l’ensemble :

Son paramètre data correspond au RebaseData au format Entity, il provient donc de useData() — et non de rebaseClient.data, qui correspond à la couche SDK en lignes plates. <Rebase> convertit l’un en l’autre pour vous (voir Deux structures de données ci-dessous), ce hook doit donc être appelé à l’intérieur de l’arbre <Rebase>.

const data = useData();
const navigationStateController = useBuildNavigationStateController({
collections: () => [...collections], // Collection definitions
views: customViews, // Custom navigation views
plugins, // Plugin instances
authController,
data,
collectionRegistryController,
urlController,
adminMode: adminModeController.mode
});

Gère la façon dont les collections sont résolues à partir des chemins d’URL :

const collectionRegistryController = useBuildCollectionRegistryController({
userConfigPersistence
});

Configure la génération d’URL :

const urlController = useBuildUrlController({
basePath: "/",
baseCollectionPath: "/c",
collectionRegistryController
});

Gère le thème clair/sombre. Tant qu’il est activé, il possède le thème de la page : il place la classe dark sur <html>, suit prefers-color-scheme jusqu’à ce que l’utilisateur choisisse un mode, et mémorise ce choix dans localStorage.

const modeController = useBuildModeController();
modeController.mode; // "light" | "dark"
modeController.setMode("dark"); // "light" | "dark" | "system" — "system" follows the OS again

<Rebase> en construit un lui-même.

Depuis 0.25 Lorsque le composant qui l’utilise se démonte, il retire la classe dark qu’il a ajoutée ; une classe dark déjà présente sur la page reste. Construisez le vôtre au-dessus de <Rebase> uniquement lorsque des composants extérieurs à celui-ci ont aussi besoin du mode, et confiez-le avec la prop modeController, pour qu’il n’y ait qu’un seul contrôleur et non deux. useBuildModeController({ enabled: false }) construit un contrôleur inerte : il garde mode dans l’état React et ne touche ni classe, ni media query, ni localStorage. Lorsque votre application possède déjà le thème, vous n’avez pas besoin de ce hook du tout : passez votre propre contrôleur comme modeController.

Bascule entre les modes Studio et Contenu :

const adminModeController = useBuildAdminModeController();
// Provides: adminModeController.mode ("cms" | "studio")
Composant Description
<Scaffold> Conteneur de mise en page principal avec barre latérale responsive
<AppBar> Barre de navigation supérieure avec recherche, bascule de mode, menu utilisateur
<Drawer> Navigation latérale avec liste des collections et liens vers les vues
<SideDialogs> Conteneur pour les éditeurs d’entités en panneau latéral
<RebaseRoutes> Conteneur de routes s’intégrant avec React Router
<RebaseRoute> Gère les routes des collections (/c/*)
<ContentHomePage> Page d’accueil par défaut affichant les cartes des collections
<StudioHomePage> Page d’accueil du mode Studio avec les outils de développement

Ajoutez des vues de navigation de premier niveau pour des tableaux de bord, des outils ou des pages personnalisées. Un AppView est un objet plat — tout ce qui suit se trouve au niveau racine, il n’y a pas de bloc admin imbriqué :

import type { AppView } from "@rebasepro/cms-types";
const views: AppView[] = [
{
slug: "dashboard",
name: "Dashboard",
icon: "LayoutDashboard",
view: <MyDashboard/>
},
{
slug: "settings",
name: "App Settings",
icon: "Settings",
group: "Admin",
// Register `settings/*` too, so the view can route inside itself.
nestedRoutes: true,
// Reachable by URL, but not listed in the drawer.
hideFromNavigation: true,
view: <AppSettings/>
}
];

Transmettez-les à <RebaseCMS>, aux côtés de vos collections — c’est le composant qui enregistre la navigation :

<RebaseCMS collections={collections} views={views}/>
Champ
slug le chemin auquel elle est accessible, sous la racine de l’administration
name le libellé dans le panneau latéral (drawer) et sur la page d’accueil
view l’élément à afficher, ou un ComponentType pour un chargement différé
icon un nom d’icône Lucide, ex. "ShoppingCart" — ou n’importe quel nœud
group regroupe les vues dans le panneau latéral ; "Admin" et "Settings" sont relégués en bas
pinToBottom place le groupe tout en bas quel que soit son nom — à privilégier par rapport aux deux chaînes magiques
nestedRoutes enregistre également slug/*, pour une vue ayant ses propres routes internes
hideFromNavigation conserve la route, mais masque l’entrée dans le menu de navigation
roles seuls les utilisateurs possédant l’un de ces rôles voient la vue ou peuvent y accéder
description Markdown, affiché sur la carte de la page d’accueil

Pour placer une vue dans le Studio plutôt que dans le CMS, passez-la à <RebaseStudio devViews>.

Rebase utilise Tailwind CSS v4 et prend en charge les modes clair et sombre. Personnalisez via :

  • Propriétés personnalisées CSS (variables) — Remplacer les design tokens
  • <Rebase modeController> — Confier à Rebase le mode clair/sombre de votre application
  • Configuration Tailwind — Personnalisation Tailwind standard
/* Override design tokens */
:root {
--font-sans: "Instrument Sans", sans-serif;
--font-headers: "Instrument Sans", sans-serif;
--font-mono: "JetBrains Mono", monospace;
}

Par défaut, <Rebase> possède le thème de la page : il place la classe dark sur <html>, suit prefers-color-scheme jusqu’à ce que l’utilisateur choisisse un mode dans l’administration, et mémorise ce choix dans localStorage.

Depuis 0.25 Lorsque <Rebase> se démonte, il retire la classe dark qu’il a ajoutée ; une classe déjà présente sur la page reste. En 0.24, la classe reste en place.

Depuis 0.25 Lorsque votre application a déjà un thème, passez-le comme modeController. Rebase y lit mode, appelle son setMode depuis son propre sélecteur de thème, et ne touche jamais à <html>, à prefers-color-scheme ni à localStorage :

import { useEffect, useMemo, useState } from "react";
import { Rebase, type ModeController } from "@rebasepro/app";
export function App() {
const [mode, setMode] = useState<"light" | "dark">("light");
const modeController = useMemo<ModeController>(() => ({
mode,
setMode: (next) => setMode(next === "dark" ? "dark" : "light")
}), [mode]);
// Your app applies the mode, as it does for the rest of the page.
useEffect(() => {
document.documentElement.classList.toggle("dark", mode === "dark");
}, [mode]);
return (
<Rebase client={client} modeController={modeController}>
<MyAdmin/>
</Rebase>
);
}

Le CSS du scaffold applique le mode sombre sous tout élément portant la classe dark (@custom-variant dark (&:where(.dark, .dark *))). Les panneaux latéraux, les boîtes de dialogue et les menus s’affichent dans un portail sur <body> : placez donc la classe sur <html> ou <body> plutôt que sur un wrapper autour de <Rebase>.

Envelopper <Rebase> dans un ModeControllerProvider ne produit pas cet effet : <Rebase> fournit un contrôleur qui lui est propre à l’intérieur. En 0.24, la prop n’existe pas, et il n’y a aucun moyen de confier à <Rebase> le mode de votre application.