"use client" import { useEffect, useRef, useState } from "react" import { Icon } from "@iconify/react" import { LandingReveal } from "@/components/landing/landing-reveal" import { ULTICAL_APP_NAME } from "@/lib/suite/page-metadata" import { cn } from "@/lib/utils" type DemoTab = { id: string label: string icon: string src: string fakeUrl: string hint: string } const DEMO_TABS: DemoTab[] = [ { id: "mail", label: "Boîte mail", icon: "mdi:email-outline", src: "/demo/mail", fakeUrl: "suite.votre-domaine.fr/mail", hint: "Lisez, archivez, répondez, composez — comme dans la vraie boîte.", }, { id: "drive", label: "UltiDrive", icon: "mdi:folder-outline", src: "/demo/drive", fakeUrl: "suite.votre-domaine.fr/drive", hint: "Parcourez, classez, favorisez et recherchez vos fichiers.", }, { id: "agenda", label: ULTICAL_APP_NAME, icon: "mdi:calendar-outline", src: "/demo/agenda", fakeUrl: "suite.votre-domaine.fr/agenda", hint: "Créez des événements, invitez des participants, ajoutez une visio.", }, { id: "contacts", label: "Contacts", icon: "mdi:account-group-outline", src: "/demo/contacts", fakeUrl: "suite.votre-domaine.fr/contacts", hint: "Recherchez, créez et modifiez vos contacts.", }, { id: "docs", label: "Éditeur UltiDocs", icon: "mdi:file-document-edit-outline", src: "/demo/docs", fakeUrl: "suite.votre-domaine.fr/docs", hint: "Le vrai éditeur de la suite : mise en forme, tableaux, styles, pages.", }, ] type LandingDemoSectionProps = { activeTab?: string onActiveTabChange?: (tabId: string) => void /** Incrémenté depuis le hero pour forcer le montage des iframes avant scroll. */ revealNonce?: number } export function LandingDemoSection({ activeTab: controlledTab, onActiveTabChange, revealNonce = 0, }: LandingDemoSectionProps = {}) { const sectionRef = useRef(null) const [visible, setVisible] = useState(false) const [internalTab, setInternalTab] = useState(DEMO_TABS[0].id) const activeTab = controlledTab ?? internalTab const setActiveTab = (tabId: string) => { if (onActiveTabChange) onActiveTabChange(tabId) else setInternalTab(tabId) } /** Onglets dont l'iframe a été montée (état conservé au changement d'onglet). */ const [mounted, setMounted] = useState>({}) /** Incrément par onglet pour réinitialiser la démo (remount iframe). */ const [resetKeys, setResetKeys] = useState>({}) useEffect(() => { const node = sectionRef.current if (!node || visible) return const observer = new IntersectionObserver( (entries) => { if (entries.some((entry) => entry.isIntersecting)) { setVisible(true) observer.disconnect() } }, { rootMargin: "240px 0px" } ) observer.observe(node) return () => observer.disconnect() }, [visible]) useEffect(() => { if (!revealNonce) return setVisible(true) setMounted((prev) => ({ ...prev, [activeTab]: true })) }, [revealNonce, activeTab]) useEffect(() => { if (!visible) return setMounted((prev) => (prev[activeTab] ? prev : { ...prev, [activeTab]: true })) }, [visible, activeTab]) const active = DEMO_TABS.find((tab) => tab.id === activeTab) ?? DEMO_TABS[0] return (
Démo interactive

Essayez maintenant, sans compte

De vraies fenêtres, de vrais comportements. Tout reste dans votre onglet : zéro rétention, rien n'est envoyé ni enregistré.

{/* Onglets */}
{DEMO_TABS.map((tab) => ( ))}
{/* Fenêtre virtuelle */}
{active.fakeUrl}
Plein écran
{DEMO_TABS.map((tab) => mounted[tab.id] ? (