import { Callout } from "./index"; import type { CalloutProps } from "./types"; import { blogItems, defaultCalloutArgs, defaultSimpleItems, floatingImageItems, fourColumnItems, fullImageItems, withFloatingImageArgs, } from "./Callout.stories.mocks"; import { DocsPage } from "@shared/stories/DocsTemplate"; import type { Meta, StoryObj } from "@storybook/react"; import React from "react"; /* * Tailwind safelist — classes referenced here are included in the CSS bundle * so they work when entered dynamically via Storybook controls. * border-2 border-red-500 border-blue-500 border-green-500 * rounded-3xl p-8 bg-gray-100 */ /* Controls */ const controls: Record = { title: { control: "text" }, subtitle: { control: "text" }, description: { control: "text" }, enableHeading: { control: "boolean" }, applyBoxShadow: { control: "boolean" }, cardStackingMobile: { control: "boolean" }, items: { control: "object" }, cta: { control: "object" }, finePrint: { control: "text" }, color: { control: "select", options: ["dark", "light"], }, cardsWidth: { control: "boolean" }, maxCardsPerRow: { control: { type: "number", min: 1, max: 6 } }, noGutter: { control: "boolean" }, maxWidth: { control: "boolean" }, cardType: { control: "select", options: ["simple", "blog", "fullImage", "floatingImage"], description: "Default card type. Each type expects a different item data shape — use the dedicated card type stories (SimpleCards, BlogCards, etc.) to preview each variant with correct mock data.", }, backgroundColor: { control: "select", options: [ "cream500", "gray100", "white", "transparent", "blue", "green", "navy", "purple", "yellow", ], }, background: { control: "text" }, textColor: { control: "text" }, disableAnimation: { control: "boolean" }, animationType: { control: "select", options: ["scale", "shadow", "lift", "opacity", "grow", "pop"], }, anchorId: { table: { disable: true } }, containerClassName: { control: "text" }, innerClassName: { control: "text" }, }; /* -------- Decorator: maps backgroundColor prop values to correct CSS variable colors via inline style. Applied in Storybook only — index.tsx remains untouched. ----------- */ const bgColorVarMap: Record = { blue: "var(--color-bg-fill-brand-supporting)", green: "var(--color-bg-fill-brand)", navy: "var(--color-bg-fill-inverse)", purple: "var(--color-bg-fill-brand-tertiary)", gray100: "var(--color-bg-fill-info)", }; const navyTextOverride = ` .navy-callout-override .text-text, .navy-callout-override .decoration-text, .navy-callout-override .text-link, .navy-callout-override .heading2, .navy-callout-override .heading1, .navy-callout-override .subheading1, .navy-callout-override .subheading3, .navy-callout-override .body1, .navy-callout-override .heading6, .navy-callout-override .label1, .navy-callout-override .footnote { color: white !important; text-decoration-color: white !important; } `; /** * Transforms item image fields based on cardType, mimicking the martech * `toLocalImageAsset` conversion. Converts string images to the * { href, title, width, height } format that fullImage/floatingImage expect. */ const normalizeItemsForCardType = ( items: CalloutProps["items"], cardType: string ): CalloutProps["items"] => { if (cardType !== "fullImage" && cardType !== "floatingImage") return items; return items.map((item) => { const img = (item as any).image; if (typeof img === "string") { return { ...item, image: { href: img, title: (item as any).imageAlt ?? (item as any).title ?? "", width: (item as any).imageWidth ?? 400, height: (item as any).imageHeight ?? 300, }, }; } return item; }); }; /* -------- Meta ----------- */ const meta: Meta = { title: "Contentful Blocks/Callout", component: Callout, tags: ["autodocs"], argTypes: controls, parameters: { layout: "centered", docs: { page: DocsPage, description: { component: "Flexible card grid/stack section with multiple card types (simple, blog, full-image, floating-image), configurable backgrounds, column layouts, and animation support.", }, }, }, decorators: [ (Story, context) => { const bg = context.args.backgroundColor as string; const cardType = context.args.cardType as string; const isNavy = bg === "navy" || context.args.background === bgColorVarMap.navy; const cssVar = bgColorVarMap[bg]; const transformedItems = normalizeItemsForCardType( context.args.items as CalloutProps["items"], cardType ); const newArgs: Record = { ...context.args }; const userBackground = context.args.background as string; if (cssVar && !userBackground) { newArgs.backgroundColor = undefined; newArgs.background = cssVar; } if (transformedItems !== context.args.items) { newArgs.items = transformedItems; } const hasOverrides = (cssVar && !userBackground) || transformedItems !== context.args.items; return ( <>
{hasOverrides ? : }
); }, ], args: { ...defaultCalloutArgs, items: defaultSimpleItems, } as CalloutProps, }; export default meta; type Story = StoryObj; /* Stories */ export const Default: Story = {}; export const SimpleCards: Story = { args: { title: "Simple Card Variant", subtitle: "Basic cards with title and description", items: defaultSimpleItems, cardType: "simple", }, }; export const BlogCards: Story = { args: { title: "Blog Card Variant", subtitle: "Stay informed with our blog", items: blogItems, cardType: "blog", }, }; export const FullImageCards: Story = { args: { title: "Full Image Card Variant", subtitle: "Cards with full-bleed imagery", items: fullImageItems, cardType: "fullImage", }, }; export const FloatingImageCards: Story = { args: { ...withFloatingImageArgs, }, }; export const BackgroundWhite: Story = { args: { title: "White Background", items: defaultSimpleItems, backgroundColor: "white", }, }; export const BackgroundNavy: Story = { args: { title: "Navy Background", subtitle: "Dark theme with light text", items: defaultSimpleItems, background: bgColorVarMap.navy, color: "light", textColor: "#ffffff", }, }; export const BackgroundGreen: Story = { args: { title: "Green Background", items: defaultSimpleItems, background: bgColorVarMap.green, color: "light", }, }; export const BackgroundBlue: Story = { args: { title: "Blue Background", items: defaultSimpleItems, background: bgColorVarMap.blue, color: "light", }, }; export const BackgroundPurple: Story = { args: { title: "Purple Background", items: defaultSimpleItems, background: bgColorVarMap.purple, color: "light", }, }; export const BackgroundYellow: Story = { args: { title: "Yellow Background", items: defaultSimpleItems, backgroundColor: "yellow", }, }; export const BackgroundTransparent: Story = { args: { title: "Transparent Background", items: defaultSimpleItems, backgroundColor: "transparent", }, }; export const StackLayout: Story = { args: { title: "Stacked Layout", items: defaultSimpleItems, cardsWidth: false, }, }; export const WithCta: Story = { args: { title: "Ready to Get Started?", subtitle: "Pick the plan that works for you", items: defaultSimpleItems, cta: { buttonLabel: "View All Plans", href: "/plans", showButtonAs: "solid", buttonVariant: "primary_brand", }, finePrint: "* Terms and conditions apply. Prices subject to change.", }, }; export const WithAnimation: Story = { args: { title: "Animated Cards", subtitle: "Hover over the cards to see the grow animation", items: defaultSimpleItems, disableAnimation: false, animationType: "grow", }, }; export const CustomColumns: Story = { args: { title: "Custom 4-Column Layout", items: fourColumnItems, maxCardsPerRow: 4, applyBoxShadow: true, }, };