Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve slide animation for bottom sheet modal #4202

Merged
merged 9 commits into from
Oct 11, 2024
Merged
16 changes: 11 additions & 5 deletions src/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Navbar from '../components/Futa/Navbar/Navbar';
import Footer from '../components/Futa/Footer/Footer';
import { useModal } from '../components/Global/Modal/useModal';
import CSSModal from '../pages/common/CSSDebug/CSSModal';
import { useBottomSheet } from '../contexts/BottomSheetContext';

/** ***** React Function *******/
export default function App() {
Expand All @@ -52,6 +53,9 @@ export default function App() {
sidebar: { toggle: toggleSidebar },
} = useContext(SidebarContext);

const { isBottomSheetOpen } =
useBottomSheet();

const containerStyle = currentLocation.includes('trade')
? 'content-container-trade'
: 'content-container';
Expand Down Expand Up @@ -137,6 +141,12 @@ export default function App() {
};
}, [isCSSModalOpen]);

const footerDisplay = platformName === 'futa' ? (
<Footer data-theme={skin.active} />
) : (
ambientFooter
)

return (
<>
{location.pathname == '/' && platformName !== 'futa' && (
Expand Down Expand Up @@ -168,11 +178,7 @@ export default function App() {
)}
<RouteRenderer platformName={platformName} />
</FlexContainer>
{platformName === 'futa' ? (
<Footer data-theme={skin.active} />
) : (
ambientFooter
)}
{!isBottomSheetOpen && footerDisplay}
<GlobalPopup data-theme={skin.active} />
<SnackbarComponent />
{isWalletModalOpen && <GateWalletModal />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const ExchangeBalanceDropdown = () => {
const [fullLayoutActive, setFullLayoutActive] = useState<boolean>(false);
const [tokenModalOpen, setTokenModalOpen] = useState(false);
const escapePressed = useKeyPress('Escape');


useEffect(() => {
if (fullLayoutActive && !tokenModalOpen && escapePressed) {
Expand Down
86 changes: 86 additions & 0 deletions src/components/Global/BottomSheet/BottomSheet.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* Overlay that covers the whole screen with a blur effect */
.modal_overlay {

position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4); /* Semi-transparent overlay */
backdrop-filter: blur(3px); /* Blur effect */
-webkit-backdrop-filter: blur(3px); /* Safari support */
z-index: 999; /* Make sure it sits behind the modal */


}

/* Bottom Sheet (Modal) */
.bottom_sheet {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding-bottom: 56px;
background-color: var(--background-color);
border-top-left-radius: 16px;
border-top-right-radius: 16px;
box-shadow: 0px -2px 8px rgba(0, 0, 0, 0.15);
z-index: 1000; /* Sits above the overlay */
overflow: hidden;
width: 100%;
max-width: 100%;

background: var(--dark2);
border: 1px solid var(--dark3);
transition: height 0.3s ease, backdrop-filter 0.3s ease;


}

.sheet_handle {
display: flex;
justify-content: center;
padding: 10px;
cursor: pointer;
}

.drag_handle {
width: 40px;
height: 5px;
background-color: var(--accent1);
border-radius: 2.5px;


}

.sheet_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
border-bottom: 1px solid var(--border-color);
}

.sheet_title {
font-size: 18px;
font-weight: 300;
margin: 0;
}

.close_button {
cursor: pointer;
color: var(--text-color);
}

.sheet_content {
padding: 16px;
max-height: 60vh;
overflow-y: auto;
pointer-events: all;
}

.sheet_footer {
padding: 16px;
border-top: 1px solid var(--border-color);
}

133 changes: 133 additions & 0 deletions src/components/Global/BottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { motion, useAnimation, PanInfo } from 'framer-motion';
import { RiCloseFill } from 'react-icons/ri';
import styles from './BottomSheet.module.css';
import useMediaQuery from '../../../utils/hooks/useMediaQuery';
import useOnClickOutside from '../../../utils/hooks/useOnClickOutside';
import { useBottomSheet } from '../../../contexts/BottomSheetContext';

interface BottomSheetProps {
title?: string;
footer?: React.ReactNode;
children: React.ReactNode;
}

const BottomSheet: React.FC<BottomSheetProps> = ({
title = '',
footer,
children,
}) => {
const { isBottomSheetOpen, closeBottomSheet } = useBottomSheet();

const mainRef = useRef<HTMLDivElement>(null);

const clickOutsideLevelHandler = () => {
closeBottomSheet();
};
useOnClickOutside(mainRef, clickOutsideLevelHandler);

const controls = useAnimation();
const isMobile = useMediaQuery('(max-width: 500px)');
// eslint-disable-next-line
const [isDragging, setIsDragging] = useState(false);

const variants = {
hidden: { y: window.innerHeight, opacity: 0 }, // Replace '100%' with viewport height
visible: { y: 0, opacity: 1 },
};

const handleDragEnd = (
_: MouseEvent | TouchEvent | PointerEvent,
info: PanInfo,
) => {
setIsDragging(false);
if (info.offset.y > 100) {
closeBottomSheet();
} else {
controls.start({ y: 0 });
}
};

useEffect(() => {
if (isBottomSheetOpen) {
controls.start('visible');
} else {
controls.start('hidden');
}
}, [isBottomSheetOpen, controls]);

const escFunction = useCallback(
(event: KeyboardEvent) => {
if (event.key === 'Escape' && isBottomSheetOpen) {
closeBottomSheet();
}
},
[isBottomSheetOpen, closeBottomSheet],
);

useEffect(() => {
document.addEventListener('keydown', escFunction, false);
return () => {
document.removeEventListener('keydown', escFunction, false);
};
}, [escFunction]);

// Handle clicks on the overlay to close the modal
const handleOverlayClick = (e: React.MouseEvent) => {
e.preventDefault();
closeBottomSheet(); // Close sheet when clicking outside the modal
};

// Prevent clicks inside the modal from closing it
const handleModalClick = (e: React.MouseEvent) => {
e.stopPropagation(); // Stop event from bubbling up to the overlay
};

return (
<>
{isBottomSheetOpen && (
<div
className={styles.modal_overlay}
onClick={handleOverlayClick} // Close modal only on clicking overlay
/>
)}
<motion.div
className={styles.bottom_sheet}
initial="hidden"
animate={controls}
exit="hidden"
variants={variants}
transition={{
duration: 0.5,
type: 'spring',
damping: 25,
stiffness: 200,
}}
drag={isMobile ? 'y' : false}
dragConstraints={{ top: 0 }}
dragElastic={0.2}
onDragStart={() => setIsDragging(true)}
onDragEnd={handleDragEnd}
onClick={handleModalClick} // Ensure modal clicks don't trigger overlay close
>
<div className={styles.sheet_handle}>
<div className={styles.drag_handle} />
</div>
<header className={styles.sheet_header}>
<h2 className={styles.sheet_title}>{title}</h2>
<RiCloseFill
className={styles.close_button}
size={24}
onClick={closeBottomSheet}
/>
</header>
<section className={styles.sheet_content}>{children}</section>
{footer && (
<footer className={styles.sheet_footer}>{footer}</footer>
)}
</motion.div>
</>
);
};

export default BottomSheet;
4 changes: 1 addition & 3 deletions src/components/Global/DropdownMenu2/DropdownMenu2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { AppStateContext } from '../../../contexts/AppStateContext';
import useKeyPress from '../../../App/hooks/useKeyPress';
import { brand } from '../../../ambient-utils/constants';
import Modal from '../Modal/Modal';
import ModalHeader from '../ModalHeader/ModalHeader';
import styles from './DropdownMenu2.module.css'
import { motion } from 'framer-motion';
// Interface for React functional components
Expand Down Expand Up @@ -89,7 +88,6 @@ export default function DropdownMenu2(props: propsIF) {

const modalVersion = (
<Modal usingCustomHeader onClose={() => setIsMenuOpen(false)}>
<ModalHeader title={'Select Network'} onClose={() => setIsMenuOpen(false)} />
{dropdownMenuContent}
</Modal>
)
Expand Down Expand Up @@ -158,4 +156,4 @@ export default function DropdownMenu2(props: propsIF) {
{isMenuOpen && (showMobileVersion ? modalVersion : dropdownMenuContent)}
</div>
);
}
}
Loading
Loading