Sidebar - Comunicazione con Iframe
Panoramica
Il controllo della sidebar può essere gestito dall'interno dell'iframe usando l'API esposta su window.parent.sidebar. Questo permette all'applicazione PHP legacy di controllare lo stato della sidebar nella SPA React.
La sidebar ha comportamenti differenti basati sulla risoluzione dello schermo per garantire la migliore esperienza utente su tutti i dispositivi.
Comportamenti Basati sulla Risoluzione
La sidebar utilizza due breakpoint principali per determinare il suo comportamento:
Breakpoint: 1024px (Mobile Mode)
Larghezza finestra < 1024px:
- La sidebar passa in modalità mobile (Sheet laterale)
- Si sovrappone al contenuto invece di spostarlo
- Usa componente
SheetRootinvece della sidebar desktop - La chiusura avviene anche cliccando sull'overlay
Larghezza finestra >= 1024px:
- La sidebar è in modalità desktop
- Può essere collapsed/expanded
- Il contenuto si adatta alla larghezza disponibile
Breakpoint: 1280px (Stato Iniziale)
Larghezza finestra < 1280px:
- ✅ La sidebar parte chiusa al caricamento iniziale
- ✅ Il fallback automatico è disabilitato (non si riapre automaticamente)
- ℹ️ L'utente può comunque aprirla manualmente con il toggle
- ℹ️ L'iframe può comunque controllarla tramite
window.parent.sidebar.open()
Larghezza finestra >= 1280px:
- ✅ La sidebar parte aperta al caricamento iniziale
- ✅ Il fallback automatico è attivo (si riapre dopo 300ms se l'iframe non la controlla)
- ℹ️ Comportamento ideale per schermi desktop
Riepilogo Comportamenti
| Risoluzione | Stato Iniziale | Fallback Auto | Modalità | Note |
|---|---|---|---|---|
| < 1024px | Chiusa | Disabilitato | Mobile (Sheet) | Overlay sopra il contenuto |
| 1024px - 1279px | Chiusa | Disabilitato | Desktop | Sidebar collassabile |
| >= 1280px | Aperta | Attivo (300ms) | Desktop | Esperienza desktop completa |
Implementazione dei Breakpoint
File: app/components/ui/sidebar.tsx
function SidebarProvider({ defaultOpen, ... }) {
// Breakpoint mobile: 1025px (attiva mobile mode quando width <= 1024px)
const isMobile = useIsMobile(1025)
// Stato iniziale basato sulla risoluzione
const [_open, _setOpen] = React.useState(() => {
if (defaultOpen !== undefined) {
return defaultOpen
}
// >= 1280px: aperta, < 1280px: chiusa
return window.innerWidth >= 1280
})
// ...
}2
3
4
5
6
7
8
9
10
11
12
13
14
File: app/routes/r.$path.tsx
// Fallback per riaprire la sidebar dopo 300ms
const sidebarFallbackTimer = setTimeout(() => {
const hasControlled = useSidebarStore.getState().iframeHasControlledSidebar;
if (hasControlled) {
console.log("Fallback saltato: iframe ha controllato la sidebar");
return;
}
// Non riaprire automaticamente su schermi piccoli
if (window.innerWidth < 1280) {
console.log("Fallback saltato: schermo piccolo");
return;
}
window.sidebar?.open();
}, 300);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
API Disponibile
window.parent.sidebar.close()
Chiude la sidebar.
Esempio:
// Dall'interno dell'iframe
window.parent.sidebar.close();2
window.parent.sidebar.open()
Apre la sidebar.
Esempio:
// Dall'interno dell'iframe
window.parent.sidebar.open();2
window.parent.sidebar.toggle()
Alterna lo stato della sidebar (aperta ↔ chiusa).
Esempio:
// Dall'interno dell'iframe
window.parent.sidebar.toggle();2
Implementazione
Hook: useTopbarSidebar
L'hook useTopbarSidebar gestisce la registrazione dell'API sidebar sul window parent. Segue lo stesso pattern degli altri hook di comunicazione (topbar, messages, cart, navigation).
File: app/hooks/ui/useTopbarSidebar.ts
import { useEffect } from "react";
export function useTopbarSidebar(setOpen?: (open: boolean) => void, toggleSidebar?: () => void) {
useEffect(() => {
console.log("[useTopbarSidebar] Hook montato, registrazione sidebar...");
const closeFromIframe = () => {
console.log("[useTopbarSidebar] 🚪 close chiamata dall'iframe");
if (setOpen) {
setOpen(false);
console.log("[useTopbarSidebar] ✅ Sidebar chiusa");
} else {
console.warn("[useTopbarSidebar] ⚠️ setOpen non disponibile");
}
};
const openFromIframe = () => {
console.log("[useTopbarSidebar] 🚪 open chiamata dall'iframe");
if (setOpen) {
setOpen(true);
console.log("[useTopbarSidebar] ✅ Sidebar aperta");
} else {
console.warn("[useTopbarSidebar] ⚠️ setOpen non disponibile");
}
};
const toggleFromIframe = () => {
console.log("[useTopbarSidebar] 🔄 toggle chiamata dall'iframe");
if (toggleSidebar) {
toggleSidebar();
console.log("[useTopbarSidebar] ✅ Sidebar toggled");
} else {
console.warn("[useTopbarSidebar] ⚠️ toggleSidebar non disponibile");
}
};
window.sidebar = {
close: closeFromIframe,
open: openFromIframe,
toggle: toggleFromIframe,
};
console.log("[useTopbarSidebar] ✅ Oggetto sidebar registrato sul window");
return () => {
console.log("[useTopbarSidebar] ⚠️ Cleanup: rimozione sidebar...");
delete window.sidebar;
};
}, [setOpen, toggleSidebar]);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Bridge Component: SidebarIframeBridge
Per accedere al useSidebar context, viene utilizzato un componente bridge che deve essere renderizzato dentro il SidebarProvider.
File: app/components/layout/sidebar/sidebar-iframe-bridge.tsx
import { useSidebar } from "@/components/ui/sidebar";
import { useTopbarSidebar } from "@/hooks/ui/useTopbarSidebar";
export function SidebarIframeBridge() {
const { setOpen, toggleSidebar } = useSidebar();
useTopbarSidebar(setOpen, toggleSidebar);
return null;
}2
3
4
5
6
7
8
Utilizzo in AuthenticatedLayout
Il bridge component viene renderizzato dentro il SidebarProvider (solo quando non è in minimal mode):
<SidebarProvider>
<SidebarIframeBridge />
<AppSidebar className="bg-secondary" />
<SidebarInset>
{/* ... */}
</SidebarInset>
</SidebarProvider>2
3
4
5
6
7
In minimalMode, la sidebar non è disponibile e l'API viene comunque registrata ma senza effetto.
Test
Pagina di Test
La pagina /test-iframe include test per verificare la funzionalità della sidebar:
- Check Sidebar API - Verifica che
window.parent.sidebarsia disponibile con tutti i metodi - Close Sidebar - Testa la chiusura della sidebar dall'iframe
- Open Sidebar - Testa l'apertura della sidebar dall'iframe
- Toggle Sidebar - Testa l'alternanza dello stato della sidebar dall'iframe
Test Manuale
- Avvia l'applicazione in development
- Naviga a
/test-iframe - Clicca sulla tab "🚪 Sidebar"
- Clicca su "Verifica Sidebar API" per controllare la disponibilità di tutti i metodi
- Prova i pulsanti:
- "🚪 Chiudi Sidebar" per chiudere
- "📖 Apri Sidebar" per aprire
- "🔄 Toggle Sidebar" per alternare lo stato
Logging
L'hook logga tutte le operazioni nella console per facilitare il debug:
- Montaggio dell'hook e registrazione dell'API
- Chiamate dall'iframe
- Cleanup alla rimozione del componente
Note Tecniche
Pattern di Implementazione
L'implementazione segue lo stesso pattern degli altri hook di comunicazione iframe:
- Hook React che gestisce la registrazione sul
window - Oggetto esposto con metodi callable dall'iframe
- Cleanup automatico al unmount del componente
- Type definitions in
window.d.ts - Test nell'iframe di test
Type Definitions
I types sono definiti in app/types/window.d.ts:
interface Window {
sidebar?: {
/**
* Chiude la sidebar
*/
close: () => void;
/**
* Apre la sidebar
*/
open: () => void;
/**
* Alterna lo stato della sidebar
*/
toggle: () => void;
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Compatibilità
- ✅ Funziona solo quando l'iframe è caricato all'interno del
AuthenticatedLayout - ✅ La sidebar deve essere disponibile (modalità non-minimal)
- ✅ Compatibile con il pattern StrictMode-safe di React 19
- ✅ Responsive: si adatta automaticamente a desktop (>= 1024px) e mobile (< 1024px)
- ✅ Stato iniziale adattivo basato su risoluzione (breakpoint 1280px)
- ✅ Fallback automatico intelligente (attivo solo su schermi >= 1280px)
Casi d'Uso
Controllo Responsive
È consigliabile verificare la risoluzione prima di controllare la sidebar per rispettare le convenzioni UX:
// Controllo adattivo della sidebar basato sulla risoluzione
function openSidebarIfDesktop() {
if (window.parent && window.parent.sidebar) {
// Apri la sidebar solo su schermi >= 1280px
if (window.innerWidth >= 1280) {
window.parent.sidebar.open();
console.log('Sidebar aperta (desktop)');
} else {
console.log('Sidebar non aperta automaticamente (mobile/tablet)');
}
}
}
// Chiudi sempre la sidebar indipendentemente dalla risoluzione
function closeSidebar() {
if (window.parent && window.parent.sidebar) {
window.parent.sidebar.close();
console.log('Sidebar chiusa');
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Chiusura Automatica
L'applicazione PHP può chiudere automaticamente la sidebar quando l'utente inizia a lavorare su una schermata specifica:
// Dall'interno dell'iframe PHP
if (window.parent && window.parent.sidebar) {
window.parent.sidebar.close();
}2
3
4
Nota: La chiusura funziona su tutte le risoluzioni e modalità (desktop/mobile).
Apertura al Click
L'applicazione PHP può aprire la sidebar per mostrare il menu:
// Dall'interno dell'iframe PHP
if (window.parent && window.parent.sidebar) {
window.parent.sidebar.open();
}2
3
4
Nota: L'apertura funziona su tutte le risoluzioni. Tuttavia, su schermi < 1280px, considera se l'apertura automatica migliora o peggiora l'esperienza utente (potrebbe coprire il contenuto).
Toggle per Shortcuts
L'applicazione PHP può alternare lo stato della sidebar con una scorciatoia:
// Dall'interno dell'iframe PHP
if (window.parent && window.parent.sidebar) {
window.parent.sidebar.toggle();
}2
3
4
Controllo Condizionale
// Verifica disponibilità prima di chiamare
function controlSidebar(action) {
if (window.parent && window.parent.sidebar) {
switch(action) {
case 'close':
if (typeof window.parent.sidebar.close === 'function') {
window.parent.sidebar.close();
console.log('Sidebar chiusa');
}
break;
case 'open':
if (typeof window.parent.sidebar.open === 'function') {
window.parent.sidebar.open();
console.log('Sidebar aperta');
}
break;
case 'toggle':
if (typeof window.parent.sidebar.toggle === 'function') {
window.parent.sidebar.toggle();
console.log('Sidebar toggled');
}
break;
}
} else {
console.warn('Sidebar API non disponibile');
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Migrazione Sheet → SheetRoot
Contesto Storico
Commit: 857829e - "fix: replace Sheet component with SheetRoot in ModuleMenuMobile and Sidebar for improved mobile handling"
Data: Dicembre 2024
Cambiamento
Il progetto è migrato da Sheet a SheetRoot per i componenti mobili:
Prima:
import { Sheet, SheetContent } from "@elerama/ui"
<Sheet open={openMobile} onOpenChange={setOpenMobile}>
<SheetContent side="left">
{/* contenuto */}
</SheetContent>
</Sheet>2
3
4
5
6
7
Dopo:
import { SheetRoot, SheetContent } from "@elerama/ui"
<SheetRoot open={openMobile} onOpenChange={setOpenMobile}>
<SheetContent side="left">
{/* contenuto */}
</SheetContent>
</SheetRoot>2
3
4
5
6
7
Motivazioni
Migliore Mobile Handling
SheetRootoffre un controllo più granulare sul comportamento del drawer- Compatibilità migliorata con il Touch Scroll Fix
- Gestione più robusta degli eventi touch su dispositivi mobili
Architettura Componenti
- Separazione più chiara tra root container e content
- Allineamento con pattern Radix UI moderni
- Facilita l'implementazione di funzionalità avanzate
Stabilità
- Risolve edge cases su iOS Safari con scroll touch
- Migliore gestione dell'overlay e click-outside
- Performance migliorata su dispositivi low-end
Componenti Aggiornati
File modificati:
app/components/ui/sidebar.tsx:14,182- Sidebar mobileapp/components/layout/topbar/module-menu-mobile.tsx:14,144- Menu modulo mobile
Breaking Changes
Nessun breaking change per i consumer:
- Le props sono identiche
- Il comportamento è retrocompatibile
- L'API esposta su
window.sidebarnon cambia
Migrazione per Altri Componenti
Se hai componenti custom che usano Sheet, migrali seguendo questo pattern:
Aggiorna import
typescript// Prima import { Sheet } from "@elerama/ui" // Dopo import { SheetRoot } from "@elerama/ui"1
2
3
4
5Sostituisci componente
typescript// Prima <Sheet open={open} onOpenChange={setOpen}> // Dopo <SheetRoot open={open} onOpenChange={setOpen}>1
2
3
4
5Verifica touch scroll
- Se il contenuto è scrollabile, applica il Touch Scroll Fix
- Testa su dispositivi touch reali (non solo DevTools)
Riferimenti
- Touch Scroll Fix - Fix per scroll mobile
- ModuleMenuMobile - Esempio implementazione
Best Practices
Raccomandazioni per l'Uso dell'API
Apertura Automatica
- ✅ Consigliata solo su schermi >= 1280px
- ⚠️ Valuta attentamente su schermi < 1280px (potrebbe coprire il contenuto)
- ℹ️ Il fallback gestisce già l'apertura automatica su desktop
Chiusura Automatica
- ✅ Consigliata su tutte le risoluzioni quando necessario
- ✅ Utile per massimizzare lo spazio disponibile durante il lavoro
Toggle
- ✅ Ideale per shortcuts da tastiera o click dell'utente
- ✅ Funziona bene su tutte le risoluzioni
Verifica della Risoluzione
javascript// Pattern consigliato per apertura condizionale function shouldOpenSidebar() { return window.innerWidth >= 1280; } if (shouldOpenSidebar() && window.parent?.sidebar) { window.parent.sidebar.open(); }1
2
3
4
5
6
7
8Rispetta il Controllo dell'Utente
- Se l'utente chiude manualmente la sidebar, evita di riaprirla automaticamente
- Considera l'uso di localStorage per tracciare le preferenze utente
- Il sistema di fallback rispetta già questa logica (verifica
iframeHasControlledSidebar)
Casi da Evitare
❌ Non aprire automaticamente la sidebar su mobile senza un motivo valido:
// NON FARE - ignora la risoluzione
window.parent.sidebar.open(); // Copre il contenuto su schermi piccoli2
❌ Non forzare lo stato ripetutamente:
// NON FARE - loop infinito
setInterval(() => {
window.parent.sidebar.open();
}, 1000);2
3
4
✅ FARE - Rispetta la risoluzione:
// Pattern corretto
if (window.innerWidth >= 1280 && window.parent?.sidebar) {
window.parent.sidebar.open();
}2
3
4