Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/banner-action-slot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/kumo": minor
---

Add `action` prop to Banner for rendering CTA buttons alongside structured title/description content without resorting to the `children` prop.
54 changes: 51 additions & 3 deletions packages/kumo-docs-astro/src/components/demos/BannerDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Banner, Text } from "@cloudflare/kumo";
import { Info, WarningCircle, Warning } from "@phosphor-icons/react";
import { Banner, Button, Text } from "@cloudflare/kumo";
import { Info, WarningCircle, Warning, X } from "@phosphor-icons/react";

/** Shows all banner variants with structured title and description. */
export function BannerVariantsDemo() {
return (
<div className="space-y-3">
<div className="w-full space-y-3">
<Banner
icon={<Info weight="fill" />}
title="Update available"
Expand Down Expand Up @@ -98,6 +98,54 @@ export function BannerCustomContentDemo() {
);
}

/** Banner with action buttons: CTA and dismissable. */
export function BannerWithActionDemo() {
return (
<div className="w-full space-y-3">
<Banner
icon={<Info weight="fill" />}
title="Update available"
description="A new version is ready to install."
action={<Button size="sm">Update now</Button>}
/>
<Banner
icon={<Info weight="fill" />}
title="Update available"
description="A new version is ready to install."
action={
<Button
size="sm"
variant="ghost"
shape="square"
icon={X}
aria-label="Dismiss"
/>
}
/>
</div>
);
}

/** Banner with multiple action buttons. */
export function BannerWithActionsDemo() {
return (
<Banner
icon={<Warning weight="fill" />}
variant="alert"
title="Session expiring"
description="Your session will expire in 5 minutes."
action={
<>
<Button size="sm" variant="secondary">
Dismiss
</Button>
<Button size="sm">Extend session</Button>
</>
}
/>
);
}

/** Legacy banner using children prop (backwards compatible). */
export function BannerLegacyDemo() {
return (
Expand Down
12 changes: 12 additions & 0 deletions packages/kumo-docs-astro/src/pages/components/banner.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
BannerAlertDemo,
BannerErrorDemo,
BannerWithIconDemo,
BannerWithActionDemo,
BannerWithActionsDemo,
BannerCustomContentDemo,
} from "~/components/demos/BannerDemo";

Expand Down Expand Up @@ -89,6 +91,16 @@ export default function Example() {
<BannerWithIconDemo client:visible />
</ComponentExample>

<Heading level={3}>With action</Heading>
<ComponentExample demo="BannerWithActionDemo">
<BannerWithActionDemo client:visible />
</ComponentExample>

<Heading level={3}>With multiple actions</Heading>
<ComponentExample demo="BannerWithActionsDemo">
<BannerWithActionsDemo client:visible />
</ComponentExample>

<Heading level={3}>Custom content</Heading>
<ComponentExample demo="BannerCustomContentDemo">
<BannerCustomContentDemo client:visible />
Expand Down
24 changes: 18 additions & 6 deletions packages/kumo/src/components/banner/banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export interface BannerProps {
title?: string;
/** Secondary description text displayed below the title. Use for i18n string injection. */
description?: ReactNode;
/** Action slot rendered at the trailing end of the banner (e.g. a CTA button or link). Only used in structured mode (with `title` or `description`). */
action?: ReactNode;
/** @deprecated Use `title` and `description` instead. Will be removed in a future major version. */
text?: string;
/** @deprecated Use `title` and `description` instead for better i18n support. */
Expand Down Expand Up @@ -123,6 +125,7 @@ export const Banner = forwardRef<HTMLDivElement, BannerProps>(function Banner(
icon,
title,
description,
action,
children,
text,
variant = KUMO_BANNER_DEFAULT_VARIANTS.variant,
Expand All @@ -146,12 +149,21 @@ export const Banner = forwardRef<HTMLDivElement, BannerProps>(function Banner(
{icon}
</span>
)}
<div className="flex flex-col gap-0.5">
{title && <p className="font-medium leading-snug">{title}</p>}
{description && (
<div className="text-sm leading-snug">
{isValidElement(description) ? description : <p>{description}</p>}
</div>
<div className="flex min-w-0 flex-1 items-center justify-between gap-3">
<div className="flex flex-col gap-0.5">
{title && <p className="font-medium leading-snug">{title}</p>}
{description && (
<div className="text-sm leading-snug">
{isValidElement(description) ? (
description
) : (
<p>{description}</p>
)}
</div>
)}
</div>
{action && (
<div className="flex shrink-0 items-center gap-2">{action}</div>
)}
</div>
</div>
Expand Down
Loading