);
};
// Story args extend the banner props with the editable mock fields so the
// address bar text and CTA can be controlled from the Storybook controls
// instead of being hardcoded.
type StoryArgs = ComponentProps & {
ctaLabel?: string;
ctaButtonVariant?: "primary_brand" | "primary_inverse" | "secondary";
mobilePrompt?: string;
placeholder?: string;
};
const meta: Meta = {
title: "Contentful Blocks/AddressInputBanner",
component: AddressInputBanner,
tags: ["autodocs"],
// Provide a tall, scrollable canvas so the sticky banner's `navHeight` offset
// is observable — CSS `position: sticky` only applies its `top` value while
// scrolling within a container taller than the viewport.
decorators: [
Story => (
Scroll to see the banner stick at the top (offset by navHeight).
),
],
parameters: {
layout: "fullscreen",
docs: {
page: DocsPage,
description: {
component:
"A sticky address input banner component that supports multiple themed variants and an optional CTA. It can be shown/hidden dynamically and adjusts positioning based on the navigation height.",
},
},
},
argTypes: {
title: {
control: { type: "text" },
description: "Title shown as the desktop heading next to the input",
},
variant: {
control: { type: "select" },
options: ["yellow", "white", "navy", "green"],
description: "Themed color variant of the banner",
},
navHeight: {
control: { type: "number" },
description: "Navigation height used to offset the sticky position",
},
isVisible: {
control: { type: "boolean" },
description: "Whether the banner is rendered",
},
ctaLabel: {
control: { type: "text" },
description: "CTA button label",
},
ctaButtonVariant: {
control: { type: "select" },
options: ["primary_brand", "primary_inverse", "secondary"],
description: "CTA button variant",
},
mobilePrompt: {
control: { type: "text" },
description: "Prompt text shown on mobile",
},
placeholder: {
control: { type: "text" },
description: "Address input placeholder text",
},
entryName: {
table: { disable: true },
},
cta: {
table: { disable: true },
},
},
args: {
title: "To see plans for your address, check availability",
variant: "yellow",
isVisible: true,
navHeight: 0,
ctaLabel: "Check plans",
mobilePrompt: "Show me the best offers in my area",
placeholder: "123 Dale Springs, Apt B, Binsport, NC, 17032",
},
render: ({
title,
ctaLabel,
ctaButtonVariant,
mobilePrompt,
placeholder,
renderCheckPlans,
cta,
...args
}) => {
// Dark-background variants need light prompt text for contrast.
const promptTextClass =
args.variant === "navy" || args.variant === "green"
? "text-white"
: "text-text";
// Derive the CTA button variant from the banner variant so it adapts when
// the variant control changes. An explicit ctaButtonVariant still overrides.
const effectiveButtonVariant =
ctaButtonVariant ??
(args.variant === "yellow" || args.variant === "green"
? "primary_inverse"
: "primary_brand");
// Keep the cta's buttonVariant in sync with the effective variant so the
// ctaButtonVariant control also drives stories that supply their own cta
// and renderCheckPlans (e.g. WithCta).
const effectiveCta = cta
? { ...cta, buttonVariant: effectiveButtonVariant }
: cta;
return (
(
))
}
/>
);
},
};
export default meta;
type Story = StoryObj;
// Default banner using all the default entries from meta args
export const Default: Story = {
args: {},
parameters: {
docs: {
description: {
story:
"Default AddressInputBanner rendered with all the default entries (yellow variant, visible, with the address bar CTA).",
},
},
},
};
// Yellow variant
export const Yellow: Story = {
args: {
variant: "yellow",
},
};
// White variant
export const White: Story = {
args: {
variant: "white",
},
};
// Navy variant
export const Navy: Story = {
args: {
variant: "navy",
},
};
// Green variant
export const Green: Story = {
args: {
variant: "green",
},
};
// Hidden banner renders nothing
export const Hidden: Story = {
args: {
isVisible: false,
},
parameters: {
docs: {
description: {
story:
"When isVisible is false, the banner renders nothing to the DOM.",
},
},
},
};
// Banner supplied with an explicit cta prop. It falls back to the default
// address bar render, so it looks consistent with the other variant stories
// while still exercising the cta overrides.
export const WithCta: Story = {
args: {
cta: {
buttonLabel: "Check plans",
buttonVariant: "primary_brand",
showButtonAs: "solid",
},
},
parameters: {
docs: {
description: {
story:
"AddressInputBanner rendered with an explicit `cta` prop. It uses the default address bar render, so it looks consistent with the other variant stories while still exercising the `cta` overrides.",
},
},
},
};
// Demonstrates the sticky positioning offset with a non-zero navHeight value.
// A mock fixed nav bar is layered on top of the shared scrollable canvas so the
// banner can be seen sticking directly below it once the page scrolls.
export const CustomNavHeight: Story = {
args: {
navHeight: 80,
},
decorators: [
(Story, context) => {
// Keep the mock nav height/label in sync with the navHeight control.
const navHeight = (context.args.navHeight as number | undefined) ?? 80;
return (
{/* Mock fixed navigation bar that matches the navHeight control */}
{`Mock Navigation (${navHeight}px)`}
{/* Spacer so content starts below the fixed nav */}
{/* Tall content in the SAME container as the banner so it has room to
stick while scrolling. */}
{`Scroll — the banner stays pinned ${navHeight}px from the top, below the nav.`}
);
},
],
parameters: {
docs: {
description: {
story:
"Demonstrates the sticky positioning with a non-zero `navHeight` (80px), which offsets the banner's `top` value so it sticks below a fixed navigation bar. Scroll the canvas to see the offset take effect.",
},
},
},
};