Merged in feat/sw-1513-anchoring-on-enter-details (pull request #1379)
feat(SW-1513): scroll to new section on enter details page * feat(SW-1513): scroll to new section on enter details page Approved-by: Simon.Emanuelsson
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"use client"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { CheckIcon, ChevronDownIcon } from "@/components/Icons"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useStickyPosition from "@/hooks/useStickyPosition"
|
||||
|
||||
import styles from "./sectionAccordion.module.css"
|
||||
|
||||
@@ -30,6 +31,7 @@ export default function SectionAccordion({
|
||||
selectRoomStatus(state, roomIndex)
|
||||
)
|
||||
|
||||
const stickyPosition = useStickyPosition({})
|
||||
const setStep = useEnterDetailsStore((state) => state.actions.setStep)
|
||||
const { bedType, breakfast } = useEnterDetailsStore((state) =>
|
||||
selectRoom(state, roomIndex)
|
||||
@@ -67,8 +69,28 @@ export default function SectionAccordion({
|
||||
setIsComplete(isValid)
|
||||
}, [isValid, setIsComplete])
|
||||
|
||||
const accordionRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(roomStatus.currentStep === step && currentRoomIndex === roomIndex)
|
||||
const shouldBeOpen =
|
||||
roomStatus.currentStep === step && currentRoomIndex === roomIndex
|
||||
|
||||
setIsOpen(shouldBeOpen)
|
||||
|
||||
// Scroll to this section when it is opened, but wait for the accordion animations to
|
||||
// finish, else the height calculations will not be correct and the scroll position
|
||||
// will be off.
|
||||
if (shouldBeOpen) {
|
||||
setTimeout(() => {
|
||||
if (accordionRef.current) {
|
||||
window.scrollTo({
|
||||
top: accordionRef.current.offsetTop - stickyPosition.getTopOffset(),
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
}, 250)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentRoomIndex, roomIndex, roomStatus.currentStep, setIsOpen, step])
|
||||
|
||||
function onModify() {
|
||||
@@ -98,6 +120,7 @@ export default function SectionAccordion({
|
||||
className={styles.accordion}
|
||||
data-section-open={isOpen}
|
||||
data-step={step}
|
||||
ref={accordionRef}
|
||||
>
|
||||
<div className={styles.iconWrapper}>
|
||||
<div className={styles.circle} data-checked={isComplete}>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
|
||||
import { env } from "@/env/client"
|
||||
import useStickyPositionStore, {
|
||||
type StickyElement,
|
||||
type StickyElementNameEnum,
|
||||
} from "@/stores/sticky-position"
|
||||
|
||||
@@ -71,6 +72,36 @@ export default function useStickyPosition({
|
||||
}
|
||||
}, [ref, name, group, registerSticky, unregisterSticky, updateHeights])
|
||||
|
||||
/**
|
||||
* Get the top position of element at index `index`
|
||||
*
|
||||
* Calculates the total height of all sticky elements _before_ the element
|
||||
* at position `index`. If `index` is not provided all elements are included
|
||||
* in the calculation. Takes grouping into consideration (only counts one
|
||||
* element per group)
|
||||
*/
|
||||
const getTopOffset = useCallback(
|
||||
(index?: number) => {
|
||||
// Get the group name of the current sticky element.
|
||||
// This will be used to only count one element per group.
|
||||
const elementGroup = index ? stickyElements[index].group : undefined
|
||||
|
||||
return stickyElements
|
||||
.slice(0, index)
|
||||
.reduce<StickyElement[]>((acc, curr) => {
|
||||
if (
|
||||
(elementGroup && curr.group === elementGroup) ||
|
||||
acc.some((elem: StickyElement) => elem.group === curr.group)
|
||||
) {
|
||||
return acc
|
||||
}
|
||||
return [...acc, curr]
|
||||
}, [])
|
||||
.reduce((acc, el) => acc + el.height, baseTopOffset)
|
||||
},
|
||||
[baseTopOffset, stickyElements]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (ref) {
|
||||
// Find the index of the current sticky element in the array of stickyElements.
|
||||
@@ -78,25 +109,13 @@ export default function useStickyPosition({
|
||||
const index = stickyElements.findIndex((el) => el.ref === ref)
|
||||
|
||||
if (index !== -1 && ref.current) {
|
||||
// Get the group name of the current sticky element.
|
||||
// This will be used to filter out other elements in the same group.
|
||||
const currentGroup = stickyElements[index].group
|
||||
|
||||
// Calculate the top offset for the current element.
|
||||
// We sum the heights of all elements that appear before the current element
|
||||
// in the stickyElements array, but only if they belong to a different group.
|
||||
// This ensures that elements in the same group don't stack on top of each other.
|
||||
const topOffset = stickyElements
|
||||
.slice(0, index)
|
||||
.filter((el) => el.group !== currentGroup)
|
||||
.reduce((acc, el) => acc + el.height, baseTopOffset)
|
||||
|
||||
const topOffset = getTopOffset(index)
|
||||
// Apply the calculated top offset to the current element's style.
|
||||
// This positions the element at the correct location within the document.
|
||||
ref.current.style.top = `${topOffset}px`
|
||||
}
|
||||
}
|
||||
}, [baseTopOffset, stickyElements, ref])
|
||||
}, [baseTopOffset, stickyElements, ref, getTopOffset])
|
||||
|
||||
useEffect(() => {
|
||||
if (!resizeObserver) {
|
||||
@@ -128,5 +147,6 @@ export default function useStickyPosition({
|
||||
return {
|
||||
currentHeight: ref?.current?.offsetHeight || null,
|
||||
allElements: getAllElements(),
|
||||
getTopOffset,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user