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
This commit is contained in:
@@ -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 (
|
||||
<SidePeekSelfControlled
|
||||
title={intl.formatMessage({
|
||||
@@ -68,6 +81,7 @@ export function PreviousStaysSidePeek({
|
||||
})}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
sidePeekSEO={false}
|
||||
>
|
||||
<div ref={scrollContainerRef} className={styles.content}>
|
||||
{isLoading ? (
|
||||
@@ -84,12 +98,16 @@ export function PreviousStaysSidePeek({
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.staysList}>
|
||||
{stays.map((stay) => (
|
||||
<StayCard
|
||||
key={stay.attributes.confirmationNumber}
|
||||
stay={stay}
|
||||
/>
|
||||
))}
|
||||
{stays.map((stay) => {
|
||||
const isFirstNewItem = stay === firstNewTransaction
|
||||
return (
|
||||
<StayCard
|
||||
key={stay.attributes.confirmationNumber}
|
||||
stay={stay}
|
||||
ref={isFirstNewItem ? focusRefCallback : null}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
|
||||
@@ -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 (
|
||||
<Link href={bookingUrl} className={styles.link}>
|
||||
<Link className={styles.link} href={bookingUrl} ref={ref}>
|
||||
<CardContent stay={stay} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function CardContent({ stay }: StayCardProps) {
|
||||
function CardContent({ stay }: { stay: StayCardProps["stay"] }) {
|
||||
const lang = useLang()
|
||||
const intl = useIntl()
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<SidePeekSelfControlled
|
||||
title={intl.formatMessage({
|
||||
@@ -69,6 +81,7 @@ export function UpcomingStaysSidePeek({
|
||||
})}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
sidePeekSEO={false}
|
||||
>
|
||||
<div ref={scrollContainerRef} className={styles.content}>
|
||||
{isLoading ? (
|
||||
@@ -85,12 +98,16 @@ export function UpcomingStaysSidePeek({
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.staysList}>
|
||||
{stays.map((stay) => (
|
||||
<StayCard
|
||||
key={stay.attributes.confirmationNumber}
|
||||
stay={stay}
|
||||
/>
|
||||
))}
|
||||
{stays.map((stay) => {
|
||||
const isFirstNewItem = stay === firstNewTransaction
|
||||
return (
|
||||
<StayCard
|
||||
key={stay.attributes.confirmationNumber}
|
||||
stay={stay}
|
||||
ref={isFirstNewItem ? focusRefCallback : null}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
|
||||
@@ -2,4 +2,5 @@ import type { Stay } from "@scandic-hotels/trpc/routers/user/output"
|
||||
|
||||
export type StayCardProps = {
|
||||
stay: Stay
|
||||
ref?: React.Ref<HTMLAnchorElement>
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalOverlay>
|
||||
|
||||
<SidePeekSEO title={title}>{children}</SidePeekSEO>
|
||||
{sidePeekSEO && <SidePeekSEO title={title}>{children}</SidePeekSEO>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user