feat(BOOK-757): Moved TeaserCard to design system and added stories

Approved-by: Bianca Widstam
This commit is contained in:
Erik Tiekstra
2026-01-21 07:50:43 +00:00
parent 8a143a2916
commit b91504e7c6
18 changed files with 465 additions and 107 deletions

View File

@@ -1,3 +1,4 @@
import { TeaserCard } from "@scandic-hotels/design-system/TeaserCard"
import {
CardsGridEnum,
CardsGridLayoutEnum,
@@ -9,7 +10,6 @@ import { SectionHeader } from "@/components/Section/Header"
import Card from "@/components/TempDesignSystem/Card"
import Grids from "@/components/TempDesignSystem/Grids"
import LoyaltyCard from "@/components/TempDesignSystem/LoyaltyCard"
import TeaserCard from "@/components/TempDesignSystem/TeaserCard"
import type { CardsGrid as CardsGridBlock } from "@scandic-hotels/trpc/types/blocks"
import type { VariantProps } from "class-variance-authority"
@@ -89,8 +89,8 @@ export default function CardsGrid({
return (
<TeaserCard
key={card.system.uid}
title={card.heading}
description={card.body_text}
heading={card.heading}
bodyText={card.body_text}
primaryButton={card.primaryButton}
secondaryButton={card.secondaryButton}
sidePeekButton={card.sidePeekButton}

View File

@@ -1,4 +1,5 @@
import { JsonToHtml } from "@scandic-hotels/design-system/JsonToHtml"
import { TeaserCard } from "@scandic-hotels/design-system/TeaserCard"
import { DynamicContentEnum } from "@scandic-hotels/trpc/types/dynamicContent"
import { SidebarEnums } from "@scandic-hotels/trpc/types/sidebar"
@@ -7,7 +8,6 @@ import EmployeeBenefitsAuthCard from "@/components/DigitalTeamMemberCard/Employe
import ShortcutsList from "../Blocks/ShortcutsList"
import Card from "../TempDesignSystem/Card"
import TeaserCard from "../TempDesignSystem/TeaserCard"
import JoinLoyaltyContact from "./JoinLoyalty"
import styles from "./sidebar.module.css"
@@ -71,9 +71,9 @@ export default function Sidebar({ blocks }: SidebarProps) {
return (
<TeaserCard
key={block.teaser_card.system.uid}
title={block.teaser_card.heading}
description={block.teaser_card.body_text}
intent={block.teaser_card.theme}
heading={block.teaser_card.heading}
bodyText={block.teaser_card.body_text}
style={block.teaser_card.theme}
primaryButton={block.teaser_card.primaryButton}
secondaryButton={block.teaser_card.secondaryButton}
sidePeekButton={block.teaser_card.sidePeekButton}

View File

@@ -1,78 +0,0 @@
"use client"
import { useState } from "react"
import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { JsonToHtml } from "@scandic-hotels/design-system/JsonToHtml"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import styles from "./sidepeek.module.css"
import type { TeaserCardSidepeekProps } from "@/types/components/teaserCard"
export default function TeaserCardSidepeek({
button,
sidePeekContent,
}: TeaserCardSidepeekProps) {
const intl = useIntl()
const [sidePeekIsOpen, setSidePeekIsOpen] = useState(false)
const { heading, content, primary_button, secondary_button } = sidePeekContent
return (
<div className={styles.teaserCardSidepeek}>
<Button
onPress={() => setSidePeekIsOpen(true)}
variant="Secondary"
color="Primary"
size="sm"
>
{button.call_to_action_text}
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" />
</Button>
<SidePeek
title={heading}
isOpen={sidePeekIsOpen}
handleClose={() => setSidePeekIsOpen(false)}
openInRoot
closeLabel={intl.formatMessage({
id: "common.close",
defaultMessage: "Close",
})}
>
{content ? (
<JsonToHtml
nodes={content.json.children}
embeds={content.embedded_itemsConnection.edges}
/>
) : null}
<div className={styles.ctaContainer}>
{primary_button && (
<ButtonLink
variant="Primary"
color="Primary"
size="sm"
href={primary_button.href}
target={primary_button.openInNewTab ? "_blank" : undefined}
>
{primary_button.title}
</ButtonLink>
)}
{secondary_button && (
<ButtonLink
variant="Secondary"
color="Primary"
size="sm"
href={secondary_button.href}
target={secondary_button.openInNewTab ? "_blank" : undefined}
>
{secondary_button.title}
</ButtonLink>
)}
</div>
</SidePeek>
</div>
)
}

View File

@@ -1,8 +0,0 @@
.teaserCardSidepeek {
display: grid;
}
.ctaContainer {
display: grid;
gap: var(--Space-x2);
}

View File

@@ -1,84 +0,0 @@
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import Image from "@scandic-hotels/design-system/Image"
import { Typography } from "@scandic-hotels/design-system/Typography"
import TeaserCardSidepeek from "./Sidepeek"
import { teaserCardVariants } from "./variants"
import styles from "./teaserCard.module.css"
import type { TeaserCardProps } from "@/types/components/teaserCard"
export default function TeaserCard({
title,
description,
primaryButton,
secondaryButton,
sidePeekButton,
sidePeekContent,
image,
intent,
alwaysStack = false,
className,
}: TeaserCardProps) {
const classNames = teaserCardVariants({ intent, alwaysStack, className })
return (
<article className={classNames}>
{image && (
<div className={styles.imageContainer}>
<Image
src={image.url}
alt={image.meta?.alt || ""}
className={styles.image}
focalPoint={image.focalPoint}
dimensions={image.dimensions}
fill
/>
</div>
)}
<div className={styles.content}>
<Typography variant="Title/Subtitle/md">
<p>{title}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>{description}</p>
</Typography>
{sidePeekButton && sidePeekContent ? (
<TeaserCardSidepeek
button={sidePeekButton}
sidePeekContent={sidePeekContent}
/>
) : (
<div className={styles.ctaContainer}>
{primaryButton && (
<ButtonLink
variant="Tertiary"
color="Primary"
size="sm"
className={styles.ctaButton}
href={primaryButton.href}
target={primaryButton.openInNewTab ? "_blank" : undefined}
>
{primaryButton.title}
</ButtonLink>
)}
{secondaryButton && (
<ButtonLink
variant="Secondary"
color="Primary"
size="sm"
className={styles.ctaButton}
href={secondaryButton.href}
target={secondaryButton.openInNewTab ? "_blank" : undefined}
>
{secondaryButton.title}
</ButtonLink>
)}
</div>
)}
</div>
</article>
)
}

View File

@@ -1,61 +0,0 @@
.card {
border-radius: var(--Corner-radius-md);
display: flex;
flex-direction: column;
overflow: hidden;
}
.imageContainer {
width: 100%;
height: 200px;
position: relative;
}
.default {
background-color: var(--Base-Surface-Subtle-Normal);
}
.featured {
background-color: var(--Main-Grey-White);
}
.default,
.featured {
border: 1px solid var(--Base-Border-Subtle);
}
.image {
width: 100%;
height: 12.5rem; /* 200px */
}
.content {
display: grid;
gap: var(--Space-x15);
padding: var(--Space-x2) var(--Space-x3);
grid-template-rows: auto 1fr auto;
flex-grow: 1;
color: var(--Main-Grey-100);
}
.description {
color: var(--Base-Text-Medium-contrast);
}
.ctaContainer {
display: grid;
grid-template-columns: 1fr;
gap: var(--Space-x1);
width: 100%;
}
@media (min-width: 1367px) {
.card:not(.alwaysStack) .ctaContainer {
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}
.card:not(.alwaysStack) .ctaContainer:has(:only-child) {
grid-template-columns: 1fr;
}
}

View File

@@ -1,20 +0,0 @@
import { cva } from "class-variance-authority"
import styles from "./teaserCard.module.css"
export const teaserCardVariants = cva(styles.card, {
variants: {
intent: {
default: styles.default,
featured: styles.featured,
},
alwaysStack: {
true: styles.alwaysStack,
false: "",
},
},
defaultVariants: {
intent: "default",
alwaysStack: false,
},
})

View File

@@ -1,27 +0,0 @@
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { TeaserCard } from "@scandic-hotels/trpc/types/blocks"
import type { VariantProps } from "class-variance-authority"
import type { CardProps } from "@/components/TempDesignSystem/Card/card"
import type { teaserCardVariants } from "@/components/TempDesignSystem/TeaserCard/variants"
interface SidePeekButton {
call_to_action_text: string
}
export interface TeaserCardProps
extends VariantProps<typeof teaserCardVariants> {
title: string
description: string
primaryButton?: CardProps["primaryButton"]
secondaryButton?: CardProps["secondaryButton"]
sidePeekButton?: SidePeekButton
sidePeekContent?: TeaserCard["sidepeek_content"]
image?: ImageVaultAsset
className?: string
}
export interface TeaserCardSidepeekProps {
button: SidePeekButton
sidePeekContent: NonNullable<TeaserCard["sidepeek_content"]>
}