diff --git a/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx b/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx index 4e7b48c25..29032d3c5 100644 --- a/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx +++ b/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx @@ -1,5 +1,6 @@ "use client" -import { useEffect, useState } from "react" +import { animate, motion, scroll, useAnimate } from "framer-motion" +import { useEffect, useRef, useState } from "react" import { useIntl } from "react-intl" import { useEnterDetailsStore } from "@/stores/enter-details" @@ -33,6 +34,10 @@ export default function SectionAccordion({ const noBreakfastTitle = intl.formatMessage({ id: "No breakfast" }) const breakfastTitle = intl.formatMessage({ id: "Breakfast buffet" }) + + const accordionRef = useRef(null) + const [scope, animate] = useAnimate() + useEffect(() => { if (step === StepEnum.selectBed && bedType) { setTitle(bedType.description) @@ -55,6 +60,49 @@ export default function SectionAccordion({ setIsOpen(currentStep === step) }, [currentStep, setIsOpen, step]) + useEffect(() => { + if (!accordionRef.current) return + + const animateAccordion = async () => { + // Measure the initial position of the accordion + const initialOffset = accordionRef.current?.offsetTop || 0 + + // Start the accordion expansion animation + const accordionAnimation = animate( + scope.current, + { + gridTemplateRows: isOpen + ? "var(--header-height) 1fr" + : "var(--header-height) 0fr", + }, + { duration: 0.3, ease: "easeInOut" } + ) + + if (isOpen) { + // Start scrolling immediately + const scrollAnimation = animate( + window.scrollY, + initialOffset - 100, // 100px offset from top + { + duration: 0.3, + ease: "easeInOut", + onUpdate: (latest) => { + window.scrollTo(0, latest) + }, + } + ) + + // Wait for both animations to complete + await Promise.all([accordionAnimation, scrollAnimation]) + } else { + // If closing, just wait for the accordion animation + await accordionAnimation + } + } + + animateAccordion() + }, [isOpen, scope, animate]) + function onModify() { navigate(step) } @@ -62,7 +110,13 @@ export default function SectionAccordion({ const textColor = isComplete || isOpen ? "uiTextHighContrast" : "baseTextDisabled" return ( -
+
{isComplete ? ( @@ -70,7 +124,7 @@ export default function SectionAccordion({ ) : null}
-
+
- + ) } diff --git a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css index 57b00dafa..eadd6255b 100644 --- a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css +++ b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css @@ -5,7 +5,7 @@ gap: var(--Spacing-x3); width: 100%; padding-top: var(--Spacing-x3); - transition: 0.4s ease-out; + /* transition: 0.4s ease-out; */ display: grid; grid-template-areas: "circle header" "content content"; @@ -13,6 +13,8 @@ grid-template-rows: var(--header-height) 0fr; column-gap: var(--Spacing-x-one-and-half); + + transform-origin: top; } .accordion:last-child { @@ -79,9 +81,9 @@ background-color: var(--Base-Surface-Subtle-Hover); } -.accordion[data-open="true"] { +/* .accordion[data-open="true"] { grid-template-rows: var(--header-height) 1fr; -} +} */ .contentWrapper { padding-bottom: var(--Spacing-x3); @@ -91,6 +93,7 @@ overflow: hidden; grid-area: content; border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); + transform-origin: top; } @media screen and (min-width: 768px) {