Forme DS

Patterns & Shell

FormeAppShell

Le shell d'application du DS 2.0 : la coquille complète d'une page — barre latérale à gauche, contenu à droite, et le tiroir mobile natif (burger + drawer) géré pour toi. Il enrobe l'AppShell d'Astryx (variant section) et FormeSideNav sous une seule API Forme.

Source unique du cadre. FormeAppShell est la composition de shell partagée pour Forme®Admin, AI, Space et Frame. Fini les cadres .app-frame maison, les data-drawer sur <html>, les burgers et backdrops par-app : le responsive, le repli et le tiroir viennent d'Astryx, pas du CSS d'app. L'app ne fournit que des données et des slots.

Aperçu

Le cadre section : nav à gauche (sections, icônes, item isSelected, badges), un fin trait séparateur, et le contenu de page en children. (Tire la poignée à droite pour voir le contenu s'adapter.)

import { FormeAppShell, FormeAdminLockup, FormeAccountMenu, AccountMenuItem } from "@forme-ch/ui";
import { SearchInput } from "@forme-ch/ui/controls";
import { ReceiptIcon, FileTextIcon, UsersThreeIcon, SignOutIcon } from "@forme-ch/ui/icons";
import Link from "next/link";

<FormeAppShell
  variant="section"
  linkComponent={Link}
  collapsible
  resizable=&#123;&#123; defaultWidth: 280, minWidth: 220, maxWidth: 400 &#125;&#125;
  header={<FormeAdminLockup height={20} />}
  topContent={<SearchInput placeholder="Rechercher…" size="sm" />}
  footer={
    <FormeAccountMenu name="Jeremy Doe" sub="Forme®Admin" avatar="JD">
      <AccountMenuItem icon={<SignOutIcon size={15} />}>Déconnexion</AccountMenuItem>
    </FormeAccountMenu>
  }
  groups={[
    { title: "Ventes", items: [
      { label: "Factures", href: "/admin/factures", icon: <ReceiptIcon size={18} />, isSelected: true, badge: 12 },
      { label: "Devis", href: "/admin/devis", icon: <FileTextIcon size={18} />, badge: 3 },
    ]},
    { title: "Organisation", items: [
      { label: "Clients", href: "/admin/clients", icon: <UsersThreeIcon size={18} /> },
    ]},
  ]}
>
  <PageContent />
</FormeAppShell>

Import

// Ré-exporté par l'index et par l'entrée /shell
import { FormeAppShell } from "@forme-ch/ui";
// ou
import { FormeAppShell } from "@forme-ch/ui/shell";

Deux modes de barre latérale

Nav simple (la plupart des pages) : passe groups, et FormeAppShell construit la FormeSideNav pour toi. Nav applicative riche (un arbre de conversations, des menus contextuels par ligne — ce que groups ne couvre pas) : passe une barre déjà composée en sideNav. Elle reste un <FormeSideNav> (items riches en children) — jamais un SideNav Astryx brut : le style de barre est garanti par le socle, l'app ne fournit que le contenu métier.

// Mode riche : la sidebar métier reste un FormeSideNav
<FormeAppShell sideNav={<ChatSidebar />}>
  <ChatContent />
</FormeAppShell>

Mobile — natif

Sous le breakpoint (défaut md), AppShell génère le burger et le tiroir automatiquement à partir de la barre. Aucune plomberie côté app. Pour refermer le tiroir après une action (sélection d'une conversation, d'un projet…), pilote-le en mode contrôlé avec mobileOpen / onMobileOpenChange.

Props

PropTypeDéfautDescription
children * ReactNode — Contenu de page — rendu en <main> scrollable par AppShell.
groups FormeSideNavGroup[] — Sections de navigation (data-mode) passées à FormeSideNav. Optionnel : ignoré si sideNav est fourni.
sideNav ReactNode — Barre déjà composée (mode « sidebar riche »). Si fournie, elle remplace la nav construite depuis groups. Elle DOIT être un <FormeSideNav> — jamais un SideNav Astryx brut.
header ReactNode — En-tête sticky de la nav : le commutateur des espaces <FormeEspaceSwitcher /> (recommandé — voir la page FormeSideNav), ou un lockup / marque.
topContent ReactNode — Contenu sticky sous le header — typiquement le déclencheur de recherche ⌘K.
footer ReactNode — Footer de la nav : c'est ici qu'on compose le menu de compte (<FormeAccountMenu />).
footerIcons ReactNode — Barre d'icônes sticky tout en bas de la nav.
sidebarChildren ReactNode — Contenu libre rendu après les groups (listes dynamiques : conversations, projets…).
banner ReactNode — Bandeau système au-dessus du contenu (scrolle avec la page) — ex. mode lecture seule.
variant "section" | "wash" | "surface" | "elevated" "section" Fond de nav / séparateurs. Forme utilise « section » partout (fin trait séparateur, look classique).
collapsible boolean | SideNavCollapsibleConfig — Autorise le repli de la barre (config Astryx : hasButton, buttonLabel…).
resizable boolean | ResizableConfig — Redimensionnement. ResizableConfig = { defaultWidth?, minWidth?, maxWidth?, autoSaveId? }.
linkComponent LinkComponentType — Composant lien du framework (ex. next/link), branché à toute la nav.
ariaLabel string "Navigation" Nom accessible de la région de navigation.
breakpoint "sm" | "md" | "lg" | "none" "md" Largeur sous laquelle la nav bascule en drawer mobile (géré nativement).
contentPadding SpacingStep — Padding du <main>. 4 (16px) pour formulaires/pages texte, 0 pour dashboards/tables edge-to-edge.
height "fill" | "auto" "fill" fill = shell plein écran, contenu scrolle ; auto = la page scrolle.
mobileOpen boolean — État contrôlé du drawer mobile. À fournir avec onMobileOpenChange quand l'app doit piloter le tiroir (ex. le refermer après sélection).
onMobileOpenChange (isOpen: boolean) => void — Callback quand l'ouverture du drawer mobile change (mode contrôlé).
defaultIsMobile boolean — Hint SSR : rendu initial en layout mobile (dérivé d'un cookie/UA). Évite le flash.
className string — Classe(s) sur le conteneur racine du shell.
sidebarClassName string — Classe(s) sur la nav (intégration fine par-app).

Composition & slots

Le cadre est fixe : la nav (header → topContent → groups / sideNav → sidebarChildren → footer) à gauche, le banner puis les children à droite. Le menu de compte se compose dans footer avec FormeAccountMenu. La barre elle-même est documentée sur FormeSideNav.

Accessibilité

  • ariaLabel nomme la région de navigation (défaut « Navigation »).
  • Le contenu est rendu dans un <main> — un seul par page.
  • Le tiroir mobile gère le focus et la fermeture (Échap) nativement — ne le réimplémente pas.

Do & Don't

✓ À faire

Utiliser FormeAppShell pour toute page avec barre latérale. Passer groups (calculés : actif, compteurs) ou une FormeSideNav en sideNav. Composer le compte dans footer. Garder variant="section".

✕ À éviter

Recréer un cadre .app-frame maison, un burger ou un data-drawer par-app. Passer un SideNav Astryx brut en sideNav. Changer de variant d'une app à l'autre.

Commutateur des espaces. En tête de la nav, le shell héberge le <FormeEspaceSwitcher /> (slot header) — le wordmark Forme® constant, le nom de lespace actif, un menu vers les autres espaces (registre partagé FORME_ESPACES). Détail et props sur la page FormeSideNav. La barre nest plus repliable par défaut (collapsible=false).