feat(BOOK-757): Moved TeaserCard to design system and added stories
Approved-by: Bianca Widstam
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { TeaserCard } from "@scandic-hotels/design-system/TeaserCard"
|
||||||
import {
|
import {
|
||||||
CardsGridEnum,
|
CardsGridEnum,
|
||||||
CardsGridLayoutEnum,
|
CardsGridLayoutEnum,
|
||||||
@@ -9,7 +10,6 @@ import { SectionHeader } from "@/components/Section/Header"
|
|||||||
import Card from "@/components/TempDesignSystem/Card"
|
import Card from "@/components/TempDesignSystem/Card"
|
||||||
import Grids from "@/components/TempDesignSystem/Grids"
|
import Grids from "@/components/TempDesignSystem/Grids"
|
||||||
import LoyaltyCard from "@/components/TempDesignSystem/LoyaltyCard"
|
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 { CardsGrid as CardsGridBlock } from "@scandic-hotels/trpc/types/blocks"
|
||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority"
|
||||||
@@ -89,8 +89,8 @@ export default function CardsGrid({
|
|||||||
return (
|
return (
|
||||||
<TeaserCard
|
<TeaserCard
|
||||||
key={card.system.uid}
|
key={card.system.uid}
|
||||||
title={card.heading}
|
heading={card.heading}
|
||||||
description={card.body_text}
|
bodyText={card.body_text}
|
||||||
primaryButton={card.primaryButton}
|
primaryButton={card.primaryButton}
|
||||||
secondaryButton={card.secondaryButton}
|
secondaryButton={card.secondaryButton}
|
||||||
sidePeekButton={card.sidePeekButton}
|
sidePeekButton={card.sidePeekButton}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { JsonToHtml } from "@scandic-hotels/design-system/JsonToHtml"
|
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 { DynamicContentEnum } from "@scandic-hotels/trpc/types/dynamicContent"
|
||||||
import { SidebarEnums } from "@scandic-hotels/trpc/types/sidebar"
|
import { SidebarEnums } from "@scandic-hotels/trpc/types/sidebar"
|
||||||
|
|
||||||
@@ -7,7 +8,6 @@ import EmployeeBenefitsAuthCard from "@/components/DigitalTeamMemberCard/Employe
|
|||||||
|
|
||||||
import ShortcutsList from "../Blocks/ShortcutsList"
|
import ShortcutsList from "../Blocks/ShortcutsList"
|
||||||
import Card from "../TempDesignSystem/Card"
|
import Card from "../TempDesignSystem/Card"
|
||||||
import TeaserCard from "../TempDesignSystem/TeaserCard"
|
|
||||||
import JoinLoyaltyContact from "./JoinLoyalty"
|
import JoinLoyaltyContact from "./JoinLoyalty"
|
||||||
|
|
||||||
import styles from "./sidebar.module.css"
|
import styles from "./sidebar.module.css"
|
||||||
@@ -71,9 +71,9 @@ export default function Sidebar({ blocks }: SidebarProps) {
|
|||||||
return (
|
return (
|
||||||
<TeaserCard
|
<TeaserCard
|
||||||
key={block.teaser_card.system.uid}
|
key={block.teaser_card.system.uid}
|
||||||
title={block.teaser_card.heading}
|
heading={block.teaser_card.heading}
|
||||||
description={block.teaser_card.body_text}
|
bodyText={block.teaser_card.body_text}
|
||||||
intent={block.teaser_card.theme}
|
style={block.teaser_card.theme}
|
||||||
primaryButton={block.teaser_card.primaryButton}
|
primaryButton={block.teaser_card.primaryButton}
|
||||||
secondaryButton={block.teaser_card.secondaryButton}
|
secondaryButton={block.teaser_card.secondaryButton}
|
||||||
sidePeekButton={block.teaser_card.sidePeekButton}
|
sidePeekButton={block.teaser_card.sidePeekButton}
|
||||||
|
|||||||
@@ -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"]>
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,3 @@ export const AlertVisibleOnEnum = {
|
|||||||
WEB: "WEB",
|
WEB: "WEB",
|
||||||
APP: "APP",
|
APP: "APP",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type SidepeekContent = {
|
|
||||||
heading: string
|
|
||||||
content: {
|
|
||||||
json?: any
|
|
||||||
embedded_itemsConnection: {
|
|
||||||
edges: any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"./constants/rateType": "./constants/rateType.ts",
|
"./constants/rateType": "./constants/rateType.ts",
|
||||||
"./constants/routes/*": "./constants/routes/*.ts",
|
"./constants/routes/*": "./constants/routes/*.ts",
|
||||||
"./constants/sessionKeys": "./constants/sessionKeys.ts",
|
"./constants/sessionKeys": "./constants/sessionKeys.ts",
|
||||||
|
"./constants/sidepeekContent": "./constants/sidepeekContent.ts",
|
||||||
"./constants/signatureHotels": "./constants/signatureHotels.ts",
|
"./constants/signatureHotels": "./constants/signatureHotels.ts",
|
||||||
"./constants/transactionType": "./constants/transactionType.ts",
|
"./constants/transactionType": "./constants/transactionType.ts",
|
||||||
"./dataCache": "./dataCache/index.ts",
|
"./dataCache": "./dataCache/index.ts",
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ export default function AlertSidepeek({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<JsonToHtml
|
<JsonToHtml
|
||||||
nodes={content.json.children}
|
nodes={content?.json.children}
|
||||||
embeds={content.embedded_itemsConnection.edges}
|
embeds={content?.embedded_itemsConnection.edges}
|
||||||
/>
|
/>
|
||||||
</SidePeek>
|
</SidePeek>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { SidepeekContent } from "@scandic-hotels/common/constants/alert"
|
import { AlertSidepeekContent } from "../../../types/sidepeekContent"
|
||||||
|
|
||||||
export interface AlertSidepeekProps {
|
export interface AlertSidepeekProps {
|
||||||
ctaText: string
|
ctaText: string
|
||||||
sidePeekContent: NonNullable<SidepeekContent>
|
sidePeekContent: NonNullable<AlertSidepeekContent>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import type {
|
import type { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
|
||||||
AlertTypeEnum,
|
|
||||||
SidepeekContent,
|
|
||||||
} from "@scandic-hotels/common/constants/alert"
|
|
||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority"
|
||||||
import type { AriaRole, ReactNode } from "react"
|
import type { AriaRole, ReactNode } from "react"
|
||||||
|
|
||||||
|
import { AlertSidepeekContent } from "../../types/sidepeekContent"
|
||||||
import type { alertVariants } from "./variants"
|
import type { alertVariants } from "./variants"
|
||||||
|
|
||||||
export interface AlertProps extends VariantProps<typeof alertVariants> {
|
export interface AlertProps extends VariantProps<typeof alertVariants> {
|
||||||
@@ -17,7 +15,7 @@ export interface AlertProps extends VariantProps<typeof alertVariants> {
|
|||||||
phoneNumber?: string
|
phoneNumber?: string
|
||||||
footnote?: string | null
|
footnote?: string | null
|
||||||
} | null
|
} | null
|
||||||
sidepeekContent?: SidepeekContent | null
|
sidepeekContent?: AlertSidepeekContent | null
|
||||||
sidepeekCtaText?: string | null
|
sidepeekCtaText?: string | null
|
||||||
link?: {
|
link?: {
|
||||||
url: string
|
url: string
|
||||||
|
|||||||
@@ -3,15 +3,20 @@
|
|||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { Button } from "@scandic-hotels/design-system/Button"
|
import { TeaserCardSidepeekContent } from "../../../types/sidepeekContent"
|
||||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
import { Button } from "../../Button"
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import ButtonLink from "../../ButtonLink"
|
||||||
import { JsonToHtml } from "@scandic-hotels/design-system/JsonToHtml"
|
import { MaterialIcon } from "../../Icons/MaterialIcon"
|
||||||
import SidePeek from "@scandic-hotels/design-system/SidePeek"
|
import { JsonToHtml } from "../../JsonToHtml/JsonToHtml"
|
||||||
|
import SidePeek from "../../SidePeek"
|
||||||
import styles from "./sidepeek.module.css"
|
import styles from "./sidepeek.module.css"
|
||||||
|
|
||||||
import type { TeaserCardSidepeekProps } from "@/types/components/teaserCard"
|
interface TeaserCardSidepeekProps {
|
||||||
|
button: {
|
||||||
|
call_to_action_text: string
|
||||||
|
}
|
||||||
|
sidePeekContent: TeaserCardSidepeekContent
|
||||||
|
}
|
||||||
|
|
||||||
export default function TeaserCardSidepeek({
|
export default function TeaserCardSidepeek({
|
||||||
button,
|
button,
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/nextjs-vite"
|
||||||
|
|
||||||
|
import { TeaserCard } from "./TeaserCard.tsx"
|
||||||
|
import { config } from "./variants.ts"
|
||||||
|
|
||||||
|
const PRIMARY_BUTTON = {
|
||||||
|
title: "Primary action",
|
||||||
|
href: "#",
|
||||||
|
openInNewTab: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SECONDARY_BUTTON = {
|
||||||
|
...PRIMARY_BUTTON,
|
||||||
|
title: "Secondary action",
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIDEPEEK_CONTENT = {
|
||||||
|
heading: "Sidepeek heading",
|
||||||
|
content: {
|
||||||
|
json: {
|
||||||
|
type: "doc",
|
||||||
|
attrs: {},
|
||||||
|
uid: "8126b570ffef4090a78f8c863d73c3b8",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "p",
|
||||||
|
attrs: {},
|
||||||
|
uid: "ed82964e32764cf589e07a251014543b",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae neque non ipsum efficitur hendrerit at ut nulla. Cras in tellus et ligula posuere ullamcorper. Praesent pulvinar rutrum metus ut gravida.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "h3",
|
||||||
|
attrs: {},
|
||||||
|
uid: "799903bc123d479d9bcf29cb4ba24b65",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Lorem ipsum",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "p",
|
||||||
|
attrs: {},
|
||||||
|
uid: "00886e5b0a5d4268930a1472b58e9170",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae neque non ipsum efficitur hendrerit at ut nulla. Cras in tellus et ligula posuere ullamcorper. Praesent pulvinar rutrum metus ut gravida.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "p",
|
||||||
|
attrs: {},
|
||||||
|
uid: "6bd20356e8bd4612a99e1af6061f861c",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: "28e963603b714055b948a038539bfd98",
|
||||||
|
type: "a",
|
||||||
|
attrs: {
|
||||||
|
url: "https://www.scandichotels.com/en",
|
||||||
|
target: "_blank",
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Learn more about this",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "h3",
|
||||||
|
attrs: {},
|
||||||
|
uid: "9db195292c5b49ac970edc8b4be19081",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Dolor sit amet",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "p",
|
||||||
|
attrs: {},
|
||||||
|
uid: "f45861b9146040ff8a6076cc2bc4d218",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae neque non ipsum efficitur hendrerit at ut nulla. Cras in tellus et ligula posuere ullamcorper. Praesent pulvinar rutrum metus ut gravida.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: "a40eb77a4c26401689255e0caa2f564a",
|
||||||
|
type: "ul",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "li",
|
||||||
|
attrs: {},
|
||||||
|
uid: "701d76e63827483cb80180056d1b396e",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Lorem ipsum",
|
||||||
|
id: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "li",
|
||||||
|
attrs: {},
|
||||||
|
uid: "f3f28b61c4b54162a7c723995a38a063",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Dolor sit amet consectetur adipiscing elit.",
|
||||||
|
id: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "li",
|
||||||
|
attrs: {},
|
||||||
|
uid: "27f51d6ad42e4dafb5037b5ee790f0b2",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Curabitur vitae neque non ipsum efficitur",
|
||||||
|
id: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attrs: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_version: 16,
|
||||||
|
},
|
||||||
|
embedded_itemsConnection: {
|
||||||
|
edges: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primary_button: {
|
||||||
|
title: "Sidepeek primary action",
|
||||||
|
href: "#",
|
||||||
|
openInNewTab: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_ARGS = {
|
||||||
|
heading: "Lorem ipsum",
|
||||||
|
bodyText:
|
||||||
|
"Dolor sit amet, consectetur adipiscing elit. Curabitur vitae neque non ipsum efficitur hendrerit at ut nulla.",
|
||||||
|
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 },
|
||||||
|
},
|
||||||
|
primaryButton: PRIMARY_BUTTON,
|
||||||
|
secondaryButton: SECONDARY_BUTTON,
|
||||||
|
alwaysStack: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta: Meta<typeof TeaserCard> = {
|
||||||
|
title: "Core Components/Cards/TeaserCard",
|
||||||
|
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: TeaserCard,
|
||||||
|
argTypes: {
|
||||||
|
heading: {
|
||||||
|
control: "text",
|
||||||
|
table: {
|
||||||
|
type: { summary: "string" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bodyText: {
|
||||||
|
control: "text",
|
||||||
|
table: {
|
||||||
|
type: { summary: "string" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primaryButton: {
|
||||||
|
control: "object",
|
||||||
|
table: {
|
||||||
|
type: {
|
||||||
|
summary:
|
||||||
|
"{ title: string, href: string, openInNewTab?: boolean, onPress?: () => void } | undefined",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secondaryButton: {
|
||||||
|
control: "object",
|
||||||
|
table: {
|
||||||
|
type: {
|
||||||
|
summary:
|
||||||
|
"{ title: string, href: string, openInNewTab?: boolean, onPress?: () => void } | undefined",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sidePeekButton: {
|
||||||
|
control: "object",
|
||||||
|
table: {
|
||||||
|
type: {
|
||||||
|
summary: "{ call_to_action_text: string } | undefined",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sidePeekContent: {
|
||||||
|
control: "object",
|
||||||
|
table: {
|
||||||
|
type: {
|
||||||
|
summary: "any | undefined",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
control: "object",
|
||||||
|
table: {
|
||||||
|
type: {
|
||||||
|
summary: "ImageVaultAsset | undefined",
|
||||||
|
detail:
|
||||||
|
"{ id: number, url: string, meta: {alt?: string | null, caption?: string | null}, focalPoint: { x: number, y: number }, dimensions: { width: number, height: number, aspectRatio: number } }",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
control: "select",
|
||||||
|
options: Object.keys(config.variants.style),
|
||||||
|
table: {
|
||||||
|
type: { summary: Object.keys(config.variants.style).join(" | ") },
|
||||||
|
},
|
||||||
|
defaultValue: config.defaultVariants.style,
|
||||||
|
},
|
||||||
|
alwaysStack: {
|
||||||
|
control: "boolean",
|
||||||
|
table: {
|
||||||
|
type: { summary: "boolean" },
|
||||||
|
},
|
||||||
|
defaultValue: config.defaultVariants.alwaysStack.toString(),
|
||||||
|
description:
|
||||||
|
"If true, the buttons will always be stacked vertically, regardless if these would fit next to each other.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: { ...DEFAULT_ARGS },
|
||||||
|
globals: {
|
||||||
|
backgrounds: { default: "storybookLight" },
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story, context) => {
|
||||||
|
const showMultipleStyles = [
|
||||||
|
"with sidepeek",
|
||||||
|
"without image",
|
||||||
|
"always stack buttons",
|
||||||
|
].some((substring) => context.name.toLowerCase().includes(substring))
|
||||||
|
if (showMultipleStyles) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gap: "1em",
|
||||||
|
gridTemplateColumns: `repeat(${Object.keys(config.variants.style).length}, 400px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(config.variants.style).map((style, ix) => {
|
||||||
|
return (
|
||||||
|
<TeaserCard
|
||||||
|
key={ix}
|
||||||
|
{...context.args}
|
||||||
|
style={style as keyof typeof config.variants.style}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ maxWidth: "400px" }}>
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof TeaserCard>
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
...meta.args,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Featured: Story = {
|
||||||
|
args: {
|
||||||
|
...meta.args,
|
||||||
|
style: "featured",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithSidepeek: Story = {
|
||||||
|
args: {
|
||||||
|
...meta.args,
|
||||||
|
sidePeekButton: {
|
||||||
|
call_to_action_text: "Side peek action",
|
||||||
|
},
|
||||||
|
sidePeekContent: SIDEPEEK_CONTENT,
|
||||||
|
style: "featured",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithoutImage: Story = {
|
||||||
|
args: {
|
||||||
|
...meta.args,
|
||||||
|
image: undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlwaysStackButtons: Story = {
|
||||||
|
args: {
|
||||||
|
...meta.args,
|
||||||
|
alwaysStack: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,27 +1,47 @@
|
|||||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
import { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
|
||||||
import Image from "@scandic-hotels/design-system/Image"
|
import { VariantProps } from "class-variance-authority"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { TeaserCardSidepeekContent } from "../../types/sidepeekContent"
|
||||||
|
import { type ButtonProps } from "../Button"
|
||||||
|
import ButtonLink from "../ButtonLink"
|
||||||
|
import Image from "../Image"
|
||||||
|
import { Typography } from "../Typography"
|
||||||
import TeaserCardSidepeek from "./Sidepeek"
|
import TeaserCardSidepeek from "./Sidepeek"
|
||||||
|
import styles from "./teaserCard.module.css"
|
||||||
import { teaserCardVariants } from "./variants"
|
import { teaserCardVariants } from "./variants"
|
||||||
|
|
||||||
import styles from "./teaserCard.module.css"
|
interface SidePeekButton {
|
||||||
|
call_to_action_text: string
|
||||||
|
}
|
||||||
|
|
||||||
import type { TeaserCardProps } from "@/types/components/teaserCard"
|
interface TeaserCardButton extends Pick<ButtonProps, "onPress"> {
|
||||||
|
title: string
|
||||||
|
href: string
|
||||||
|
openInNewTab?: boolean
|
||||||
|
}
|
||||||
|
interface TeaserCardProps extends VariantProps<typeof teaserCardVariants> {
|
||||||
|
heading: string
|
||||||
|
bodyText: string
|
||||||
|
primaryButton?: TeaserCardButton
|
||||||
|
secondaryButton?: TeaserCardButton
|
||||||
|
sidePeekButton?: SidePeekButton
|
||||||
|
sidePeekContent?: TeaserCardSidepeekContent
|
||||||
|
image?: ImageVaultAsset
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
export default function TeaserCard({
|
export function TeaserCard({
|
||||||
title,
|
heading,
|
||||||
description,
|
bodyText,
|
||||||
primaryButton,
|
primaryButton,
|
||||||
secondaryButton,
|
secondaryButton,
|
||||||
sidePeekButton,
|
sidePeekButton,
|
||||||
sidePeekContent,
|
sidePeekContent,
|
||||||
image,
|
image,
|
||||||
intent,
|
style,
|
||||||
alwaysStack = false,
|
alwaysStack = false,
|
||||||
className,
|
className,
|
||||||
}: TeaserCardProps) {
|
}: TeaserCardProps) {
|
||||||
const classNames = teaserCardVariants({ intent, alwaysStack, className })
|
const classNames = teaserCardVariants({ style, alwaysStack, className })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={classNames}>
|
<article className={classNames}>
|
||||||
@@ -30,7 +50,6 @@ export default function TeaserCard({
|
|||||||
<Image
|
<Image
|
||||||
src={image.url}
|
src={image.url}
|
||||||
alt={image.meta?.alt || ""}
|
alt={image.meta?.alt || ""}
|
||||||
className={styles.image}
|
|
||||||
focalPoint={image.focalPoint}
|
focalPoint={image.focalPoint}
|
||||||
dimensions={image.dimensions}
|
dimensions={image.dimensions}
|
||||||
fill
|
fill
|
||||||
@@ -39,10 +58,10 @@ export default function TeaserCard({
|
|||||||
)}
|
)}
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<Typography variant="Title/Subtitle/md">
|
<Typography variant="Title/Subtitle/md">
|
||||||
<p>{title}</p>
|
<p>{heading}</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<p>{description}</p>
|
<p>{bodyText}</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{sidePeekButton && sidePeekContent ? (
|
{sidePeekButton && sidePeekContent ? (
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { TeaserCard } from "./TeaserCard"
|
||||||
@@ -1,8 +1,18 @@
|
|||||||
.card {
|
.teaserCard {
|
||||||
border-radius: var(--Corner-radius-md);
|
border-radius: var(--Corner-radius-md);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--Border-Default);
|
||||||
|
color: var(--Text-Default);
|
||||||
|
|
||||||
|
&.default {
|
||||||
|
background-color: var(--Surface-Secondary-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.featured {
|
||||||
|
background-color: var(--Surface-Primary-Default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.imageContainer {
|
.imageContainer {
|
||||||
@@ -11,36 +21,12 @@
|
|||||||
position: relative;
|
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 {
|
.content {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Space-x15);
|
gap: var(--Space-x15);
|
||||||
padding: var(--Space-x2) var(--Space-x3);
|
padding: var(--Space-x2) var(--Space-x3);
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr auto;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
color: var(--Main-Grey-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
color: var(--Base-Text-Medium-contrast);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ctaContainer {
|
.ctaContainer {
|
||||||
@@ -51,11 +37,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1367px) {
|
@media (min-width: 1367px) {
|
||||||
.card:not(.alwaysStack) .ctaContainer {
|
.teaserCard:not(.alwaysStack) .ctaContainer {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
|
||||||
}
|
|
||||||
|
|
||||||
.card:not(.alwaysStack) .ctaContainer:has(:only-child) {
|
&:has(:only-child) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,9 @@ import { cva } from "class-variance-authority"
|
|||||||
|
|
||||||
import styles from "./teaserCard.module.css"
|
import styles from "./teaserCard.module.css"
|
||||||
|
|
||||||
export const teaserCardVariants = cva(styles.card, {
|
export const config = {
|
||||||
variants: {
|
variants: {
|
||||||
intent: {
|
style: {
|
||||||
default: styles.default,
|
default: styles.default,
|
||||||
featured: styles.featured,
|
featured: styles.featured,
|
||||||
},
|
},
|
||||||
@@ -14,7 +14,9 @@ export const teaserCardVariants = cva(styles.card, {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
intent: "default",
|
style: "default",
|
||||||
alwaysStack: false,
|
alwaysStack: false,
|
||||||
},
|
},
|
||||||
})
|
} as const
|
||||||
|
|
||||||
|
export const teaserCardVariants = cva(styles.teaserCard, config)
|
||||||
13
packages/design-system/lib/normalize.css
vendored
13
packages/design-system/lib/normalize.css
vendored
@@ -24,3 +24,16 @@ ul {
|
|||||||
outline-color: var(--Border-Interactive-Focus);
|
outline-color: var(--Border-Interactive-Focus);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* From Tailwind */
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|||||||
26
packages/design-system/lib/types/sidepeekContent.ts
Normal file
26
packages/design-system/lib/types/sidepeekContent.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
type SidepeekContentBase = {
|
||||||
|
heading: string
|
||||||
|
content?: {
|
||||||
|
json?: any
|
||||||
|
embedded_itemsConnection: {
|
||||||
|
edges: any
|
||||||
|
}
|
||||||
|
} | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AlertSidepeekContent = SidepeekContentBase
|
||||||
|
export type TeaserCardSidepeekContent = SidepeekContentBase & {
|
||||||
|
primary_button?: {
|
||||||
|
href: string
|
||||||
|
title: string
|
||||||
|
openInNewTab?: boolean
|
||||||
|
isExternal?: boolean
|
||||||
|
}
|
||||||
|
secondary_button?: {
|
||||||
|
href: string
|
||||||
|
title: string
|
||||||
|
openInNewTab?: boolean
|
||||||
|
isExternal?: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -179,6 +179,7 @@
|
|||||||
"./Switch": "./lib/components/Switch/index.tsx",
|
"./Switch": "./lib/components/Switch/index.tsx",
|
||||||
"./Table": "./lib/components/Table/index.tsx",
|
"./Table": "./lib/components/Table/index.tsx",
|
||||||
"./TermModal": "./lib/components/RateCard/TermModal/index.tsx",
|
"./TermModal": "./lib/components/RateCard/TermModal/index.tsx",
|
||||||
|
"./TeaserCard": "./lib/components/TeaserCard/index.tsx",
|
||||||
"./TextArea": "./lib/components/TextArea/index.tsx",
|
"./TextArea": "./lib/components/TextArea/index.tsx",
|
||||||
"./TextLink": "./lib/components/TextLink/index.tsx",
|
"./TextLink": "./lib/components/TextLink/index.tsx",
|
||||||
"./TextLinkButton": "./lib/components/TextLinkButton/index.tsx",
|
"./TextLinkButton": "./lib/components/TextLinkButton/index.tsx",
|
||||||
|
|||||||
Reference in New Issue
Block a user