Merged in feature/SW-1736-foating-booking-widget (pull request #1696)

Feature/SW-1736 floating booking widget

* feature: Add floating booking widget on start page SW-1736

* fix: Make sure we don't try to use IntersectionObserver on the server

* fix: make sure that we disconnect the intersectionobserver when dismounting

* fix: pass searchparams to floating bookingwidget


Approved-by: Michael Zetterberg
This commit is contained in:
Joakim Jäderberg
2025-04-04 06:52:37 +00:00
parent 7b1760ca17
commit 3c810d67a2
17 changed files with 243 additions and 21 deletions

View File

@@ -0,0 +1,27 @@
.floatingBookingWidget {
width: 100vw;
min-height: 84px;
z-index: 1000;
position: relative;
.floatingBackground {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(255, 255, 255, 0);
width: 100%;
transition: background 0.2s ease;
}
&[data-intersecting="true"] {
.floatingBackground {
background: transparent;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}
}
}

View File

@@ -0,0 +1,65 @@
"use client"
import { useEffect, useRef, useState } from "react"
import BookingWidgetClient from "../Client"
import styles from "./FloatingBookingWidget.module.css"
import type { BookingWidgetClientProps } from "@/types/components/bookingWidget"
type Props = Omit<BookingWidgetClientProps, "type">
export function FloatingBookingWidgetClient(props: Props) {
const containerRef = useRef<HTMLDivElement>(null)
const observerRef = useRef<IntersectionObserver | null>(null)
const [stickyTop, setStickyTop] = useState(false)
useEffect(() => {
observerRef.current = new IntersectionObserver(
([entry]) => {
const hasScrolledPastTop = entry.boundingClientRect.top < 0
setStickyTop(hasScrolledPastTop)
},
{ threshold: 0, rootMargin: "0px 0px -100% 0px" }
)
if (containerRef.current) {
observerRef.current?.observe(containerRef.current)
}
return () => {
observerRef.current?.disconnect()
}
}, [])
useEffect(() => {
/*
Re-observe the element on an interval to ensure the observer is up to date
This is a workaround for the fact that the observer doesn't always trigger
when the element is scrolled out of view if you do it too fast
*/
const interval = setInterval(() => {
if (!containerRef.current || !observerRef.current) {
return
}
observerRef.current.unobserve(containerRef.current)
observerRef.current.observe(containerRef.current)
}, 500)
return () => clearInterval(interval)
}, [])
return (
<div
className={styles.floatingBookingWidget}
data-intersecting={stickyTop}
ref={containerRef}
>
<div className={styles.floatingBackground}>
<BookingWidgetClient {...props} type={"compact"} />
</div>
</div>
)
}

View File

@@ -0,0 +1,23 @@
import { getPageSettingsBookingCode } from "@/lib/trpc/memoizedRequests"
import { FloatingBookingWidgetClient } from "./FloatingBookingWidgetClient"
import type { BookingWidgetProps } from "@/types/components/bookingWidget"
export async function FloatingBookingWidget({
bookingWidgetSearchParams,
}: Omit<BookingWidgetProps, "type">) {
console.log("DEBUG: FloatingBookingWidget", bookingWidgetSearchParams)
let pageSettingsBookingCodePromise: Promise<string> | null = null
if (!bookingWidgetSearchParams.bookingCode) {
pageSettingsBookingCodePromise = getPageSettingsBookingCode()
}
return (
<FloatingBookingWidgetClient
bookingWidgetSearchParams={bookingWidgetSearchParams}
pageSettingsBookingCodePromise={pageSettingsBookingCodePromise}
/>
)
}