Merged in refactor-tripadvisor-chip (pull request #3404)

Refactor TripadvisorChip

* feat: create new StaticChip componeny

* refactor tripadvisor chip to use ChipStatic

* refactor: use TripadvisorChip everywhere

* fix: use withChipStatic


Approved-by: Erik Tiekstra
This commit is contained in:
Matilda Landström
2026-01-23 12:19:37 +00:00
parent 5171d2d4d7
commit ae77fa3028
18 changed files with 129 additions and 194 deletions

View File

@@ -14,19 +14,6 @@
position: relative;
}
.tripAdvisor {
position: absolute;
top: var(--Space-x2);
left: var(--Space-x2);
display: flex;
align-items: center;
gap: var(--Space-x05);
background-color: var(--Surface-Primary-Default);
padding: var(--Space-x025) var(--Space-x1);
border-radius: var(--Corner-Radius-sm);
color: var(--Text-Interactive-Default);
}
.content {
padding: 0 var(--Space-x2);
display: grid;

View File

@@ -5,8 +5,8 @@ import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { FacilityToIcon } from "@scandic-hotels/design-system/FacilityToIcon"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
import TripadvisorIcon from "@scandic-hotels/design-system/Icons/TripadvisorIcon"
import ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
@@ -47,12 +47,12 @@ export default function HotelListingItem({
)}
/>
{tripadvisorRating ? (
<Typography variant="Title/Overline/sm">
<div className={styles.tripAdvisor}>
<TripadvisorIcon color="CurrentColor" />
<span>{tripadvisorRating}</span>
</div>
</Typography>
<TripAdvisorChip
color="Neutral"
rating={tripadvisorRating}
wrapper="x2"
size="sm"
/>
) : null}
</div>
<div className={styles.content}>

View File

@@ -25,19 +25,6 @@
height: 200px;
}
.tripAdvisor {
position: absolute;
top: var(--Space-x2);
left: var(--Space-x2);
display: flex;
align-items: center;
gap: var(--Space-x05);
background-color: var(--Surface-Primary-Default);
padding: var(--Space-x025) var(--Space-x1);
border-radius: var(--Corner-Radius-sm);
color: var(--Text-Interactive-Default);
}
.intro {
display: grid;
gap: var(--Space-x05);
@@ -90,11 +77,6 @@
height: 100%;
}
.tripAdvisor {
top: 12px;
left: 12px;
}
.content {
padding: var(--Space-x15);
gap: var(--Space-x1);

View File

@@ -9,8 +9,8 @@ import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { FacilityToIcon } from "@scandic-hotels/design-system/FacilityToIcon"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
import TripadvisorIcon from "@scandic-hotels/design-system/Icons/TripadvisorIcon"
import ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
@@ -80,12 +80,12 @@ export default function HotelListItem({ hotel, url }: HotelListItemProps) {
)}
/>
{hotel.tripadvisor && (
<Typography variant="Title/Overline/sm">
<div className={styles.tripAdvisor}>
<TripadvisorIcon color="CurrentColor" />
<span>{hotel.tripadvisor}</span>
</div>
</Typography>
<TripAdvisorChip
color="Neutral"
rating={hotel.tripadvisor}
wrapper="x2"
size="sm"
/>
)}
</div>
<div className={styles.content}>

View File

@@ -18,19 +18,6 @@
height: 200px;
}
.tripAdvisor {
position: absolute;
top: var(--Space-x2);
left: var(--Space-x2);
display: flex;
align-items: center;
gap: var(--Space-x05);
background-color: var(--Base-Surface-Primary-light-Normal);
padding: var(--Space-x025) var(--Space-x1);
border-radius: var(--Corner-Radius-sm);
color: var(--Text-Interactive-Default);
}
.hotelName {
color: var(--Text-Default);
}

View File

@@ -10,8 +10,8 @@ import { Divider } from "@scandic-hotels/design-system/Divider"
import { FacilityToIcon } from "@scandic-hotels/design-system/FacilityToIcon"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import TripadvisorIcon from "@scandic-hotels/design-system/Icons/TripadvisorIcon"
import ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
@@ -54,12 +54,12 @@ export default function HotelListingItem({
)}
/>
{hotel.tripadvisor && (
<Typography variant="Title/Overline/sm">
<div className={styles.tripAdvisor}>
<TripadvisorIcon color="CurrentColor" />
<span>{hotel.tripadvisor}</span>
</div>
</Typography>
<TripAdvisorChip
color="Neutral"
rating={hotel.tripadvisor}
wrapper="x2"
size="sm"
/>
)}
</div>
<div className={styles.content}>

View File

@@ -9,13 +9,6 @@
object-fit: cover;
}
.imageContainer .tripAdvisor {
position: absolute;
left: 7px;
top: 7px;
border-radius: var(--Corner-Radius-sm);
}
@media screen and (max-width: 500px) {
.imageContainer {
min-width: 120px;

View File

@@ -1,7 +1,6 @@
import { ChipStatic } from "@scandic-hotels/design-system/ChipStatic"
import TripadvisorIcon from "@scandic-hotels/design-system/Icons/TripadvisorIcon"
import Image from "@scandic-hotels/design-system/Image"
import ImageFallback from "@scandic-hotels/design-system/ImageFallback"
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
import styles from "./dialogImage.module.css"
@@ -32,12 +31,12 @@ export default function DialogImage({
/>
)}
{rating ? (
<div className={styles.tripAdvisor}>
<ChipStatic color="Neutral" size="sm" className={styles.tripAdvisor}>
<TripadvisorIcon color="Icon/Interactive/Default" />
{rating}
</ChipStatic>
</div>
<TripAdvisorChip
rating={rating}
color="Neutral"
size="sm"
wrapper="x15"
/>
) : null}
</div>
)

View File

@@ -39,22 +39,12 @@ export default async function TripAdvisorSection({
: null
if (!tripAdvisorHref) {
return (
<TripAdvisorChip
rating={tripAdvisor.rating}
color="subtle"
wrapper={false}
/>
)
return <TripAdvisorChip rating={tripAdvisor.rating} color="Subtle" />
}
return (
<div className={styles.tripAdvisorSection}>
<TripAdvisorChip
rating={tripAdvisor.rating}
color="subtle"
wrapper={false}
/>
<TripAdvisorChip rating={tripAdvisor.rating} color="Subtle" />
<ButtonLink
href={tripAdvisorHref}
variant="Text"

View File

@@ -16,27 +16,27 @@ export const Neutral: Story = {
render: () => (
<div style={{ display: "flex", gap: "16px", alignItems: "center" }}>
<ChipStatic color="Neutral" size="xs" lowerCase>
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
lowerCase xs
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Neutral" size="xs">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
xs
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Neutral" size="sm">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
sm
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Neutral" size="lg">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={20} />
lg
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={20} />
</ChipStatic>
</div>
),
@@ -46,27 +46,27 @@ export const Subtle: Story = {
render: () => (
<div style={{ display: "flex", gap: "16px", alignItems: "center" }}>
<ChipStatic color="Subtle" size="xs" lowerCase>
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
lowerCase xs
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Subtle" size="xs">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
xs
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Subtle" size="sm">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
sm
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Subtle" size="lg">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={20} />
lg
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={20} />
</ChipStatic>
</div>
),
@@ -76,27 +76,27 @@ export const Disabled: Story = {
render: () => (
<div style={{ display: "flex", gap: "16px", alignItems: "center" }}>
<ChipStatic color="Disabled" size="xs" lowerCase>
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
lowerCase xs
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Disabled" size="xs">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
xs
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Disabled" size="sm">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
sm
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={16} />
</ChipStatic>
<ChipStatic color="Disabled" size="lg">
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={20} />
lg
<MaterialIcon icon="lock" />
<MaterialIcon icon="lock" size={20} />
</ChipStatic>
</div>
),

View File

@@ -1 +1,3 @@
export { ChipStatic } from "./ChipStatic"
// eslint-disable-next-line react-refresh/only-export-components
export { variants as chipStaticVariants, withChipStatic } from "./variants"

View File

@@ -2,6 +2,8 @@ import { cva } from "class-variance-authority"
import styles from "./chip-static.module.css"
import { deepmerge } from "deepmerge-ts"
const config = {
variants: {
color: {
@@ -22,3 +24,16 @@ const config = {
} as const
export const variants = cva(styles.chip, config)
const chipConfig = {
variants: {
...config.variants,
},
defaultVariants: {
...config.defaultVariants,
},
} as const
export function withChipStatic<T>(config: T) {
return deepmerge(chipConfig, config)
}

View File

@@ -40,7 +40,9 @@ export function HotelCardDialogImage({
{rating?.tripAdvisor && (
<TripAdvisorChip
rating={rating.tripAdvisor}
size={position === "top" ? "small" : "default"}
color="Neutral"
size={position === "top" ? "xs" : "sm"}
wrapper={position === "top" ? "x05" : "x15"}
/>
)}
</div>

View File

@@ -216,7 +216,12 @@ export const HotelCardComponent = memo(
sizes="(min-width: 768px) calc(100vw - 340px), (min-width: 1367px) 33vw, 100vw"
/>
{hotel.ratings?.tripAdvisor && (
<TripAdvisorChip rating={hotel.ratings.tripAdvisor} />
<TripAdvisorChip
rating={hotel.ratings.tripAdvisor}
color="Neutral"
size="sm"
wrapper="x2"
/>
)}
</div>
</div>

View File

@@ -59,7 +59,12 @@ export function HotelInfoCard({
<div className={styles.imageWrapper}>
<ImageGallery title={hotel.name} images={galleryImages} fill />
{hotel.ratings?.tripAdvisor && (
<TripAdvisorChip rating={hotel.ratings.tripAdvisor.rating} />
<TripAdvisorChip
rating={hotel.ratings.tripAdvisor.rating}
color="Neutral"
size="sm"
wrapper="x2"
/>
)}
</div>
<div className={styles.hotelContent}>

View File

@@ -9,16 +9,10 @@ const meta: Meta<typeof TripAdvisorChip> = {
rating: {
control: { type: "number", min: 0, max: 5, step: 0.1 },
},
size: {
control: { type: "select" },
options: ["default", "small"],
},
color: {
control: { type: "select" },
options: ["default", "subtle"],
},
wrapper: {
control: { type: "boolean" },
control: { type: "select" },
options: ["x05", "x15", "x2"],
},
},
}
@@ -29,35 +23,40 @@ type Story = StoryObj<typeof TripAdvisorChip>
export const Default: Story = {
args: {
rating: 4.5,
size: "default",
color: "default",
wrapper: false,
},
}
export const WithWrapper: Story = {
export const WithSmallWrapper: Story = {
args: {
rating: 4.5,
size: "default",
color: "default",
wrapper: true,
wrapper: "x05",
},
}
export const WithMediumWrapper: Story = {
args: {
rating: 4.5,
wrapper: "x15",
},
}
export const WithLargeWrapper: Story = {
args: {
rating: 4.5,
wrapper: "x2",
},
}
export const Small: Story = {
args: {
rating: 4.5,
size: "small",
color: "default",
wrapper: true,
size: "sm",
},
}
export const Subtle: Story = {
args: {
rating: 4.5,
size: "default",
color: "subtle",
wrapper: false,
color: "Subtle",
},
}

View File

@@ -1,60 +1,44 @@
import { cva, type VariantProps } from "class-variance-authority"
import TripadvisorIcon from "../Icons/Customised/Socials/Tripadvisor"
import styles from "./tripAdvisorChip.module.css"
import { Typography } from "../Typography"
import { ChipStatic, withChipStatic } from "../ChipStatic"
const container = cva(styles.container, {
variants: {
size: {
default: null,
small: styles.containerSmall,
wrapper: {
x05: styles["padding-x05"],
x15: styles["padding-x15"],
x2: styles["padding-x2"],
},
},
defaultVariants: {
size: "default",
wrapper: undefined,
},
})
const chip = cva(styles.tripAdvisor, {
variants: {
size: {
default: null,
small: styles.tripAdvisorSmall,
},
color: {
default: null,
subtle: styles.tripAdvisorSubtle,
},
},
defaultVariants: {
size: "default",
color: "default",
},
})
const _chipVariant = cva("", withChipStatic({}))
type TripAdvisorProps = {
interface TripAdvisorProps
extends VariantProps<typeof container>, VariantProps<typeof _chipVariant> {
rating: number
wrapper?: boolean
} & VariantProps<typeof chip>
}
export function TripAdvisorChip({
rating,
wrapper = true,
size,
color,
wrapper,
size = "sm",
color = "Subtle",
}: TripAdvisorProps) {
const content = (
<div className={chip({ size, color })}>
<ChipStatic size={size} color={color}>
<TripadvisorIcon size={16} color="CurrentColor" />
<Typography variant="Tag/sm">
<p>{rating}</p>
</Typography>
</div>
{rating}
</ChipStatic>
)
return wrapper ? (
// Wrapping the chip in a transparent container with some padding to increase the touch target
<div className={container({ size })}>{content}</div>
<div className={container({ wrapper })}>{content}</div>
) : (
content
)

View File

@@ -2,31 +2,16 @@
position: absolute;
left: 0;
top: 0;
padding: var(--Space-x2);
}
.containerSmall {
position: absolute;
left: 0;
top: 0;
.padding-x05 {
padding: var(--Space-x05);
}
.tripAdvisor {
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);
.padding-x15 {
padding: var(--Space-x15);
}
.tripAdvisorSmall {
padding: 0 var(--Space-x05) 0 3px;
border-radius: 2px;
}
.tripAdvisorSubtle {
background-color: var(--Surface-Secondary-Subtle, #e3d9d1);
.padding-x2 {
padding: var(--Space-x2);
}