From 46d155e3b007fd9ab212d60538b30e4db44ec269 Mon Sep 17 00:00:00 2001 From: share-a-byte Date: Mon, 2 Jun 2025 18:41:16 -0700 Subject: [PATCH] formatted foldable --- apps/info/src/components/Foldable.tsx | 724 ++++++++++++++++++++------ 1 file changed, 576 insertions(+), 148 deletions(-) diff --git a/apps/info/src/components/Foldable.tsx b/apps/info/src/components/Foldable.tsx index 85c70a23..cc1e0550 100644 --- a/apps/info/src/components/Foldable.tsx +++ b/apps/info/src/components/Foldable.tsx @@ -1,169 +1,597 @@ // credit to : https://www.frontend.fyi/tutorials/making-a-foldable-map-with-framer-motion -import React, { useState } from "react"; -import { Box, Flex, Text, Button, useColorModeValue } from "@chakra-ui/react"; -import { motion, AnimatePresence } from "framer-motion"; - -const PANEL_W = 250; -const PANEL_H = 480; -const PANELS = [-2, -1, 0, 1, 2]; - -const FoldableFAQ: React.FC = () => { - const bg = useColorModeValue("white", "gray.800"); - const altBg = useColorModeValue("gray.50", "gray.700"); - const borderColor = useColorModeValue("gray.200", "gray.600"); - const shadow = useColorModeValue("rgba(0,0,0,0.1)", "rgba(0,0,0,0.4)"); - const [open, setOpen] = useState(false); - - const deckVariants = { - closed: { - scale: 0.96, - x: "-50%", - transition: { type: "spring", stiffness: 80, damping: 20 } - }, - open: { - scale: 0.82, - x: "-50%", // STILL CENTER IT ONCE WE OPEN - transition: { - type: "spring", - stiffness: 80, - damping: 20, - delayChildren: 0.1, - staggerChildren: 0.18 - } - } - }; +import React, { useState, useEffect } from "react"; +import { + Box, + Flex, + Text, + Heading, + VStack, + ChakraProvider, + extendTheme, + useColorModeValue +} from "@chakra-ui/react"; +import { + motion, + useMotionValue, + useTransform, + useMotionValueEvent, + useSpring, + AnimatePresence +} from "framer-motion"; + +const theme = extendTheme({ + styles: { global: { body: { bg: "gray.50" } } } +}); + +const MotionBox = motion(Box); +const MotionFlex = motion(Flex); +const MotionHeading = motion(Heading); +const MotionText = motion(Text); + +interface FaqItem { + question: string; + answer: string; +} + +interface FoldableFAQProps { + title?: string; +} + +const faqItems: FaqItem[] = [ + { + question: "Lorem ipsum dolor sit amet?", + answer: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ac felis eget justo cursus finibus in vel nibh." + }, + { + question: "Nulla facilisi mauris vel?", + answer: + "Nulla facilisi. Mauris vel erat nec justo cursus auctor. Suspendisse potenti. Sed mattis neque ut ex volutpat." + }, + { + question: "Fusce sagittis magna vel?", + answer: + "Fusce sagittis magna vel nunc porttitor, nec hendrerit magna posuere. Sed porttitor eros vitae diam finibus." + }, + { + question: "Vestibulum ante ipsum primis?", + answer: + "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec lacinia sapien in elit." + }, + { + question: "Cras at justo sit amet?", + answer: + "Cras at justo sit amet erat molestie varius. Integer hendrerit dolor sed eleifend sodales. Phasellus magna elit." + } +]; + +const FoldableFAQ: React.FC = ({ title = "FAQ" }) => { + const maxDrag = 380; + const xDrag = useMotionValue(0); + const xSpring = useSpring(xDrag, { stiffness: 400, damping: 35 }); + const componentX = useTransform(xSpring, [0, maxDrag], [0, -maxDrag]); + const [isFolded, setIsFolded] = useState(true); + + useMotionValueEvent(xDrag, "change", (x) => { + if (x > maxDrag * 0.8 && isFolded) setIsFolded(false); + if (x < maxDrag * 0.2 && !isFolded) setIsFolded(true); + }); + + const [size, setSize] = useState({ width: 0, height: 0 }); + useEffect(() => { + const update = () => { + const width = Math.min(window.innerWidth * 0.92, 1200); + const height = width * 0.55; + setSize({ width, height }); + }; + update(); + window.addEventListener("resize", update); + return () => window.removeEventListener("resize", update); + }, []); + + const windmillEffect = [ + { rotateZ: -90, rotateY: -45, scale: 0.8, rotateX: 0, y: 0, skewY: 0 }, + { rotateZ: -45, rotateY: -22, scale: 0.9, rotateX: 0, y: 0, skewY: 0 }, + { rotateZ: 0, rotateY: 0, scale: 1, rotateX: 0, y: 0, skewY: 0 }, + { rotateZ: 45, rotateY: 22, scale: 0.9, rotateX: 0, y: 0, skewY: 0 }, + { rotateZ: 90, rotateY: 45, scale: 0.8, rotateX: 0, y: 0, skewY: 0 } + ]; + + const panelShift = [220, 110, 0, -110, -220]; + const globalRotateY = useTransform(xSpring, [0, maxDrag], [0, 3]); + const globalZ = useTransform(xSpring, [0, maxDrag], [0, 50]); + + const panel0X = useTransform(xSpring, [0, maxDrag], [panelShift[0], 0]); + const panel0RotateY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[0].rotateY, 0] + ); + const panel0RotateX = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[0].rotateX, 0] + ); + const panel0RotateZ = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[0].rotateZ, 0] + ); + const panel0Scale = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[0].scale, 1] + ); + const panel0Y = useTransform(xSpring, [0, maxDrag], [windmillEffect[0].y, 0]); + const panel0SkewY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[0].skewY, 0] + ); + const panel0Z = useTransform(xSpring, [0, maxDrag], [0, 0 * 15]); + const panel0Opacity = useTransform( + xSpring, + [0, maxDrag * 0.3, maxDrag], + [0.4, 0.8, 1] + ); + + const panel1X = useTransform(xSpring, [0, maxDrag], [panelShift[1], 0]); + const panel1RotateY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[1].rotateY, 0] + ); + const panel1RotateX = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[1].rotateX, 0] + ); + const panel1RotateZ = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[1].rotateZ, 0] + ); + const panel1Scale = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[1].scale, 1] + ); + const panel1Y = useTransform(xSpring, [0, maxDrag], [windmillEffect[1].y, 0]); + const panel1SkewY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[1].skewY, 0] + ); + const panel1Z = useTransform(xSpring, [0, maxDrag], [0, 1 * 15]); + const panel1Opacity = useTransform( + xSpring, + [0, maxDrag * 0.3, maxDrag], + [0.4, 0.8, 1] + ); - const panelVariants = (side: "left" | "right" | "center") => ({ - initial: { - rotateY: side === "left" ? -90 : side === "right" ? 90 : 0, - opacity: side === "center" ? 1 : 0 + const panel2X = useTransform(xSpring, [0, maxDrag], [panelShift[2], 0]); + const panel2RotateY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[2].rotateY, 0] + ); + const panel2RotateX = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[2].rotateX, 0] + ); + const panel2RotateZ = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[2].rotateZ, 0] + ); + const panel2Scale = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[2].scale, 1] + ); + const panel2Y = useTransform(xSpring, [0, maxDrag], [windmillEffect[2].y, 0]); + const panel2SkewY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[2].skewY, 0] + ); + const panel2Z = useTransform(xSpring, [0, maxDrag], [0, 2 * 15]); + const panel2Opacity = useTransform( + xSpring, + [0, maxDrag * 0.3, maxDrag], + [0.4, 0.8, 1] + ); + + const panel3X = useTransform(xSpring, [0, maxDrag], [panelShift[3], 0]); + const panel3RotateY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[3].rotateY, 0] + ); + const panel3RotateX = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[3].rotateX, 0] + ); + const panel3RotateZ = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[3].rotateZ, 0] + ); + const panel3Scale = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[3].scale, 1] + ); + const panel3Y = useTransform(xSpring, [0, maxDrag], [windmillEffect[3].y, 0]); + const panel3SkewY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[3].skewY, 0] + ); + const panel3Z = useTransform(xSpring, [0, maxDrag], [0, 3 * 15]); + const panel3Opacity = useTransform( + xSpring, + [0, maxDrag * 0.3, maxDrag], + [0.4, 0.8, 1] + ); + + const panel4X = useTransform(xSpring, [0, maxDrag], [panelShift[4], 0]); + const panel4RotateY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[4].rotateY, 0] + ); + const panel4RotateX = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[4].rotateX, 0] + ); + const panel4RotateZ = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[4].rotateZ, 0] + ); + const panel4Scale = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[4].scale, 1] + ); + const panel4Y = useTransform(xSpring, [0, maxDrag], [windmillEffect[4].y, 0]); + const panel4SkewY = useTransform( + xSpring, + [0, maxDrag], + [windmillEffect[4].skewY, 0] + ); + const panel4Z = useTransform(xSpring, [0, maxDrag], [0, 4 * 15]); + const panel4Opacity = useTransform( + xSpring, + [0, maxDrag * 0.3, maxDrag], + [0.4, 0.8, 1] + ); + + const panelTransforms = [ + { + x: panel0X, + rotateY: panel0RotateY, + rotateX: panel0RotateX, + rotateZ: panel0RotateZ, + scale: panel0Scale, + y: panel0Y, + skewY: panel0SkewY, + z: panel0Z, + opacity: panel0Opacity }, - open: { - rotateY: 0, - opacity: 1, - transition: { type: "spring", stiffness: 70, damping: 16, mass: 0.6 } + { + x: panel1X, + rotateY: panel1RotateY, + rotateX: panel1RotateX, + rotateZ: panel1RotateZ, + scale: panel1Scale, + y: panel1Y, + skewY: panel1SkewY, + z: panel1Z, + opacity: panel1Opacity }, - closed: { - rotateY: side === "left" ? -90 : side === "right" ? 90 : 0, - opacity: side === "center" ? 1 : 0, - transition: { duration: 0.4 } + { + x: panel2X, + rotateY: panel2RotateY, + rotateX: panel2RotateX, + rotateZ: panel2RotateZ, + scale: panel2Scale, + y: panel2Y, + skewY: panel2SkewY, + z: panel2Z, + opacity: panel2Opacity + }, + { + x: panel3X, + rotateY: panel3RotateY, + rotateX: panel3RotateX, + rotateZ: panel3RotateZ, + scale: panel3Scale, + y: panel3Y, + skewY: panel3SkewY, + z: panel3Z, + opacity: panel3Opacity + }, + { + x: panel4X, + rotateY: panel4RotateY, + rotateX: panel4RotateX, + rotateZ: panel4RotateZ, + scale: panel4Scale, + y: panel4Y, + skewY: panel4SkewY, + z: panel4Z, + opacity: panel4Opacity } - }); + ]; + + const bgColor = useColorModeValue("white", "gray.800"); + const cardBg = useColorModeValue("gray.50", "gray.700"); + const textColor = useColorModeValue("gray.700", "white"); + const accent = useColorModeValue("blue.500", "blue.300"); - const leftOf = (idx: number) => `${idx * PANEL_W}px`; return ( - + - - {PANELS.map((i) => { - const isCentre = i === 0; - const side: "left" | "right" | "center" = - i < 0 ? "left" : i > 0 ? "right" : "center"; - - return ( - - {(isCentre || open) && ( - (t > maxDrag * 0.45 ? maxDrag : 0) + }} + style={{ x: xDrag, width: size.width, height: size.height }} + cursor="grab" + _active={{ cursor: "grabbing" }} + position="relative" + borderRadius="lg" + boxShadow="2xl" + overflow="hidden" + bg={bgColor} + animate={{ + boxShadow: isFolded + ? "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)" + : "0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.05)" + }} + transition={{ duration: 0.3 }} + > + + {faqItems.map((faq, i) => { + const transforms = panelTransforms[i]; + + return ( + 0 ? "1px solid" : undefined} + borderColor="gray.100" + bg={bgColor} + boxShadow="xl" + overflow="hidden" + > + + + + {!isFolded && ( + + + {faq.question} + + + {faq.answer} + + + )} + + + + + ); + })} + + + + {isFolded && ( + + + {title} + + - {isCentre ? ( - <> - - FAQ - - - - - ) : ( - <> - - - - )} - - )} - - ); - })} - + Drag to open → + + + )} + + + - + ); };