Merged in feat/SW-904-add-back-to-top-button (pull request #923)

Feat/SW-904 add back to top button on hotel list page

* feat(SW-904): Added back to top button

* fix: removed alert on hotel listing page

* Remove console.log


Approved-by: Niclas Edenvin
This commit is contained in:
Pontus Dreij
2024-11-19 08:22:03 +00:00
committed by Niclas Edenvin
parent bd0720dc0f
commit 60f1d268a9
8 changed files with 124 additions and 31 deletions

View File

@@ -7,7 +7,6 @@ import { selectHotelMap, selectRate } from "@/constants/routes/hotelReservation"
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
import ImageGallery from "@/components/ImageGallery"
import Alert from "@/components/TempDesignSystem/Alert"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
@@ -133,13 +132,6 @@ export default function HotelCard({
hotel={hotelData}
showCTA={true}
/>
{hotelData.specialAlerts.length > 0 && (
<div className={styles.specialAlerts}>
{hotelData.specialAlerts.map((alert) => (
<Alert key={alert.id} type={alert.type} text={alert.text} />
))}
</div>
)}
</section>
<HotelPriceList price={price} hotelId={hotel.hotelData.operaId} />
</div>

View File

@@ -1,9 +1,12 @@
"use client"
import { useSearchParams } from "next/navigation"
import { useMemo } from "react"
import { useEffect, useMemo, useState } from "react"
import { useIntl } from "react-intl"
import { useHotelFilterStore } from "@/stores/hotel-filters"
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
import HotelCard from "../HotelCard"
import { DEFAULT_SORT } from "../SelectHotel/HotelSorter"
@@ -22,9 +25,11 @@ export default function HotelCardListing({
activeCard,
onHotelCardHover,
}: HotelCardListingProps) {
const intl = useIntl()
const searchParams = useSearchParams()
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
const sortBy = useMemo(
() => searchParams.get("sort") ?? DEFAULT_SORT,
@@ -82,6 +87,20 @@ export default function HotelCardListing({
return filteredHotels
}, [activeFilters, sortedHotels, setResultCount])
useEffect(() => {
const handleScroll = () => {
const hasScrolledPast = window.scrollY > 490
setShowBackToTop(hasScrolledPast)
}
window.addEventListener("scroll", handleScroll, { passive: true })
return () => window.removeEventListener("scroll", handleScroll)
}, [])
function scrollToTop() {
window.scrollTo({ top: 0, behavior: "smooth" })
}
return (
<section className={styles.hotelCards}>
{hotels?.length
@@ -95,6 +114,7 @@ export default function HotelCardListing({
/>
))
: null}
{showBackToTop && <BackToTopButton onClick={scrollToTop} />}
</section>
)
}

View File

@@ -7,8 +7,9 @@ import { useMediaQuery } from "usehooks-ts"
import { selectHotel } from "@/constants/routes/hotelReservation"
import { CloseIcon, CloseLargeIcon } from "@/components/Icons"
import { ArrowUpIcon, CloseIcon, CloseLargeIcon } from "@/components/Icons"
import InteractiveMap from "@/components/Maps/InteractiveMap"
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
import Button from "@/components/TempDesignSystem/Button"
import useLang from "@/hooks/useLang"
@@ -109,17 +110,7 @@ export default function SelectHotelMap({
activeHotelPin={activeHotelPin}
setActiveHotelPin={setActiveHotelPin}
/>
{showBackToTop && (
<Button
intent="inverted"
size="small"
theme="base"
className={styles.backToTopButton}
onClick={scrollToTop}
>
{intl.formatMessage({ id: "Back to top" })}
</Button>
)}
{showBackToTop && <BackToTopButton onClick={scrollToTop} />}
</div>
<InteractiveMap
closeButton={closeButton}

View File

@@ -23,10 +23,6 @@
height: 44px;
}
.backToTopButton {
display: none;
}
@media (min-width: 768px) {
.container .closeButton {
display: flex;
@@ -34,12 +30,7 @@
.container .listingContainer .filterContainer .filterContainerCloseButton {
display: none;
}
.backToTopButton {
position: fixed;
bottom: 24px;
left: 32px;
display: flex;
}
.listingContainer {
background-color: var(--Base-Surface-Secondary-light-Normal);
padding: var(--Spacing-x3) var(--Spacing-x4);

View File

@@ -0,0 +1,33 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function ArrowUpIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
id="a"
width="20"
height="20"
x="0"
y="0"
maskUnits="userSpaceOnUse"
>
<path fill="#D9D9D9" d="M0 0h20v20H0z" />
</mask>
<path
fill="#4D001B"
d="m9.219 6.541-4.021 4.021a.74.74 0 0 1-.552.235.778.778 0 0 1-.552-.245.796.796 0 0 1-.235-.552.74.74 0 0 1 .235-.552l5.354-5.355a.77.77 0 0 1 .849-.171.77.77 0 0 1 .255.171l5.354 5.355a.782.782 0 0 1 0 1.104.764.764 0 0 1-1.114 0l-4.01-4.01v9.135c0 .215-.077.4-.23.552a.752.752 0 0 1-.552.229.752.752 0 0 1-.552-.23.752.752 0 0 1-.23-.551V6.54Z"
/>
</svg>
)
}

View File

@@ -6,6 +6,7 @@ export { default as AirIcon } from "./Air"
export { default as AirplaneIcon } from "./Airplane"
export { default as AllergyIcon } from "./Allergy"
export { default as ArrowRightIcon } from "./ArrowRight"
export { default as ArrowUpIcon } from "./ArrowUp"
export { default as BarIcon } from "./Bar"
export { default as BathtubIcon } from "./Bathtub"
export { default as BedDoubleIcon } from "./BedDouble"

View File

@@ -0,0 +1,45 @@
.backToTopButton {
border-radius: var(--Corner-radius-Rounded);
cursor: pointer;
display: flex;
align-items: flex-end;
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
background-color: var(--Base-Surface-Primary-light-Normal);
color: var(--Base-Button-Secondary-On-Fill-Normal);
border: 2px solid var(--Base-Button-Secondary-On-Fill-Normal);
gap: var(--Spacing-x-half);
padding: var(--Spacing-x1);
text-align: center;
transition:
background-color 300ms ease,
color 300ms ease;
font-family: var(--typography-Body-Bold-fontFamily);
font-weight: 500;
font-size: var(--typography-Caption-Bold-fontSize);
line-height: var(--typography-Caption-Bold-lineHeight);
letter-spacing: 0.6%;
text-decoration: none;
}
.backToTopButtonText {
display: none;
}
@media (min-width: 768px) {
.backToTopButtonText {
display: initial;
}
.backToTopButton:hover {
background-color: var(--Base-Button-Tertiary-Fill-Normal);
color: var(--Base-Button-Tertiary-On-Fill-Hover);
}
.backToTopButton:hover > svg * {
fill: var(--Base-Button-Tertiary-On-Fill-Hover);
}
.backToTopButton {
padding: calc(var(--Spacing-x1) + 2px) var(--Spacing-x2);
}
}

View File

@@ -0,0 +1,20 @@
"use client"
import { Button as ButtonRAC } from "react-aria-components"
import { useIntl } from "react-intl"
import { ArrowUpIcon } from "@/components/Icons"
import styles from "./backToTopButton.module.css"
export function BackToTopButton({ onClick }: { onClick: () => void }) {
const intl = useIntl()
return (
<ButtonRAC className={styles.backToTopButton} onPress={onClick}>
<ArrowUpIcon color="burgundy" />
<span className={styles.backToTopButtonText}>
{intl.formatMessage({ id: "Back to top" })}
</span>
</ButtonRAC>
)
}