chore(BOOK-754): Moved ContentCard to design system and added stories

Approved-by: Bianca Widstam
Approved-by: Anton Gunnarsson
This commit is contained in:
Erik Tiekstra
2026-01-20 12:37:22 +00:00
parent 510f25a812
commit d7eed5b318
12 changed files with 172 additions and 172 deletions

View File

@@ -1,3 +1,4 @@
import { ContentCard } from "@scandic-hotels/design-system/ContentCard"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { Carousel } from "@/components/Carousel"
@@ -8,7 +9,6 @@ import {
CarouselNext,
CarouselPrevious,
} from "@/components/Carousel/CarouselNavigation"
import { ContentCard } from "@/components/ContentCard"
import styles from "./allCampaigns.module.css"

View File

@@ -2,8 +2,9 @@
import { useState } from "react"
import { ContentCard } from "@scandic-hotels/design-system/ContentCard"
import { Carousel } from "@/components/Carousel"
import { ContentCard } from "@/components/ContentCard"
import { Section } from "@/components/Section"
import { SectionHeader } from "@/components/Section/Header"
import SectionLink from "@/components/Section/Link"

View File

@@ -2,8 +2,9 @@
import { useState } from "react"
import { ContentCard } from "@scandic-hotels/design-system/ContentCard"
import { Carousel } from "@/components/Carousel"
import { ContentCard } from "@/components/ContentCard"
import { Section } from "@/components/Section"
import { SectionHeader } from "@/components/Section/Header"
import SectionLink from "@/components/Section/Link"

View File

@@ -2,7 +2,7 @@
import { cx } from "class-variance-authority"
import { ContentCard } from "@/components/ContentCard"
import { ContentCard } from "@scandic-hotels/design-system/ContentCard"
import styles from "./campaignCardList.module.css"

View File

@@ -1,4 +1,5 @@
import { getTheme } from "@scandic-hotels/common/utils/theme/serverContext"
import { ContentCard } from "@scandic-hotels/design-system/ContentCard"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { Carousel } from "@/components/Carousel"
@@ -9,7 +10,6 @@ import {
CarouselNext,
CarouselPrevious,
} from "@/components/Carousel/CarouselNavigation"
import { ContentCard } from "@/components/ContentCard"
import CampaignHero from "@/components/ContentType/CampaignPage/Hero"
import CampaignCardList from "@/components/ContentType/HotelPage/Campaigns/CampaignCardList"
import { getIntl } from "@/i18n"

View File

@@ -1,160 +0,0 @@
import type { Meta, StoryObj } from "@storybook/nextjs-vite"
import { fn } from "storybook/test"
import { themes } from "../../../../.storybook/preview"
import { Card } from "../"
import { Button } from "../../Button"
import { Typography } from "../../Typography"
type CompositionProps = React.ComponentPropsWithoutRef<typeof Card> & {
_onPrimaryPress?: () => void
_onSecondaryPress?: () => void
inMainArea: boolean
showTitle: boolean
showPreamble: boolean
showPrimaryButton: boolean
showSecondaryButton: boolean
}
const meta: Meta<CompositionProps> = {
title: "Compositions/Card",
component: Card,
decorators: [
(Story, context) => {
if (context.name.toLowerCase().indexOf("all themes") >= 0) {
return (
<>
<h1>{context.name}</h1>
{Object.entries(themes.themes).map(([key, value], ix) => {
return (
<div key={ix} className={value} style={{ padding: "1em 0" }}>
<h2 style={{ paddingBottom: "0.5em" }}>{key}</h2>
<Story />
</div>
)
})}
</>
)
}
return (
<div style={{ display: "flex" }}>
<Story />
</div>
)
},
],
argTypes: {
inMainArea: {
name: "Is in main area",
},
showTitle: {
name: "Composition: Show title",
},
showPreamble: {
name: "Composition: Show preamble",
},
showPrimaryButton: {
name: "Composition: Show primary button",
},
showSecondaryButton: {
name: "Composition: Show secondary button",
},
_onPrimaryPress: {
table: {
disable: true,
},
},
_onSecondaryPress: {
table: {
disable: true,
},
},
},
render: ({
inMainArea,
showTitle,
showPreamble,
showPrimaryButton,
showSecondaryButton,
...args
}) => (
<Card {...args}>
{showTitle && (
<Typography variant="Title/md">
<h3>Content Card</h3>
</Typography>
)}
{showPreamble && (
<Typography variant="Body/Paragraph/mdRegular">
<p>
Mattis sit duis pulvinar ultricies auctor euismod. Augue mattis
mauris at est iaculis pulvinar pulvinar.
</p>
</Typography>
)}
{showPrimaryButton && inMainArea && (
<Button size="lg" variant="Primary" onPress={args._onPrimaryPress}>
Primary action
</Button>
)}
{showPrimaryButton && !inMainArea && (
<Button size="sm" variant="Tertiary" onPress={args._onPrimaryPress}>
Primary action
</Button>
)}
{showSecondaryButton && (
<Button
size={inMainArea ? "lg" : "sm"}
variant="Secondary"
onPress={args._onSecondaryPress}
>
Secondary action
</Button>
)}
</Card>
),
}
export default meta
type Story = StoryObj<CompositionProps>
export const ContentCardMainArea: Story = {
args: {
as: "Default",
inMainArea: true,
showTitle: true,
showPreamble: true,
showPrimaryButton: true,
showSecondaryButton: true,
_onPrimaryPress: fn(),
_onSecondaryPress: fn(),
},
}
export const ContentCardNonMainArea: Story = {
args: {
as: "Default",
inMainArea: false,
showTitle: true,
showPreamble: true,
showPrimaryButton: true,
showSecondaryButton: true,
_onPrimaryPress: fn(),
_onSecondaryPress: fn(),
},
}
export const ContentCardMainAreaAllThemes: Story = {
...ContentCardMainArea,
}
export const ContentCardNonMainAreaAllThemes: Story = {
...ContentCardNonMainArea,
}

View File

@@ -2,18 +2,47 @@ import type { Meta, StoryObj } from "@storybook/nextjs-vite"
import { fn } from "storybook/test"
import { MaterialIcon } from "../Icons/MaterialIcon/index.tsx"
import { ChipButton } from "./ChipButton.tsx"
import { config as chipButtonConfig } from "./variants"
import { MaterialIcon } from "../Icons/MaterialIcon/index.tsx"
const meta: Meta<typeof ChipButton> = {
title: "Core Components/ChipButton",
component: ChipButton,
argTypes: {
children: {
table: {
disable: true,
},
},
variant: {
control: "select",
type: "string",
options: Object.keys(chipButtonConfig.variants.variant),
table: {
type: {
summary: Object.keys(chipButtonConfig.variants.variant).join(" | "),
},
defaultValue: { summary: chipButtonConfig.defaultVariants.variant },
},
},
size: {
control: "select",
options: Object.keys(chipButtonConfig.variants.size),
table: {
type: {
summary: Object.keys(chipButtonConfig.variants.size).join(" | "),
},
defaultValue: { summary: "Large" },
},
description:
"Sets the size of the ChipButton component. This only affects the `FilterRounded` variant.",
},
selected: {
control: "boolean",
table: {
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
onPress: {
table: {

View File

@@ -0,0 +1,127 @@
import type { Meta, StoryObj } from "@storybook/nextjs-vite"
import { ContentCard } from "./ContentCard.tsx"
const IMAGE = {
id: 1,
url: "./img/img2.jpg",
title: "Placeholder image",
meta: {
alt: "Placeholder image",
caption: "This is a placeholder image",
},
focalPoint: { x: 50, y: 50 },
dimensions: { width: 1920, height: 1189, aspectRatio: 1.61 },
}
const IMAGE_2 = {
...IMAGE,
id: 2,
url: "./img/img3.jpg",
dimensions: { width: 1920, height: 2880, aspectRatio: 0.67 },
}
const DEFAULT_ARGS = {
heading: "Lorem ipsum",
bodyText:
"Dolor sit amet, consectetur adipiscing elit. Curabitur vitae neque non ipsum efficitur hendrerit at ut nulla.",
link: {
href: "#",
text: "Learn more",
},
image: IMAGE,
}
const meta: Meta<typeof ContentCard> = {
title: "Core Components/Cards/ContentCard",
parameters: {
docs: {
description: {
component:
"The card itself does not have a maximum width, but it will adapt to the width of its container. The card is mostly used together with other content cards. It is recommended to use the ContentCard inside a grid or a container with a set maximum width for best results.",
},
},
},
component: ContentCard,
argTypes: {
heading: {
control: "text",
table: {
type: { summary: "string" },
},
},
bodyText: {
control: "text",
table: {
type: { summary: "string" },
},
},
promoText: {
control: "text",
table: {
type: { summary: "string | undefined" },
},
},
link: {
control: "object",
description:
"The link of where the card should navigate to. The whole card is clickable.",
table: {
type: {
summary:
"{href: string, openInNewTab?: boolean, isExternal?: boolean}",
},
},
},
image: {
control: "object",
table: {
type: {
summary: "ImageVaultAsset",
detail:
"{ id: number, url: string, meta: {alt?: string | null, caption?: string | null}, focalPoint: { x: number, y: number }, dimensions: { width: number, height: number, aspectRatio: number } }",
},
},
},
},
args: { ...DEFAULT_ARGS },
globals: {
backgrounds: { default: "storybookLight" },
},
}
export default meta
type Story = StoryObj<typeof ContentCard>
export const Default: Story = {
args: {
...meta.args,
},
}
export const WithPromoText: Story = {
args: {
...meta.args,
promoText: "Popular choice",
},
}
export const MultipleCards: Story = {
args: {
...meta.args,
},
render: (args) => (
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: "16px",
width: "min(800px, 100%)",
margin: "0 auto",
}}
>
<ContentCard {...args} promoText="Popular choice" />
<ContentCard {...args} image={IMAGE_2} />
</div>
),
}

View File

@@ -4,13 +4,12 @@ import { cx } from "class-variance-authority"
import NextLink from "next/link"
import { useIntl } from "react-intl"
import { ChipStatic } from "@scandic-hotels/design-system/ChipStatic"
import Image from "@scandic-hotels/design-system/Image"
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./contentCard.module.css"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import { ChipStatic } from "../ChipStatic"
import Image from "../Image"
import { Typography } from "../Typography"
interface ContentCardProps {
link?: {
@@ -30,10 +29,11 @@ export function ContentCard({
image,
bodyText,
promoText,
className = "",
className,
link,
}: ContentCardProps) {
const intl = useIntl()
const card = (
<article className={cx(styles.card, className)}>
<div className={styles.imageContainer}>

View File

@@ -0,0 +1 @@
export { ContentCard } from "./ContentCard"

View File

@@ -20,6 +20,7 @@
"./Chips": "./lib/components/Chips/index.tsx",
"./ChipStatic": "./lib/components/ChipStatic/index.tsx",
"./CodeRateCard": "./lib/components/RateCard/Code/index.tsx",
"./ContentCard": "./lib/components/ContentCard/index.tsx",
"./DeprecatedSelect": "./lib/components/DeprecatedSelect/index.tsx",
"./Divider": "./lib/components/Divider/index.tsx",
"./FacilityToIcon": "./lib/components/FacilityToIcon/index.tsx",