Merged in feat/SW-2511-hotel-page-map (pull request #2582)
feat(SW-2511): hotel page map and marker improvements * feat(SW-2511): update hotel page map * fix(SW-2511): fix issue with identical id's for POIs Approved-by: Anton Gunnarsson
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -6,12 +6,12 @@ import { useState } from "react"
|
|||||||
import { Button as ButtonRAC } from "react-aria-components"
|
import { Button as ButtonRAC } from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { logger } from "@scandic-hotels/common/logger"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { PointOfInterestGroupEnum } from "@scandic-hotels/trpc/enums/pointOfInterest"
|
|
||||||
|
|
||||||
import PoiMarker from "@/components/Maps/Markers/Poi"
|
import PoiMarker from "@/components/Maps/Markers/Poi"
|
||||||
|
|
||||||
|
import { translatePOIGroup } from "./util"
|
||||||
|
|
||||||
import styles from "./sidebar.module.css"
|
import styles from "./sidebar.module.css"
|
||||||
|
|
||||||
import type { SidebarProps } from "@/types/components/hotelPage/map/sidebar"
|
import type { SidebarProps } from "@/types/components/hotelPage/map/sidebar"
|
||||||
@@ -93,42 +93,6 @@ export default function Sidebar({
|
|||||||
defaultMessage: "View as list",
|
defaultMessage: "View as list",
|
||||||
})
|
})
|
||||||
|
|
||||||
function translatePOIGroup(group: PointOfInterestGroupEnum) {
|
|
||||||
switch (group) {
|
|
||||||
case PointOfInterestGroupEnum.PUBLIC_TRANSPORT:
|
|
||||||
return intl.formatMessage({
|
|
||||||
defaultMessage: "Public transport",
|
|
||||||
})
|
|
||||||
case PointOfInterestGroupEnum.ATTRACTIONS:
|
|
||||||
return intl.formatMessage({
|
|
||||||
defaultMessage: "Attractions",
|
|
||||||
})
|
|
||||||
case PointOfInterestGroupEnum.BUSINESS:
|
|
||||||
return intl.formatMessage({
|
|
||||||
defaultMessage: "Business",
|
|
||||||
})
|
|
||||||
case PointOfInterestGroupEnum.LOCATION:
|
|
||||||
return intl.formatMessage({
|
|
||||||
defaultMessage: "Location",
|
|
||||||
})
|
|
||||||
case PointOfInterestGroupEnum.PARKING:
|
|
||||||
return intl.formatMessage({
|
|
||||||
defaultMessage: "Parking",
|
|
||||||
})
|
|
||||||
case PointOfInterestGroupEnum.SHOPPING_DINING:
|
|
||||||
return intl.formatMessage({
|
|
||||||
defaultMessage: "Shopping & Dining",
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
const option: never = group
|
|
||||||
logger.warn(`Unsupported group given: ${option}`)
|
|
||||||
|
|
||||||
return intl.formatMessage({
|
|
||||||
defaultMessage: "N/A",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<aside
|
<aside
|
||||||
@@ -162,23 +126,31 @@ export default function Sidebar({
|
|||||||
<Typography variant="Body/Paragraph/mdBold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<h3 className={styles.poiHeading}>
|
<h3 className={styles.poiHeading}>
|
||||||
<PoiMarker group={group} />
|
<PoiMarker group={group} />
|
||||||
{translatePOIGroup(group)}
|
{translatePOIGroup(group, intl)}
|
||||||
</h3>
|
</h3>
|
||||||
</Typography>
|
</Typography>
|
||||||
<ul className={styles.poiList}>
|
<ul className={styles.poiList}>
|
||||||
{pois.map((poi) => (
|
{pois.map((poi) => (
|
||||||
<li key={poi.name} className={styles.poiItem}>
|
<li
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
key={poi.name + poi.categoryName}
|
||||||
<ButtonRAC
|
className={styles.poiItem}
|
||||||
className={cx(styles.poiButton, {
|
>
|
||||||
[styles.active]: activePoi === poi.name,
|
<ButtonRAC
|
||||||
})}
|
className={cx(styles.poiButton, {
|
||||||
onHoverStart={() => handleMouseEnter(poi.name)}
|
[styles.active]: activePoi === poi.name,
|
||||||
onPress={() =>
|
})}
|
||||||
handlePoiClick(poi.name, poi.coordinates)
|
onHoverStart={() => handleMouseEnter(poi.name)}
|
||||||
}
|
onPress={() =>
|
||||||
>
|
handlePoiClick(poi.name, poi.coordinates)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<span>{poi.name}</span>
|
<span>{poi.name}</span>
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="Body/Supporting text (caption)/smRegular"
|
||||||
|
className={styles.distance}
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
@@ -189,8 +161,8 @@ export default function Sidebar({
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</ButtonRAC>
|
</Typography>
|
||||||
</Typography>
|
</ButtonRAC>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -47,13 +47,17 @@
|
|||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
.poiButton.active {
|
.poiButton.active {
|
||||||
background-color: var(--Base-Surface-Primary-light-Hover);
|
background-color: var(--Surface-Primary-Hover-Light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: var(--Text-Heading);
|
color: var(--Text-Heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.distance {
|
||||||
|
color: var(--Text-Secondary);
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
--sidebar-mobile-toggle-height: 88px;
|
--sidebar-mobile-toggle-height: 88px;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { logger } from "@scandic-hotels/common/logger"
|
||||||
|
import { PointOfInterestGroupEnum } from "@scandic-hotels/trpc/enums/pointOfInterest"
|
||||||
|
|
||||||
|
import type { IntlShape } from "react-intl"
|
||||||
|
|
||||||
|
export function translatePOIGroup(
|
||||||
|
group: PointOfInterestGroupEnum,
|
||||||
|
intl: IntlShape
|
||||||
|
) {
|
||||||
|
switch (group) {
|
||||||
|
case PointOfInterestGroupEnum.PUBLIC_TRANSPORT:
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "Public transport",
|
||||||
|
})
|
||||||
|
case PointOfInterestGroupEnum.ATTRACTIONS:
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "Attractions",
|
||||||
|
})
|
||||||
|
case PointOfInterestGroupEnum.BUSINESS:
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "Business",
|
||||||
|
})
|
||||||
|
case PointOfInterestGroupEnum.LOCATION:
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "Location",
|
||||||
|
})
|
||||||
|
case PointOfInterestGroupEnum.PARKING:
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "Parking",
|
||||||
|
})
|
||||||
|
case PointOfInterestGroupEnum.SHOPPING_DINING:
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "Shopping & Dining",
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
const option: never = group
|
||||||
|
logger.warn(`Unsupported group given: ${option}`)
|
||||||
|
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "N/A",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,12 +41,12 @@ export default function MapCard({ hotelName, pois }: MapCardProps) {
|
|||||||
</span>
|
</span>
|
||||||
<ul className={styles.poiList}>
|
<ul className={styles.poiList}>
|
||||||
{pois.map((poi) => (
|
{pois.map((poi) => (
|
||||||
<li key={poi.name} className={styles.poiItem}>
|
<li key={poi.name + poi.categoryName} className={styles.poiItem}>
|
||||||
<PoiMarker
|
<PoiMarker
|
||||||
group={poi.group}
|
group={poi.group}
|
||||||
categoryName={poi.categoryName}
|
categoryName={poi.categoryName}
|
||||||
skipBackground
|
skipBackground
|
||||||
size={20}
|
size="medium"
|
||||||
/>
|
/>
|
||||||
<Typography>
|
<Typography>
|
||||||
<span>{poi.name} </span>
|
<span>{poi.name} </span>
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export function IconByIconName({
|
|||||||
case IconName.Calendar:
|
case IconName.Calendar:
|
||||||
return <MaterialIcon icon="calendar_today" {...props} />
|
return <MaterialIcon icon="calendar_today" {...props} />
|
||||||
case IconName.Camera:
|
case IconName.Camera:
|
||||||
return <MaterialIcon icon="camera" {...props} />
|
return <MaterialIcon icon="photo_camera" {...props} />
|
||||||
case IconName.Cellphone:
|
case IconName.Cellphone:
|
||||||
case IconName.Phone:
|
case IconName.Phone:
|
||||||
return <MaterialIcon icon="phone" {...props} />
|
return <MaterialIcon icon="phone" {...props} />
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import {
|
|||||||
} from "@vis.gl/react-google-maps"
|
} from "@vis.gl/react-google-maps"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Body from "@scandic-hotels/design-system/Body"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import Caption from "@scandic-hotels/design-system/Caption"
|
|
||||||
|
|
||||||
import HotelMarkerByType from "../../Markers"
|
import HotelMarkerByType from "../../Markers"
|
||||||
import PoiMarker from "../../Markers/Poi"
|
import PoiMarker from "../../Markers/Poi"
|
||||||
@@ -46,7 +45,7 @@ export default function PoiMapMarkers({
|
|||||||
|
|
||||||
{pointsOfInterest.map((poi) => (
|
{pointsOfInterest.map((poi) => (
|
||||||
<AdvancedMarker
|
<AdvancedMarker
|
||||||
key={poi.name}
|
key={poi.name + poi.categoryName}
|
||||||
className={styles.advancedMarker}
|
className={styles.advancedMarker}
|
||||||
position={poi.coordinates}
|
position={poi.coordinates}
|
||||||
anchorPoint={AdvancedMarkerAnchorPoint.CENTER}
|
anchorPoint={AdvancedMarkerAnchorPoint.CENTER}
|
||||||
@@ -61,25 +60,28 @@ export default function PoiMapMarkers({
|
|||||||
<PoiMarker
|
<PoiMarker
|
||||||
group={poi.group}
|
group={poi.group}
|
||||||
categoryName={poi.categoryName}
|
categoryName={poi.categoryName}
|
||||||
size={activePoi === poi.name ? 20 : 16}
|
size={activePoi === poi.name ? "large" : "small"}
|
||||||
/>
|
/>
|
||||||
<Body className={styles.poiLabel} asChild>
|
<span className={styles.poiLabel}>
|
||||||
<span>
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
{poi.name}
|
<span>{poi.name}</span>
|
||||||
<Caption asChild>
|
</Typography>
|
||||||
<span>
|
<Typography
|
||||||
{intl.formatMessage(
|
variant="Body/Supporting text (caption)/smRegular"
|
||||||
{
|
className={styles.distance}
|
||||||
defaultMessage: "{distanceInKm} km",
|
>
|
||||||
},
|
<span>
|
||||||
{
|
{intl.formatMessage(
|
||||||
distanceInKm: poi.distance,
|
{
|
||||||
}
|
defaultMessage: "{distanceInKm} km",
|
||||||
)}
|
},
|
||||||
</span>
|
{
|
||||||
</Caption>
|
distanceInKm: poi.distance,
|
||||||
</span>
|
}
|
||||||
</Body>
|
)}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</AdvancedMarker>
|
</AdvancedMarker>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
.advancedMarker {
|
.advancedMarker {
|
||||||
height: var(--Spacing-x4);
|
height: var(--Space-x4);
|
||||||
width: var(
|
width: var(
|
||||||
--Spacing-x4
|
--Space-x4
|
||||||
) !important; /* Overwriting the default width of the @vis.gl/react-google-maps AdvancedMarker */
|
) !important; /* Overwriting the default width of the @vis.gl/react-google-maps AdvancedMarker */
|
||||||
}
|
}
|
||||||
|
|
||||||
.advancedMarker.active {
|
.advancedMarker.active {
|
||||||
height: var(--Spacing-x5);
|
height: var(--Space-x5);
|
||||||
width: var(
|
width: var(
|
||||||
--Spacing-x5
|
--Space-x5
|
||||||
) !important; /* Overwriting the default width of the @vis.gl/react-google-maps AdvancedMarker */
|
) !important; /* Overwriting the default width of the @vis.gl/react-google-maps AdvancedMarker */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,15 +19,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--Spacing-x-half);
|
padding: var(--Space-x05);
|
||||||
border-radius: var(--Corner-radius-rounded);
|
border-radius: var(--Corner-radius-rounded);
|
||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
background-color: var(--Surface-UI-Fill-Default);
|
||||||
box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.1);
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Space-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.poi.active {
|
.poi.active {
|
||||||
padding-right: var(--Spacing-x-one-and-half);
|
padding-right: var(--Space-x15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.poiLabel {
|
.poiLabel {
|
||||||
@@ -37,6 +37,10 @@
|
|||||||
.poi.active .poiLabel {
|
.poi.active .poiLabel {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--Spacing-x2);
|
gap: var(--Space-x2);
|
||||||
text-wrap: nowrap;
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.distance {
|
||||||
|
color: var(--Text-Secondary);
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ export default function PoiMarker({
|
|||||||
group,
|
group,
|
||||||
categoryName,
|
categoryName,
|
||||||
skipBackground,
|
skipBackground,
|
||||||
size = 16,
|
size = "small",
|
||||||
className = "",
|
className = "",
|
||||||
}: PoiMarkerProps) {
|
}: PoiMarkerProps) {
|
||||||
const iconName = getIconByPoiGroupAndCategory(group, categoryName)
|
const iconName = getIconByPoiGroupAndCategory(group, categoryName)
|
||||||
const classNames = poiVariants({ group, skipBackground, className })
|
const classNames = poiVariants({ group, skipBackground, size, className })
|
||||||
|
|
||||||
return iconName ? (
|
return iconName ? (
|
||||||
<span className={classNames}>
|
<span className={classNames}>
|
||||||
<IconByIconName
|
<IconByIconName
|
||||||
iconName={iconName}
|
iconName={iconName}
|
||||||
color={skipBackground ? "Icon/Feedback/Neutral" : "Icon/Inverted"}
|
color={skipBackground ? "Icon/Feedback/Neutral" : "Icon/Inverted"}
|
||||||
size={size}
|
size={size === "small" ? 16 : size === "large" ? 24 : 20}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
) : null
|
) : null
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
.icon {
|
.icon {
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: var(--Corner-radius-rounded);
|
border-radius: var(--Corner-radius-rounded);
|
||||||
background-color: var(--UI-Text-Placeholder);
|
background-color: var(--Surface-UI-Fill-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
width: var(--Space-x3);
|
||||||
|
height: var(--Space-x3);
|
||||||
|
}
|
||||||
|
.large {
|
||||||
|
width: var(--Space-x4);
|
||||||
|
height: var(--Space-x4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.attractions {
|
.attractions {
|
||||||
background-color: var(--Base-Interactive-Surface-Secondary-normal);
|
background-color: var(--Surface-Accent-3);
|
||||||
}
|
}
|
||||||
.business {
|
.business {
|
||||||
background-color: var(--Scandic-Yellow-50);
|
background-color: var(--Surface-Accent-4);
|
||||||
}
|
}
|
||||||
.location {
|
.location {
|
||||||
background-color: var(--UI-Text-Placeholder);
|
background-color: var(--Surface-Feedback-Neutral-Accent);
|
||||||
}
|
}
|
||||||
.parking {
|
.parking {
|
||||||
background-color: var(--UI-Text-Active);
|
background-color: var(--Surface-Accent-5);
|
||||||
}
|
}
|
||||||
.publicTransport {
|
.publicTransport {
|
||||||
background-color: var(--Base-Interactive-Surface-Tertiary-normal);
|
background-color: var(--Surface-Accent-2);
|
||||||
}
|
}
|
||||||
.shoppingDining {
|
.shoppingDining {
|
||||||
background-color: var(--Base-Interactive-Surface-Primary-normal);
|
background-color: var(--Surface-Accent-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon.transparent {
|
.icon.transparent {
|
||||||
|
|||||||
@@ -18,8 +18,14 @@ export const poiVariants = cva(styles.icon, {
|
|||||||
true: styles.transparent,
|
true: styles.transparent,
|
||||||
false: "",
|
false: "",
|
||||||
},
|
},
|
||||||
|
size: {
|
||||||
|
small: styles.small,
|
||||||
|
medium: styles.small,
|
||||||
|
large: styles.large,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
skipBackground: false,
|
skipBackground: false,
|
||||||
|
size: "small",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function getIconByPoiGroupAndCategory(
|
|||||||
case PointOfInterestGroupEnum.PARKING:
|
case PointOfInterestGroupEnum.PARKING:
|
||||||
return IconName.Parking
|
return IconName.Parking
|
||||||
case PointOfInterestGroupEnum.SHOPPING_DINING:
|
case PointOfInterestGroupEnum.SHOPPING_DINING:
|
||||||
return IconName.Shopping
|
return category === "Restaurant" ? IconName.Restaurant : IconName.Shopping
|
||||||
case PointOfInterestGroupEnum.LOCATION:
|
case PointOfInterestGroupEnum.LOCATION:
|
||||||
default:
|
default:
|
||||||
return IconName.Location
|
return IconName.Location
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,5 @@ import type { poiVariants } from "@/components/Maps/Markers/Poi/variants"
|
|||||||
export interface PoiMarkerProps extends VariantProps<typeof poiVariants> {
|
export interface PoiMarkerProps extends VariantProps<typeof poiVariants> {
|
||||||
group: PointOfInterestGroupEnum
|
group: PointOfInterestGroupEnum
|
||||||
categoryName?: string
|
categoryName?: string
|
||||||
size?: number
|
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,7 +269,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url(/_static/fonts/material-symbols/rounded-e6bd32d5.woff2)
|
src: url(/_static/fonts/material-symbols/rounded-9290efcd.woff2)
|
||||||
format('woff2');
|
format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -54,6 +54,7 @@ const icons = [
|
|||||||
'call',
|
'call',
|
||||||
'call_quality',
|
'call_quality',
|
||||||
'camera',
|
'camera',
|
||||||
|
'photo_camera',
|
||||||
'cancel',
|
'cancel',
|
||||||
'chair',
|
'chair',
|
||||||
'charging_station',
|
'charging_station',
|
||||||
|
|||||||
Reference in New Issue
Block a user