)}
);
};
const useFlash = () => React.useContext(FlashContext);
// ============================================================
// SHEET / BOTTOM MODAL
// ============================================================
// V3.6 — corrections rigoureuses du scroll des sheets sur iOS Safari mobile :
//
// PROBLÈME : `max-height: 92vh` se calcule contre le viewport **complet**
// (y compris la zone occupée par la toolbar Safari), alors que
// `position: fixed; bottom: 0` se positionne au-dessus de la toolbar.
// Conséquence : le bas du sheet est masqué par la toolbar Safari, le
// scroll natif n'a pas conscience de ces 80-100px cachés, et les
// boutons "Envoyer" se retrouvent inaccessibles.
//
// SOLUTION : on calcule la hauteur réellement visible via
// `window.visualViewport.height` (la seule source de vérité quand la
// toolbar Safari change d'état) et on l'applique en pixels. Recalculé
// à chaque resize visualViewport (rotation, apparition/disparition
// de la toolbar, clavier qui s'ouvre, etc.).
//
// Compteur global pour ne pas réinitialiser body.overflow quand un
// sheet se ferme alors qu'un autre est encore ouvert.
let _sheetOpenCount = 0;
const Sheet = ({ open, onClose, title, children, maxHeight = '92vh' }) => {
// Hauteur dynamique en pixels, basée sur la vraie zone visible.
const [computedMaxH, setComputedMaxH] = React.useState(maxHeight);
React.useEffect(() => {
if (!open) return;
_sheetOpenCount += 1;
document.body.style.overflow = 'hidden';
// V4.1 — classe sur body pour permettre du CSS conditionnel
// (notamment cacher la carte Leaflet derrière le sheet)
document.body.classList.add('has-sheet-open');
const recompute = () => {
const vv = window.visualViewport;
const h = vv ? vv.height : window.innerHeight;
// V4.0 — On retire ~92px (offset bottom : tabbar 80px + ~12px de safe-area)
// pour que la maxHeight reflète l'espace VRAIMENT disponible pour le sheet.
// Avant : Math.floor(h * 0.92) → le sheet pouvait théoriquement dépasser
// l'écran par le haut (titre coupé sur iPhone X+ avec encoche).
const usableH = Math.max(280, h - 92);
setComputedMaxH(`${Math.floor(usableH)}px`);
};
recompute();
const vv = window.visualViewport;
if (vv) {
vv.addEventListener('resize', recompute);
vv.addEventListener('scroll', recompute);
}
window.addEventListener('resize', recompute);
return () => {
_sheetOpenCount = Math.max(0, _sheetOpenCount - 1);
if (_sheetOpenCount === 0) {
document.body.style.overflow = '';
document.body.classList.remove('has-sheet-open');
}
if (vv) {
vv.removeEventListener('resize', recompute);
vv.removeEventListener('scroll', recompute);
}
window.removeEventListener('resize', recompute);
};
}, [open, maxHeight]);
if (!open) return null;
return (
<>
{/* V3.8 P2 — refonte scroll :
1) Le sheet s'arrête AU-DESSUS de la tabbar (bottom = hauteur tabbar)
au lieu de bottom:0 (ce qui mettait le bas du contenu derrière
la tabbar opaque).
2) Layout flexbox interne : header sticky en haut + zone scrollable
qui prend tout le reste avec `flex: 1; min-height: 0; overflow-y: auto`.
C'est la SEULE manière fiable d'avoir un vrai scroll qui descend
jusqu'en bas sur iOS Safari. */}
{title}
{/* Zone scrollable : flex:1 + min-height:0 sont CRUCIAUX pour iOS */}