Merged in fix/BOOK-210-update-hotel-card-local-charges (pull request #2835)
fix(BOOK-210): add local charges for Finland and update design for hotel card * fix(BOOK-210): add local charges for Finland and update design for hotel card * feat(BOOK-210): change variant to conditional classname * fix(BOOK-210): update link with icon * fix(BOOK-210): update buttonlink tripadvisor * fix(BOOK-210): switch wrapper logic * fix(BOOK-210): update variants tripadvisor Approved-by: Erik Tiekstra
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
import TripadvisorIcon from "@scandic-hotels/design-system/Icons/TripadvisorIcon"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./tripAdvisorLink.module.css"
|
||||
|
||||
import type { HotelTripAdvisor } from "@scandic-hotels/trpc/types/hotel"
|
||||
|
||||
import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage"
|
||||
|
||||
interface TripAdvisorLinkProps {
|
||||
tripAdvisor: NonNullable<HotelTripAdvisor>
|
||||
}
|
||||
|
||||
export default async function TripAdvisorLink({
|
||||
tripAdvisor,
|
||||
}: TripAdvisorLinkProps) {
|
||||
const intl = await getIntl()
|
||||
const { rating, numberOfReviews, reviews } = tripAdvisor
|
||||
const hasTripAdvisorData = !!(rating && numberOfReviews)
|
||||
|
||||
if (!hasTripAdvisorData) {
|
||||
return null
|
||||
}
|
||||
|
||||
const formattedTripAdvisorText = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{rating} ({count} reviews on Tripadvisor)",
|
||||
},
|
||||
{ rating, count: numberOfReviews }
|
||||
)
|
||||
|
||||
const hasTripAdvisorIframeSrc = !!reviews.widgetScriptEmbedUrlIframe
|
||||
const tripAdvisorHref = hasTripAdvisorIframeSrc
|
||||
? `?s=${SidepeekSlugs.tripAdvisor}`
|
||||
: null
|
||||
|
||||
if (!tripAdvisorHref) {
|
||||
return (
|
||||
<span className={styles.tripAdvisorText}>
|
||||
<TripadvisorIcon color="CurrentColor" />
|
||||
{formattedTripAdvisorText}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
variant="icon"
|
||||
textDecoration="underline"
|
||||
color="Text/Interactive/Secondary"
|
||||
size="small"
|
||||
href={tripAdvisorHref}
|
||||
>
|
||||
<TripadvisorIcon color="CurrentColor" size={20} />
|
||||
{formattedTripAdvisorText}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
.tripAdvisorText {
|
||||
display: flex;
|
||||
gap: var(--Space-x05);
|
||||
align-items: center;
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./tripAdvisorSection.module.css"
|
||||
|
||||
import type { HotelTripAdvisor } from "@scandic-hotels/trpc/types/hotel"
|
||||
|
||||
import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage"
|
||||
|
||||
interface TripAdvisorSectionProps {
|
||||
tripAdvisor: NonNullable<HotelTripAdvisor>
|
||||
}
|
||||
|
||||
export default async function TripAdvisorSection({
|
||||
tripAdvisor,
|
||||
}: TripAdvisorSectionProps) {
|
||||
const intl = await getIntl()
|
||||
const { rating, numberOfReviews, reviews } = tripAdvisor
|
||||
const hasTripAdvisorData = !!(rating && numberOfReviews)
|
||||
|
||||
if (!hasTripAdvisorData) {
|
||||
return null
|
||||
}
|
||||
|
||||
const formattedTripAdvisorText = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{count} reviews",
|
||||
},
|
||||
{ count: numberOfReviews }
|
||||
)
|
||||
|
||||
const hasTripAdvisorIframeSrc = !!reviews.widgetScriptEmbedUrlIframe
|
||||
const tripAdvisorHref = hasTripAdvisorIframeSrc
|
||||
? `?s=${SidepeekSlugs.tripAdvisor}`
|
||||
: null
|
||||
|
||||
if (!tripAdvisorHref) {
|
||||
return (
|
||||
<TripAdvisorChip
|
||||
rating={tripAdvisor.rating}
|
||||
color="subtle"
|
||||
wrapper={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.tripAdvisorSection}>
|
||||
<TripAdvisorChip
|
||||
rating={tripAdvisor.rating}
|
||||
color="subtle"
|
||||
wrapper={false}
|
||||
/>
|
||||
<ButtonLink
|
||||
href={tripAdvisorHref}
|
||||
variant="Text"
|
||||
color="Primary"
|
||||
size="Small"
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
wrapping={false}
|
||||
>
|
||||
{formattedTripAdvisorText}
|
||||
<MaterialIcon icon="chevron_right" color="CurrentColor" size={20} />
|
||||
</ButtonLink>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.tripAdvisorSection {
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
import { cx } from "class-variance-authority"
|
||||
|
||||
import { getSingleDecimal } from "@scandic-hotels/common/utils/numberFormatting"
|
||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
import { Country } from "@scandic-hotels/trpc/types/country"
|
||||
|
||||
import LocalCallCharges from "@/components/LocalCallCharges"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import TripAdvisorLink from "./TripAdvisorLink"
|
||||
import TripAdvisorSection from "./TripAdvisorSection"
|
||||
|
||||
import styles from "./introSection.module.css"
|
||||
|
||||
@@ -37,7 +42,7 @@ export default async function IntroSection({
|
||||
phoneNumber,
|
||||
}: IntroSectionProps) {
|
||||
const intl = await getIntl()
|
||||
const { streetAddress, city } = address
|
||||
const { streetAddress, city, country } = address
|
||||
const { distanceToCentre } = location
|
||||
const formattedDistanceText = intl.formatMessage(
|
||||
{
|
||||
@@ -48,6 +53,8 @@ export default async function IntroSection({
|
||||
|
||||
const formattedLocationText = `${streetAddress}, ${city} (${formattedDistanceText})`
|
||||
|
||||
const showLocalCharges = country === Country.Finland
|
||||
|
||||
return (
|
||||
<section className={styles.introSection}>
|
||||
<div className={styles.mainContent}>
|
||||
@@ -63,7 +70,11 @@ export default async function IntroSection({
|
||||
<h1 className={styles.title}>{hotelName}</h1>
|
||||
</Typography>
|
||||
</div>
|
||||
<address className={styles.adressPhoneNumber}>
|
||||
<address
|
||||
className={cx(styles.addressPhoneNumber, {
|
||||
[styles.stacked]: showLocalCharges,
|
||||
})}
|
||||
>
|
||||
<span className={styles.location}>
|
||||
<span className={styles.address}>
|
||||
<MaterialIcon icon="location_on" color="CurrentColor" size={20} />
|
||||
@@ -71,16 +82,35 @@ export default async function IntroSection({
|
||||
<p>{formattedLocationText}</p>
|
||||
</Typography>
|
||||
</span>
|
||||
<Divider variant="vertical" color="Border/Divider/Default" />
|
||||
|
||||
{!showLocalCharges && (
|
||||
<Divider
|
||||
className={styles.divider}
|
||||
variant="vertical"
|
||||
color="Border/Divider/Default"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<Typography variant="Body/Underline/sm">
|
||||
<a href={`tel:${phoneNumber}`} className={styles.phoneNumber}>
|
||||
|
||||
<div className={styles.phoneWrapper}>
|
||||
<Link
|
||||
href={`tel:${phoneNumber}`}
|
||||
size="small"
|
||||
color="Text/Interactive/Secondary"
|
||||
textDecoration="underline"
|
||||
variant="icon"
|
||||
>
|
||||
<MaterialIcon icon="phone" color="CurrentColor" size={20} />
|
||||
{phoneNumber}
|
||||
</a>
|
||||
</Typography>
|
||||
</Link>
|
||||
<LocalCallCharges
|
||||
className={styles.localCharges}
|
||||
country={country}
|
||||
/>
|
||||
</div>
|
||||
</address>
|
||||
{tripAdvisor ? <TripAdvisorLink tripAdvisor={tripAdvisor} /> : null}
|
||||
|
||||
{tripAdvisor ? <TripAdvisorSection tripAdvisor={tripAdvisor} /> : null}
|
||||
</div>
|
||||
<div className={styles.subtitleContent}>
|
||||
<Typography variant="Body/Lead text">
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
max-width: var(--hotel-page-intro-section-width);
|
||||
}
|
||||
|
||||
.localCharges::before {
|
||||
content: "(";
|
||||
}
|
||||
|
||||
.localCharges::after {
|
||||
content: ")";
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
display: grid;
|
||||
gap: var(--Space-x15);
|
||||
@@ -31,20 +39,13 @@
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
.adressPhoneNumber {
|
||||
.addressPhoneNumber {
|
||||
font-style: normal;
|
||||
display: grid;
|
||||
gap: var(--Space-x15);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.phoneNumber {
|
||||
color: var(--Text-Interactive-Default);
|
||||
display: flex;
|
||||
gap: var(--Space-x05);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.address {
|
||||
color: var(--Text-Secondary);
|
||||
gap: var(--Space-x05);
|
||||
@@ -58,14 +59,40 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.phoneWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x15);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 767px) {
|
||||
.adressPhoneNumber {
|
||||
.addressPhoneNumber {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
.addressPhoneNumber.stacked {
|
||||
display: grid;
|
||||
gap: var(--Space-x15);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.address {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.phoneWrapper {
|
||||
flex-direction: row;
|
||||
gap: var(--Space-x1);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,17 @@ import { getIntl } from "@/i18n"
|
||||
|
||||
interface LocalCallChargesProps {
|
||||
country: string
|
||||
className?: string
|
||||
}
|
||||
export default async function LocalCallCharges({
|
||||
country,
|
||||
className,
|
||||
}: LocalCallChargesProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
return country === Country.Finland ? (
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>
|
||||
<p className={className}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Price 0,16 €/min + local call charges",
|
||||
})}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function HotelCardDialogImage({
|
||||
{rating?.tripAdvisor && (
|
||||
<TripAdvisorChip
|
||||
rating={rating.tripAdvisor}
|
||||
variant={position === 'top' ? 'small' : 'default'}
|
||||
size={position === 'top' ? 'small' : 'default'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,36 +7,57 @@ const meta: Meta<typeof TripAdvisorChip> = {
|
||||
component: TripAdvisorChip,
|
||||
argTypes: {
|
||||
rating: {
|
||||
control: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 5,
|
||||
step: 0.1,
|
||||
},
|
||||
control: { type: 'number', min: 0, max: 5, step: 0.1 },
|
||||
},
|
||||
variant: {
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
size: {
|
||||
control: { type: 'select' },
|
||||
options: ['default', 'small'],
|
||||
},
|
||||
color: {
|
||||
control: { type: 'select' },
|
||||
options: ['default', 'subtle'],
|
||||
},
|
||||
wrapper: {
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof TripAdvisorChip>
|
||||
|
||||
export const PrimaryDefault: Story = {
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
rating: 4.5,
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
color: 'default',
|
||||
wrapper: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const WithWrapper: Story = {
|
||||
args: {
|
||||
rating: 4.5,
|
||||
size: 'default',
|
||||
color: 'default',
|
||||
wrapper: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
rating: 4.5,
|
||||
variant: 'small',
|
||||
size: 'small',
|
||||
color: 'default',
|
||||
wrapper: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const Subtle: Story = {
|
||||
args: {
|
||||
rating: 4.5,
|
||||
size: 'default',
|
||||
color: 'subtle',
|
||||
wrapper: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,42 +5,57 @@ import { Typography } from '../Typography'
|
||||
|
||||
const container = cva(styles.container, {
|
||||
variants: {
|
||||
variant: {
|
||||
size: {
|
||||
default: null,
|
||||
small: styles.containerSmall,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
})
|
||||
|
||||
const chip = cva(styles.tripAdvisor, {
|
||||
variants: {
|
||||
variant: {
|
||||
size: {
|
||||
default: null,
|
||||
small: styles.tripAdvisorSmall,
|
||||
},
|
||||
color: {
|
||||
default: null,
|
||||
subtle: styles.tripAdvisorSubtle,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
color: 'default',
|
||||
},
|
||||
})
|
||||
|
||||
type TripAdvisorProps = {
|
||||
rating: number
|
||||
} & VariantProps<typeof container>
|
||||
wrapper?: boolean
|
||||
} & VariantProps<typeof chip>
|
||||
|
||||
export function TripAdvisorChip({ rating, variant }: TripAdvisorProps) {
|
||||
return (
|
||||
// Wrapping the chip in a transparent container with some padding to increase the touch target
|
||||
<div className={container({ variant })}>
|
||||
<div className={chip({ variant })}>
|
||||
<TripadvisorIcon size={16} color="Icon/Interactive/Default" />
|
||||
<Typography variant="Tag/sm">
|
||||
<p>{rating}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
export function TripAdvisorChip({
|
||||
rating,
|
||||
wrapper = true,
|
||||
size,
|
||||
color,
|
||||
}: TripAdvisorProps) {
|
||||
const content = (
|
||||
<div className={chip({ size, color })}>
|
||||
<TripadvisorIcon size={16} color="CurrentColor" />
|
||||
<Typography variant="Tag/sm">
|
||||
<p>{rating}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
|
||||
return wrapper ? (
|
||||
// Wrapping the chip in a transparent container with some padding to increase the touch target
|
||||
<div className={container({ size })}>{content}</div>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
}
|
||||
|
||||
.containerSmall {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: var(--Space-x05);
|
||||
}
|
||||
|
||||
.tripAdvisor {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--Space-x05);
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
color: var(--Text-Interactive-Default);
|
||||
padding: var(--Space-x05) var(--Space-x1);
|
||||
border-radius: var(--Corner-radius-sm);
|
||||
}
|
||||
@@ -22,3 +26,7 @@
|
||||
padding: 0 var(--Space-x05) 0 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.tripAdvisorSubtle {
|
||||
background-color: var(--Surface-Secondary-Subtle, #e3d9d1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user