Spaces:
Sleeping
Sleeping
File size: 4,058 Bytes
0e6ed99 4f5bfc3 3937b35 f3a1979 3937b35 0e6ed99 4f5bfc3 0e6ed99 09ffe59 4f5bfc3 0e6ed99 4f5bfc3 09ffe59 4f5bfc3 09ffe59 4f5bfc3 09ffe59 4f5bfc3 09ffe59 4f5bfc3 09ffe59 4f5bfc3 09ffe59 3937b35 09ffe59 0e6ed99 4f5bfc3 09ffe59 4f5bfc3 09ffe59 4f5bfc3 09ffe59 0e6ed99 4f5bfc3 | 1 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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | import React, { useState, useRef } from 'react';
import { Box, Fab, Slide, useMediaQuery, useTheme } from '@mui/material';
import ChatIcon from '@mui/icons-material/Chat';
import CloseIcon from '@mui/icons-material/Close';
import ChatInterface from './ChatInterface';
type FloatingChatProps = {
onClose: () => void;
};
const FloatingChat: React.FC<FloatingChatProps> = ({ onClose }) => {
const [isOpen, setIsOpen] = useState(true);
const handleClose = () => {
setIsOpen(false);
onClose();
};
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const chatRef = useRef<HTMLDivElement>(null);
const toggleChat = () => setIsOpen(prev => !prev);
const ANIM_DURATION = 300;
const FAB_SIZE = 60;
const FAB_GAP = isMobile ? 16 : 24;
return (
<Box
sx={{
position: 'fixed',
bottom: 0,
right: 0,
zIndex: 1400,
pointerEvents: 'none', // children control pointerEvents individually
}}
>
{/* Slide wrapper: NOTE -> overflow must be visible so internal header isn't clipped */}
<Slide
direction="up"
in={isOpen}
mountOnEnter
unmountOnExit
timeout={ANIM_DURATION}
>
<Box
ref={chatRef}
// sx={{
// position: 'fixed',
// right: isMobile ? FAB_GAP : FAB_GAP,
// bottom: FAB_GAP,
// width: isMobile ? '0vw' : 1410,
// height: isMobile ? '0vh' : 1410,
// maxHeight: 'calc(100vh - 48px)',
// borderRadius: 2,
// // Crucial: allow overflow visible so header (and shadows) aren't clipped during transform
// overflow: 'visible',
// display: 'flex',
// flexDirection: 'column',
// boxShadow: 12,
// backgroundColor: theme.palette.background.paper,
// zIndex: 1410,
// pointerEvents: 'auto',
// // Help browser optimize the transform during animation
// willChange: 'transform, opacity',
// transformOrigin: 'bottom right',
// }}
sx={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
borderRadius: 0, // remove rounded corners since it's full screen
overflow: 'hidden', // keeps ChatInterface properly contained
display: 'flex',
flexDirection: 'column',
boxShadow: 'none', // optional, remove shadow since it's full viewport
backgroundColor: theme.palette.background.paper,
zIndex: 1410,
pointerEvents: 'auto',
willChange: 'transform, opacity',
transformOrigin: 'bottom right',
}}
>
<ChatInterface onClose={handleClose} />
</Box>
</Slide>
{/* FAB wrapper (keeps FAB mounted during transition) */}
<Box
sx={{
position: 'fixed',
right: isMobile ? FAB_GAP : FAB_GAP,
bottom: FAB_GAP,
zIndex: 1405,
pointerEvents: 'none',
}}
>
<Fab
color="primary"
aria-label="chat"
onClick={toggleChat}
sx={{
width: FAB_SIZE,
height: FAB_SIZE,
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
'&:hover': { backgroundColor: theme.palette.primary.dark },
transition: `transform ${ANIM_DURATION}ms ease, opacity ${ANIM_DURATION}ms ease`,
pointerEvents: isOpen ? 'none' : 'auto', // make non-clickable while chat is open
opacity: isOpen ? 0 : 1,
transform: isOpen ? 'translateY(12px) scale(0.98)' : 'translateY(0) scale(1)',
boxShadow: 6,
}}
>
{isOpen ? <CloseIcon /> : <ChatIcon fontSize="large" />}
</Fab>
</Box>
</Box>
);
};
export default FloatingChat;
|