diff --git a/packages/features/bookings/Booker/components/EventMeta.tsx b/packages/features/bookings/Booker/components/EventMeta.tsx index 9629bf2d6e..b3d7445b74 100644 --- a/packages/features/bookings/Booker/components/EventMeta.tsx +++ b/packages/features/bookings/Booker/components/EventMeta.tsx @@ -20,6 +20,7 @@ import { EventTypeAutoTranslatedField } from "@calcom/prisma/enums"; import i18nConfigration from "../../../../../i18n.json"; import { fadeInUp } from "../config"; import { FromToTime } from "../utils/dates"; +import { ScrollableWithGradients } from "./ScrollableWithGradients"; import { useBookerTime } from "./hooks/useBookerTime"; const WebTimezoneSelect = dynamic( @@ -174,15 +175,16 @@ export const EventMeta = ({ {translatedTitle ?? event?.title} {(event.description || translatedDescription) && ( - -
+ + +
+ )}
diff --git a/packages/features/bookings/Booker/components/ScrollableWithGradients.tsx b/packages/features/bookings/Booker/components/ScrollableWithGradients.tsx new file mode 100644 index 0000000000..0e2133cb23 --- /dev/null +++ b/packages/features/bookings/Booker/components/ScrollableWithGradients.tsx @@ -0,0 +1,75 @@ +import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; + +interface ScrollGradients { + top: boolean; + bottom: boolean; +} + +interface ScrollableWithGradientsProps { + children: ReactNode; + className?: string; + ariaLabel?: string; +} + +const SCROLL_THRESHOLD = 5; // Pixels before showing gradient + +export function ScrollableWithGradients({ children, className, ariaLabel }: ScrollableWithGradientsProps) { + const [gradients, setGradients] = useState({ top: false, bottom: false }); + const scrollRef = useRef(null); + + const updateGradients = useCallback(() => { + const node = scrollRef.current; + if (!node) return; + + const { scrollTop, scrollHeight, clientHeight } = node; + const top = scrollTop > SCROLL_THRESHOLD; + const bottom = scrollTop + clientHeight < scrollHeight - SCROLL_THRESHOLD; + + setGradients((prev) => (prev.top === top && prev.bottom === bottom ? prev : { top, bottom })); + }, []); + + const setRef = useCallback( + (node: HTMLDivElement | null) => { + scrollRef.current = node; + updateGradients(); + }, + [updateGradients] + ); + + // Handle resize events (window resize, font load, etc.) + useEffect(() => { + const node = scrollRef.current; + if (!node) return; + + const resizeObserver = new ResizeObserver(updateGradients); + resizeObserver.observe(node); + + return () => resizeObserver.disconnect(); + }, [updateGradients]); + + return ( +
+
+ {children} +
+ {gradients.top && ( +