Fix AIChat fullscreen portal

Made AIChat fullscreen render via a portal to document.body to avoid layout clipping, added createPortal import, and enforced body overflow hidden while fullscreen. Adjusted classNames to ensure fullscreen container covers viewport and button remains accessible.

X-Lovable-Edit-ID: edt-2d864842-2771-4813-9005-44ed92dda583
This commit is contained in:
gpt-engineer-app[bot]
2026-01-22 23:43:44 +00:00
+25 -5
View File
@@ -1,4 +1,5 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Send, Bot, User, Loader2, Trash2, AlertTriangle, Maximize2, Minimize2, ChevronDown, Settings } from 'lucide-react'; import { Send, Bot, User, Loader2, Trash2, AlertTriangle, Maximize2, Minimize2, ChevronDown, Settings } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@@ -108,6 +109,18 @@ const AIChat = () => {
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768; const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;
const { enterFullscreen, exitFullscreen } = useBrowserFullscreen(); const { enterFullscreen, exitFullscreen } = useBrowserFullscreen();
// Prevent page scroll while in fullscreen mode (portal overlays the viewport)
useEffect(() => {
if (typeof document === 'undefined') return;
if (!isFullscreen) return;
const prevOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = prevOverflow;
};
}, [isFullscreen]);
// Exit fullscreen on Escape key // Exit fullscreen on Escape key
useEffect(() => { useEffect(() => {
const handleEscape = (e: KeyboardEvent) => { const handleEscape = (e: KeyboardEvent) => {
@@ -382,16 +395,16 @@ const AIChat = () => {
sendMessage(); sendMessage();
} }
}; };
return ( const chatUi = (
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className={`flex flex-col ${ className={
isFullscreen isFullscreen
? 'fixed inset-0 z-50 bg-background p-2 sm:p-3 md:p-8 overflow-hidden w-screen h-screen' ? 'flex flex-col fixed inset-0 z-[100] bg-background p-2 sm:p-3 md:p-8 overflow-hidden w-screen h-screen'
: 'h-full' : 'flex flex-col h-full'
}`} }
> >
<div className="flex items-center justify-between gap-2 mb-3 flex-shrink-0"> <div className="flex items-center justify-between gap-2 mb-3 flex-shrink-0">
<GlitchText <GlitchText
@@ -698,6 +711,13 @@ const AIChat = () => {
</Dialog> </Dialog>
</motion.div> </motion.div>
); );
// IMPORTANT: MainLayout uses framer-motion transforms which create a containing block
// for `position: fixed` descendants, causing "fullscreen" to be clipped.
// Portaling to body guarantees true viewport fullscreen.
return isFullscreen && typeof document !== 'undefined'
? createPortal(chatUi, document.body)
: chatUi;
}; };
export default AIChat; export default AIChat;