feat(SW-2144): added back-to-top button on destination map views

Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-05-28 08:54:27 +00:00
parent 42701739a0
commit bb5e7b65bb
9 changed files with 81 additions and 64 deletions

View File

@@ -66,9 +66,9 @@ export default function CityListing() {
</li> </li>
))} ))}
</ul> </ul>
{showBackToTop && ( {showBackToTop ? (
<BackToTopButton position="center" onClick={scrollToTop} /> <BackToTopButton position="right" onClick={scrollToTop} />
)} ) : null}
</> </>
)} )}
</section> </section>

View File

@@ -11,8 +11,7 @@
} }
.hotelList { .hotelList {
display: flex; display: grid;
flex-direction: column;
gap: var(--Spacing-x3); gap: var(--Spacing-x3);
list-style: none; list-style: none;
} }

View File

@@ -140,6 +140,7 @@ export default function HotelListItem(data: DestinationPagesHotelData) {
variant="Tertiary" variant="Tertiary"
color="Primary" color="Primary"
size="Small" size="Small"
typography="Body/Paragraph/mdBold"
> >
{intl.formatMessage({ {intl.formatMessage({
defaultMessage: "See hotel details", defaultMessage: "See hotel details",

View File

@@ -132,6 +132,7 @@ export default function HotelListingItem(data: DestinationPagesHotelData) {
color="Primary" color="Primary"
size="Medium" size="Medium"
wrapping={false} wrapping={false}
typography="Body/Paragraph/mdBold"
onClick={() => setActiveMarker(hotel.id)} onClick={() => setActiveMarker(hotel.id)}
> >
{intl.formatMessage({ {intl.formatMessage({
@@ -150,6 +151,7 @@ export default function HotelListingItem(data: DestinationPagesHotelData) {
variant="Tertiary" variant="Tertiary"
color="Primary" color="Primary"
size="Small" size="Small"
typography="Body/Paragraph/mdBold"
> >
{intl.formatMessage({ {intl.formatMessage({
defaultMessage: "See hotel details", defaultMessage: "See hotel details",

View File

@@ -97,9 +97,9 @@ export default function HotelListing() {
</li> </li>
))} ))}
</ul> </ul>
{showBackToTop && ( {showBackToTop ? (
<BackToTopButton position="center" onClick={scrollToTop} /> <BackToTopButton position="right" onClick={scrollToTop} />
)} ) : null}
</> </>
)} )}
</section> </section>

View File

@@ -17,7 +17,9 @@ import { useDestinationDataStore } from "@/stores/destination-data"
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map" import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort" import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
import { useScrollToTop } from "@/hooks/useScrollToTop"
import { debounce } from "@/utils/debounce" import { debounce } from "@/utils/debounce"
import DynamicMap from "./DynamicMap" import DynamicMap from "./DynamicMap"
@@ -52,6 +54,12 @@ export default function Map({
const activeHotel = hotels.find(({ hotel }) => hotel.id === activeHotelId) const activeHotel = hotels.find(({ hotel }) => hotel.id === activeHotelId)
const rootDiv = useRef<HTMLDivElement | null>(null) const rootDiv = useRef<HTMLDivElement | null>(null)
const [mapHeight, setMapHeight] = useState("100dvh") const [mapHeight, setMapHeight] = useState("100dvh")
const scrollRef = useRef<HTMLElement>(null)
const { showBackToTop, scrollToTop } = useScrollToTop({
threshold: 550,
elementRef: scrollRef,
refScrollable: true,
})
const intl = useIntl() const intl = useIntl()
@@ -145,7 +153,12 @@ export default function Map({
listType={pageType === "city" ? "hotel" : "city"} listType={pageType === "city" ? "hotel" : "city"}
/> />
</div> </div>
<aside className={styles.sidebar}>{children}</aside> <aside className={styles.sidebar} ref={scrollRef}>
{children}
{showBackToTop ? (
<BackToTopButton position="left" onClick={scrollToTop} />
) : null}
</aside>
<DynamicMap <DynamicMap
markers={markers} markers={markers}
mapId={mapId} mapId={mapId}

View File

@@ -1,38 +1,33 @@
.backToTopButton { .backToTopButton {
border-radius: var(--Corner-radius-rounded); display: inline-flex;
padding: var(--Space-x1);
justify-content: center;
align-items: center;
gap: var(--Space-x05);
width: max-content;
color: var(--Component-Button-Brand-Secondary-On-fill-Default);
background-color: var(--Component-Button-Brand-Secondary-Fill-Inverted);
border: 2px solid var(--Component-Button-Brand-Secondary-Border-Default);
border-radius: var(--Corner-radius-Rounded);
box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1);
cursor: pointer; cursor: pointer;
display: flex; position: sticky;
align-items: flex-end; bottom: var(--Space-x2);
position: fixed;
bottom: 20px;
z-index: var(--back-to-top-button);
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.084px;
text-decoration: none;
}
.backToTopButtonText { &:hover {
display: none; color: var(--Component-Button-Brand-Secondary-On-fill-Inverted);
background-color: var(
--Component-Button-Brand-Secondary-Fill-Hover-Inverted
);
}
} }
.left { .left {
left: 32px; left: 0;
} }
.right { .right {
right: 32px; left: 100%;
} }
.center { .center {
@@ -40,18 +35,14 @@
transform: translateX(-50%); transform: translateX(-50%);
} }
@media (min-width: 768px) { @media screen and (max-width: 767px) {
.backToTopButtonText { .text {
display: initial; display: none;
} }
.backToTopButton:hover { }
background-color: var(--Base-Button-Tertiary-Fill-Normal);
color: var(--Base-Button-Tertiary-On-Fill-Hover); @media screen and (min-width: 768px) {
} .backToTopButton {
.backToTopButton:hover > svg * { padding: 10px var(--Space-x2);
fill: var(--Base-Button-Tertiary-On-Fill-Hover);
}
.backToTopButton {
padding: calc(var(--Spacing-x1) + 2px) var(--Spacing-x2);
} }
} }

View File

@@ -0,0 +1,9 @@
import type { VariantProps } from "class-variance-authority"
import type { ComponentProps } from "react"
import type { Button } from "react-aria-components"
import type { backToTopButtonVariants } from "./variants"
export interface BackToTopButtonProps
extends ComponentProps<typeof Button>,
VariantProps<typeof backToTopButtonVariants> {}

View File

@@ -4,30 +4,32 @@ import { Button as ButtonRAC } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { backToTopButtonVariants } from "./variants" import { backToTopButtonVariants } from "./variants"
import styles from "./backToTopButton.module.css" import styles from "./backToTopButton.module.css"
export function BackToTopButton({ import type { BackToTopButtonProps } from "./backToTopButton"
onClick,
position, export function BackToTopButton({ position, ...props }: BackToTopButtonProps) {
}: {
onClick: () => void
position: "left" | "right" | "center"
}) {
const intl = useIntl() const intl = useIntl()
return ( return (
<ButtonRAC <Typography variant="Body/Supporting text (caption)/smBold">
className={backToTopButtonVariants({ position })} <ButtonRAC
onPress={onClick} className={backToTopButtonVariants({ position })}
> aria-label={intl.formatMessage({
<MaterialIcon icon="arrow_upward" color="CurrentColor" />
<span className={styles.backToTopButtonText}>
{intl.formatMessage({
defaultMessage: "Back to top", defaultMessage: "Back to top",
})} })}
</span> {...props}
</ButtonRAC> >
<MaterialIcon icon="arrow_upward" color="CurrentColor" size={20} />
<span className={styles.text}>
{intl.formatMessage({
defaultMessage: "Back to top",
})}
</span>
</ButtonRAC>
</Typography>
) )
} }