diff --git a/src/components/DraggableButton.tsx b/src/components/DraggableButton.tsx index 1ace335..a3e0b4c 100644 --- a/src/components/DraggableButton.tsx +++ b/src/components/DraggableButton.tsx @@ -1,8 +1,21 @@ import { css, cx } from "@emotion/css"; import React, { useEffect, useRef, useState } from "react"; +const isOutsideViewport = (el: HTMLElement) => { + const rect = el.getBoundingClientRect(); + const isOutside = + rect.right < 0 || + rect.left > window.innerWidth || + rect.bottom < 0 || + rect.top > window.innerHeight; + + return isOutside; +}; + export interface DraggableButtonProps - extends React.ButtonHTMLAttributes {} + extends React.ButtonHTMLAttributes { + onOutsideViewport?: () => void; +} interface Pos { x: number; @@ -23,7 +36,7 @@ const relativePos = (pos: Pos, container: DOMRect) => { export const DraggableButton = React.forwardRef< HTMLButtonElement, DraggableButtonProps ->(({ children, ...props }, ref) => { +>(({ children, onOutsideViewport, ...props }, ref) => { const [position, setPosition] = useState({ x: 0, y: 0 }); const [rotation, setRotation] = useState(0); const [isDragging, setIsDragging] = useState(false); @@ -35,6 +48,12 @@ export const DraggableButton = React.forwardRef< const lastPosition = useRef({ x: 0, y: 0 }); const animationFrame = useRef(); + const [isOutside, setIsOutside] = useState(false); + + useEffect(() => { + if (isOutside) onOutsideViewport?.(); + }, [isOutside !== true]); + // Capture initial rotation on mount useEffect(() => { const el = myRef.current; @@ -70,6 +89,9 @@ export const DraggableButton = React.forwardRef< el.style.top = `${position.y}px`; el.style.rotate = `${rotation}deg`; + if (!isDragging && myRef.current && isOutsideViewport(myRef.current!)) + setIsOutside(true); + const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") setIsDragging(false); }; @@ -171,9 +193,10 @@ export const DraggableButton = React.forwardRef< if ( Math.abs(lastVelocity.current.x) > 0.1 || Math.abs(lastVelocity.current.y) > 0.1 - ) { + ) animationFrame.current = requestAnimationFrame(applyMomentum); - } + + if (isOutsideViewport(myRef.current!)) setIsOutside(true); }; const applyMomentum = () => { diff --git a/src/pages/main/Contact.tsx b/src/pages/main/Contact.tsx index 80cd34b..2ac8635 100644 --- a/src/pages/main/Contact.tsx +++ b/src/pages/main/Contact.tsx @@ -6,6 +6,7 @@ import { setupCursorTracking } from "../../util"; import { ReactComponent as Logo } from "../../assets/logo.svg"; import { DraggableButton } from "../../components/DraggableButton"; import { Flippable } from "../../components/Flippable"; +import { AnimateEntry } from "../../components/AnimateEntry"; const A = css` text-decoration: none; @@ -52,6 +53,7 @@ const cardRotations = Array.from({ length: 5 }, (_, i) => { const Contact: React.FC = () => { const [contact, setContact] = useState(CONTACT); + const [visible, setVisible] = useState(cardRotations.length); useEffect(() => { const deob = () => { @@ -78,12 +80,14 @@ const Contact: React.FC = () => { document.addEventListener("scroll", deob, { once: true }); document.addEventListener("click", deob, { once: true }); document.addEventListener("touchstart", deob, { once: true }); + document.addEventListener("keydown", deob, { once: true }); return () => { document.removeEventListener("mousemove", deob); document.removeEventListener("scroll", deob); document.removeEventListener("click", deob); document.removeEventListener("touchstart", deob); + document.removeEventListener("keydown", deob); }; }, []); @@ -96,9 +100,18 @@ const Contact: React.FC = () => { position: relative; `}>

MKRhere

+ {visible < 1 && ( + +

Great, You've distributed all the cards!

+

What now?

+
+ Start over? +
+ )} {cardRotations.map((rot, i) => ( setVisible(v => v - 1)} className={css` width: 22rem; height: auto;