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.
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={{ defaultWidth: 280, minWidth: 220, maxWidth: 400 }}
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
| Prop | Type | Défaut | Description |
|---|---|---|---|
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é
ariaLabelnomme 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.
<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).