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; 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 { .content {
padding: 0 var(--Space-x2); padding: 0 var(--Space-x2);
display: grid; 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 { Divider } from "@scandic-hotels/design-system/Divider"
import { FacilityToIcon } from "@scandic-hotels/design-system/FacilityToIcon" import { FacilityToIcon } from "@scandic-hotels/design-system/FacilityToIcon"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon" 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 ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
@@ -47,12 +47,12 @@ export default function HotelListingItem({
)} )}
/> />
{tripadvisorRating ? ( {tripadvisorRating ? (
<Typography variant="Title/Overline/sm"> <TripAdvisorChip
<div className={styles.tripAdvisor}> color="Neutral"
<TripadvisorIcon color="CurrentColor" /> rating={tripadvisorRating}
<span>{tripadvisorRating}</span> wrapper="x2"
</div> size="sm"
</Typography> />
) : null} ) : null}
</div> </div>
<div className={styles.content}> <div className={styles.content}>

View File

@@ -25,19 +25,6 @@
height: 200px; 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 { .intro {
display: grid; display: grid;
gap: var(--Space-x05); gap: var(--Space-x05);
@@ -90,11 +77,6 @@
height: 100%; height: 100%;
} }
.tripAdvisor {
top: 12px;
left: 12px;
}
.content { .content {
padding: var(--Space-x15); padding: var(--Space-x15);
gap: var(--Space-x1); 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 { Divider } from "@scandic-hotels/design-system/Divider"
import { FacilityToIcon } from "@scandic-hotels/design-system/FacilityToIcon" import { FacilityToIcon } from "@scandic-hotels/design-system/FacilityToIcon"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon" 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 ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map" import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
@@ -80,12 +80,12 @@ export default function HotelListItem({ hotel, url }: HotelListItemProps) {
)} )}
/> />
{hotel.tripadvisor && ( {hotel.tripadvisor && (
<Typography variant="Title/Overline/sm"> <TripAdvisorChip
<div className={styles.tripAdvisor}> color="Neutral"
<TripadvisorIcon color="CurrentColor" /> rating={hotel.tripadvisor}
<span>{hotel.tripadvisor}</span> wrapper="x2"
</div> size="sm"
</Typography> />
)} )}
</div> </div>
<div className={styles.content}> <div className={styles.content}>

View File

@@ -18,19 +18,6 @@
height: 200px; 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 { .hotelName {
color: var(--Text-Default); 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 { FacilityToIcon } from "@scandic-hotels/design-system/FacilityToIcon"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon" import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" 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 ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map" import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
@@ -54,12 +54,12 @@ export default function HotelListingItem({
)} )}
/> />
{hotel.tripadvisor && ( {hotel.tripadvisor && (
<Typography variant="Title/Overline/sm"> <TripAdvisorChip
<div className={styles.tripAdvisor}> color="Neutral"
<TripadvisorIcon color="CurrentColor" /> rating={hotel.tripadvisor}
<span>{hotel.tripadvisor}</span> wrapper="x2"
</div> size="sm"
</Typography> />
)} )}
</div> </div>
<div className={styles.content}> <div className={styles.content}>

View File

@@ -9,13 +9,6 @@
object-fit: cover; object-fit: cover;
} }
.imageContainer .tripAdvisor {
position: absolute;
left: 7px;
top: 7px;
border-radius: var(--Corner-Radius-sm);
}
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
.imageContainer { .imageContainer {
min-width: 120px; 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 Image from "@scandic-hotels/design-system/Image"
import ImageFallback from "@scandic-hotels/design-system/ImageFallback" import ImageFallback from "@scandic-hotels/design-system/ImageFallback"
import { TripAdvisorChip } from "@scandic-hotels/design-system/TripAdvisorChip"
import styles from "./dialogImage.module.css" import styles from "./dialogImage.module.css"
@@ -32,12 +31,12 @@ export default function DialogImage({
/> />
)} )}
{rating ? ( {rating ? (
<div className={styles.tripAdvisor}> <TripAdvisorChip
<ChipStatic color="Neutral" size="sm" className={styles.tripAdvisor}> rating={rating}
<TripadvisorIcon color="Icon/Interactive/Default" /> color="Neutral"
{rating} size="sm"
</ChipStatic> wrapper="x15"
</div> />
) : null} ) : null}
</div> </div>
) )

View File

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

View File

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

View File

@@ -1 +1,3 @@
export { ChipStatic } from "./ChipStatic" 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 styles from "./chip-static.module.css"
import { deepmerge } from "deepmerge-ts"
const config = { const config = {
variants: { variants: {
color: { color: {
@@ -22,3 +24,16 @@ const config = {
} as const } as const
export const variants = cva(styles.chip, config) 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 && ( {rating?.tripAdvisor && (
<TripAdvisorChip <TripAdvisorChip
rating={rating.tripAdvisor} rating={rating.tripAdvisor}
size={position === "top" ? "small" : "default"} color="Neutral"
size={position === "top" ? "xs" : "sm"}
wrapper={position === "top" ? "x05" : "x15"}
/> />
)} )}
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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