diff --git a/src/app/(playground)/playground/bottom-sheet/_components/bottom-sheet/index.tsx b/src/app/(playground)/playground/bottom-sheet/_components/bottom-sheet/index.tsx index 7a6bf98..5cf07da 100644 --- a/src/app/(playground)/playground/bottom-sheet/_components/bottom-sheet/index.tsx +++ b/src/app/(playground)/playground/bottom-sheet/_components/bottom-sheet/index.tsx @@ -1,6 +1,7 @@ "use client"; import { useForm } from "@tanstack/react-form"; +import { useCallback, useEffect, useRef, useState } from "react"; import ImageRadio from "~/app/(playground)/playground/bottom-sheet/_components/image-radio"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; @@ -8,7 +9,6 @@ import { Label } from "~/components/ui/label"; import { Sheet, - SheetClose, SheetContent, SheetHeader, SheetTitle, @@ -21,6 +21,7 @@ import AttendanceTrueDefault from "~/assets/attendance/attendance-true-deafult.s import { createInvitationResponses } from "~/lib/db/schema/invitation_response.query"; export default function BottomSheet() { + const [open, setOpen] = useState(false); const form = useForm({ defaultValues: { name: "", @@ -39,22 +40,79 @@ export default function BottomSheet() { border: 0, }, }); + setOpen(false); }, }); + const sheetContentRef = useRef(null); + const startY = useRef(null); + const scrollStartY = useRef(0); + const [isDragging, setIsDragging] = useState(false); + + useEffect(() => { + const preventDefault = (e: Event) => { + if (isDragging) { + e.preventDefault(); + } + }; + + document.body.addEventListener("touchmove", preventDefault, { + passive: false, + }); + + return () => { + document.body.removeEventListener("touchmove", preventDefault); + }; + }, [isDragging]); + + const handleTouchStart = useCallback((e: React.TouchEvent) => { + startY.current = e.touches[0].clientY; + scrollStartY.current = window.scrollY; + }, []); + + const handleTouchMove = useCallback((e: React.TouchEvent) => { + if (startY.current === null || !sheetContentRef.current) return; + + const currentY = e.touches[0].clientY; + const deltaY = currentY - startY.current; + + if (deltaY > 0 && window.scrollY <= scrollStartY.current) { + setIsDragging(true); + sheetContentRef.current!.style.transform = `translateY(${deltaY}px)`; + sheetContentRef.current!.style.transition = "none"; + } else { + setIsDragging(false); + } + }, []); + + const handleTouchEnd = useCallback( + (e: React.TouchEvent) => { + if (startY.current === null || !sheetContentRef.current) return; + + const endY = e.changedTouches[0].clientY; + const deltaY = endY - startY.current; + + sheetContentRef.current!.style.transform = ""; + sheetContentRef.current!.style.transition = ""; + + if (deltaY > 100 && isDragging) { + setOpen(false); + } + + setIsDragging(false); + startY.current = null; + }, + [isDragging], + ); + return ( - + -
+
@@ -64,6 +122,10 @@ export default function BottomSheet() { side="bottom" className="fixed w-full flex-col space-y-10 rounded-t-[32px] border-none bg-[#1A1A1A] pb-0 pt-10 text-white" aria-describedby={"세션참여 조사 Form"} + ref={sheetContentRef} + onTouchStart={handleTouchStart} + onTouchMove={handleTouchMove} + onTouchEnd={handleTouchEnd} >
@@ -91,15 +153,13 @@ export default function BottomSheet() { {(field) => ( - <> - - field.handleChange(event.target.value) - } - value={field.state.value} - className="col-span-3 h-[50px] rounded-xl border-[#3C3C3C] bg-[#222222] focus:border-[#5E8AFF]" - /> - + + field.handleChange(event.target.value) + } + value={field.state.value} + className="col-span-3 h-[50px] rounded-xl border-[#3C3C3C] bg-[#222222] text-base focus:border-[#5E8AFF]" + /> )}
@@ -108,55 +168,51 @@ export default function BottomSheet() {
{(field) => ( - <> - { - field.handleChange( - event.target.value as "true" | "false" | "", - ); - }} - > - - - - + { + field.handleChange( + event.target.value as "true" | "false" | "", + ); + }} + > + + + )}
- -
- state}> - {({ isValid, isSubmitting, errors, values }) => { - const isFormComplete = - isValid && values.name && values.attendance; - return ( - - ); - }} - -
-
+
+ state}> + {({ isValid, isSubmitting, values }) => { + const isFormComplete = + isValid && values.name && values.attendance; + return ( + + ); + }} + +