From 8b56fa84e79dfa57fa04e3e89b8ec8ac1b150ba0 Mon Sep 17 00:00:00 2001 From: Emma Zettervall Date: Tue, 20 Jan 2026 08:10:42 +0000 Subject: [PATCH] Merged in feat/LOY-522-move-focus-to-newly-loaded-item (pull request #3452) feat(LOY-522): Move focus to the newly loaded stay in sidepeek for upcoming and previous stay * feat(LOY-522): Moved focus to the newly loaded stay in sidepeek for upcoming and previous stay Approved-by: Anton Gunnarsson --- .../Stays/Previous/SidePeek/index.tsx | 30 +++++++++++++++---- .../DynamicContent/Stays/StayCard/index.tsx | 8 ++--- .../Stays/Upcoming/SidePeek/index.tsx | 29 ++++++++++++++---- .../components/myPages/stays/stayCard.ts | 1 + .../components/SidePeek/SelfControlled.tsx | 5 ++-- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SidePeek/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SidePeek/index.tsx index f5875c236..a7aa81cbf 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SidePeek/index.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SidePeek/index.tsx @@ -1,5 +1,6 @@ "use client" +import { useCallback, useRef } from "react" import { useIntl } from "react-intl" import { useSidePeekScrollToTop } from "@scandic-hotels/common/hooks/useSidePeekScrollToTop" @@ -31,6 +32,8 @@ export function PreviousStaysSidePeek({ const intl = useIntl() const lang = useLang() + const shouldFocusNextItem = useRef(false) + const { scrollContainerRef, showBackToTop, scrollToTop } = useSidePeekScrollToTop() @@ -50,6 +53,7 @@ export function PreviousStaysSidePeek({ function loadMoreData() { if (hasNextPage) { + shouldFocusNextItem.current = true fetchNextPage() } } @@ -60,6 +64,15 @@ export function PreviousStaysSidePeek({ const staysByYear = stays ? groupStaysByYear(stays) : [] + const lastPage = data?.pages?.[data.pages.length - 1] + const firstNewTransaction = lastPage?.data?.[0] + + const focusRefCallback = useCallback((node: HTMLAnchorElement | null) => { + if (!node || !shouldFocusNextItem.current) return + node.focus() + shouldFocusNextItem.current = false + }, []) + return (
{isLoading ? ( @@ -84,12 +98,16 @@ export function PreviousStaysSidePeek({
- {stays.map((stay) => ( - - ))} + {stays.map((stay) => { + const isFirstNewItem = stay === firstNewTransaction + return ( + + ) + })}
))} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/StayCard/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/StayCard/index.tsx index 85d182bbf..6cafa5181 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Stays/StayCard/index.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/StayCard/index.tsx @@ -1,6 +1,6 @@ "use client" -import Link from "next/link" +import { Link } from "react-aria-components" import { useIntl } from "react-intl" import { dt } from "@scandic-hotels/common/dt" @@ -16,7 +16,7 @@ import styles from "./stayCard.module.css" import type { StayCardProps } from "@/types/components/myPages/stays/stayCard" -export function StayCard({ stay }: StayCardProps) { +export function StayCard({ stay, ref }: StayCardProps) { const { bookingUrl, isWebAppOrigin: shouldLinkToMyStay } = stay.attributes if (!shouldLinkToMyStay) { @@ -24,13 +24,13 @@ export function StayCard({ stay }: StayCardProps) { } return ( - + ) } -function CardContent({ stay }: StayCardProps) { +function CardContent({ stay }: { stay: StayCardProps["stay"] }) { const lang = useLang() const intl = useIntl() diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Upcoming/SidePeek/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Upcoming/SidePeek/index.tsx index dd8aafcc2..f6f6ed56b 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Upcoming/SidePeek/index.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Upcoming/SidePeek/index.tsx @@ -1,5 +1,6 @@ "use client" +import { useCallback, useRef } from "react" import { useIntl } from "react-intl" import { useSidePeekScrollToTop } from "@scandic-hotels/common/hooks/useSidePeekScrollToTop" @@ -30,6 +31,7 @@ export function UpcomingStaysSidePeek({ }: UpcomingStaysSidePeekProps) { const intl = useIntl() const lang = useLang() + const shouldFocusNextItem = useRef(false) const { scrollContainerRef, showBackToTop, scrollToTop } = useSidePeekScrollToTop() @@ -51,6 +53,7 @@ export function UpcomingStaysSidePeek({ function loadMoreData() { if (hasNextPage) { + shouldFocusNextItem.current = true fetchNextPage() } } @@ -61,6 +64,15 @@ export function UpcomingStaysSidePeek({ const staysByYear = stays ? groupStaysByYear(stays, "asc") : [] + const lastPage = data?.pages?.[data.pages.length - 1] + const firstNewTransaction = lastPage?.data?.[0] + + const focusRefCallback = useCallback((node: HTMLAnchorElement | null) => { + if (!node || !shouldFocusNextItem.current) return + node.focus() + shouldFocusNextItem.current = false + }, []) + return (
{isLoading ? ( @@ -85,12 +98,16 @@ export function UpcomingStaysSidePeek({
- {stays.map((stay) => ( - - ))} + {stays.map((stay) => { + const isFirstNewItem = stay === firstNewTransaction + return ( + + ) + })}
))} diff --git a/apps/scandic-web/types/components/myPages/stays/stayCard.ts b/apps/scandic-web/types/components/myPages/stays/stayCard.ts index 314dee426..575124723 100644 --- a/apps/scandic-web/types/components/myPages/stays/stayCard.ts +++ b/apps/scandic-web/types/components/myPages/stays/stayCard.ts @@ -2,4 +2,5 @@ import type { Stay } from "@scandic-hotels/trpc/routers/user/output" export type StayCardProps = { stay: Stay + ref?: React.Ref } diff --git a/packages/design-system/lib/components/SidePeek/SelfControlled.tsx b/packages/design-system/lib/components/SidePeek/SelfControlled.tsx index 5c156d5fa..3531fa4fc 100644 --- a/packages/design-system/lib/components/SidePeek/SelfControlled.tsx +++ b/packages/design-system/lib/components/SidePeek/SelfControlled.tsx @@ -18,6 +18,7 @@ interface SidePeekSelfControlledProps extends React.PropsWithChildren { title: string isOpen: boolean onClose: () => void + sidePeekSEO?: boolean } export default function SidePeekSelfControlled({ @@ -25,6 +26,7 @@ export default function SidePeekSelfControlled({ isOpen, onClose, title, + sidePeekSEO = true, }: SidePeekSelfControlledProps) { const intl = useIntl() @@ -81,8 +83,7 @@ export default function SidePeekSelfControlled({ - - {children} + {sidePeekSEO && {children}} ) }