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"
|
"use client"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import { CheckIcon, ChevronDownIcon } from "@/components/Icons"
|
import { CheckIcon, ChevronDownIcon } from "@/components/Icons"
|
||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import useStickyPosition from "@/hooks/useStickyPosition"
|
||||||
|
|
||||||
import styles from "./sectionAccordion.module.css"
|
import styles from "./sectionAccordion.module.css"
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ export default function SectionAccordion({
|
|||||||
selectRoomStatus(state, roomIndex)
|
selectRoomStatus(state, roomIndex)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const stickyPosition = useStickyPosition({})
|
||||||
const setStep = useEnterDetailsStore((state) => state.actions.setStep)
|
const setStep = useEnterDetailsStore((state) => state.actions.setStep)
|
||||||
const { bedType, breakfast } = useEnterDetailsStore((state) =>
|
const { bedType, breakfast } = useEnterDetailsStore((state) =>
|
||||||
selectRoom(state, roomIndex)
|
selectRoom(state, roomIndex)
|
||||||
@@ -67,8 +69,28 @@ export default function SectionAccordion({
|
|||||||
setIsComplete(isValid)
|
setIsComplete(isValid)
|
||||||
}, [isValid, setIsComplete])
|
}, [isValid, setIsComplete])
|
||||||
|
|
||||||
|
const accordionRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
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])
|
}, [currentRoomIndex, roomIndex, roomStatus.currentStep, setIsOpen, step])
|
||||||
|
|
||||||
function onModify() {
|
function onModify() {
|
||||||
@@ -98,6 +120,7 @@ export default function SectionAccordion({
|
|||||||
className={styles.accordion}
|
className={styles.accordion}
|
||||||
data-section-open={isOpen}
|
data-section-open={isOpen}
|
||||||
data-step={step}
|
data-step={step}
|
||||||
|
ref={accordionRef}
|
||||||
>
|
>
|
||||||
<div className={styles.iconWrapper}>
|
<div className={styles.iconWrapper}>
|
||||||
<div className={styles.circle} data-checked={isComplete}>
|
<div className={styles.circle} data-checked={isComplete}>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
|
||||||
import { env } from "@/env/client"
|
import { env } from "@/env/client"
|
||||||
import useStickyPositionStore, {
|
import useStickyPositionStore, {
|
||||||
|
type StickyElement,
|
||||||
type StickyElementNameEnum,
|
type StickyElementNameEnum,
|
||||||
} from "@/stores/sticky-position"
|
} from "@/stores/sticky-position"
|
||||||
|
|
||||||
@@ -71,6 +72,36 @@ export default function useStickyPosition({
|
|||||||
}
|
}
|
||||||
}, [ref, name, group, registerSticky, unregisterSticky, updateHeights])
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (ref) {
|
if (ref) {
|
||||||
// Find the index of the current sticky element in the array of stickyElements.
|
// 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)
|
const index = stickyElements.findIndex((el) => el.ref === ref)
|
||||||
|
|
||||||
if (index !== -1 && ref.current) {
|
if (index !== -1 && ref.current) {
|
||||||
// Get the group name of the current sticky element.
|
const topOffset = getTopOffset(index)
|
||||||
// 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)
|
|
||||||
|
|
||||||
// Apply the calculated top offset to the current element's style.
|
// Apply the calculated top offset to the current element's style.
|
||||||
// This positions the element at the correct location within the document.
|
// This positions the element at the correct location within the document.
|
||||||
ref.current.style.top = `${topOffset}px`
|
ref.current.style.top = `${topOffset}px`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [baseTopOffset, stickyElements, ref])
|
}, [baseTopOffset, stickyElements, ref, getTopOffset])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!resizeObserver) {
|
if (!resizeObserver) {
|
||||||
@@ -128,5 +147,6 @@ export default function useStickyPosition({
|
|||||||
return {
|
return {
|
||||||
currentHeight: ref?.current?.offsetHeight || null,
|
currentHeight: ref?.current?.offsetHeight || null,
|
||||||
allElements: getAllElements(),
|
allElements: getAllElements(),
|
||||||
|
getTopOffset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user