Skip to content

refactor(ui): standardize scattered banner patterns into shared Banner component#2038

Open
evanjacobson wants to merge 18 commits intomainfrom
improvement/standardize-banners-2
Open

refactor(ui): standardize scattered banner patterns into shared Banner component#2038
evanjacobson wants to merge 18 commits intomainfrom
improvement/standardize-banners-2

Conversation

@evanjacobson
Copy link
Copy Markdown
Contributor

@evanjacobson evanjacobson commented Apr 5, 2026

Summary

Migrates 9 scattered banner/alert patterns across the codebase to use a single shared Banner compound component (Banner.Root, Banner.Icon, Banner.Content, Banner.Title, Banner.Description, Banner.Action, Banner.Button, Banner.Dismiss).

What changed:

  • Enhanced the existing Banner component with size="lg" mode (for card-style headers), outline/secondary/ghost button variants, a BannerDismiss sub-component, and per-size color maps
  • Migrated Alert-based banners (ErrorBanner, OldSessionBanner, AdminViewingBanner) to Banner, preserving role="alert" parity
  • Migrated Card-based headers (OrganizationWelcomeHeader, NewOrganizationWelcomeHeader, OrganizationTopupSuccessHeader) to Banner size="lg"
  • Migrated hand-rolled banner patterns (BillingBanner, FreeTrialWarningBanner, InsufficientBalanceBanner) to Banner
  • Added auto rel="noopener noreferrer" on BannerButton when target="_blank" is set

Live before/after preview: https://standardize-banners-2.pages.dev

Verification

  • pnpm typecheck — passes
  • pnpm format — no changes needed
  • pnpm format:check — passes (via pre-push hook)
  • Built showcase app (~250KB) and deployed to Cloudflare Pages for visual verification

Visual Changes

See the deployed before/after showcase for all 9 migrated components at both desktop and mobile viewports:

https://standardize-banners-2.pages.dev

The showcase shows before (old pattern) and after (new Banner component) side-by-side for every component and variant.

Reviewer Notes

  • The InsufficientBalanceBanner compact variant still uses some raw elements inside the Banner wrapper rather than the full compound component API — this is intentional because the compact layout doesn't fit the standard icon/content/action structure
  • NewOrganizationWelcomeHeader accepts organizationName in its props type but doesn't use it in the template — this is a pre-existing issue, not introduced by this migration
  • The BannerColorValue type is locally declared in BillingBanner.tsx and FreeTrialWarningBanner.tsx rather than imported from Banner — kept local to avoid coupling the consumer to the component's internal type

… 3 named banner files

- Add green color variant to Banner
- Add Banner.Dismiss subcomponent (X button)
- Migrate OldSessionBanner, AdminViewingBanner, ErrorBanner to shared Banner
- Save standardize-banners migration plan to .plans/
Migrate BillingBanner, FreeTrialWarningBanner, and InsufficientBalanceBanner
to the shared Banner component. Add banner-showcase/ with standalone
before/after comparison page and per-banner app screenshots at desktop
and mobile viewports.
…fix Banner.Action gap

- Add sticky controls bar with width slider, collapse/expand all buttons
- Restructure screenshot galleries to 2 rows (desktop, mobile)
- Make page full-width (remove max-w constraints)
- Fix Banner.Action missing flex gap-2 for multi-button layouts
- Re-capture screenshots with updated button spacing
Remove the banner-showcase/ standalone app (couldn't resolve parent
transitive deps cleanly). Add before/after Storybook stories for all 6
Phase 1 banners under storybook/stories/banner-migration/. These use
the real imported components and run in the existing Storybook infra.

Also add storybook/stories/_pr/ to .gitignore for future ephemeral
PR-scoped stories.
Lightweight standalone preview (~250KB) importing real components from
the parent app via Vite resolve.alias. Features sidebar tabs per
component, variant subtabs, and iframe-based mobile viewport toggle
that correctly triggers Tailwind sm: breakpoints.

Deployed to https://banner-showcase.pages.dev
BannerAction now uses flex-col sm:flex-row so multiple buttons stack
on narrow viewports instead of overflowing side-by-side.
Banner.Dismiss now uses absolute positioning (top-3 right-3) so it
always appears in the top-right corner regardless of flex-wrap layout
or viewport size. BannerRoot gains 'relative' to establish the
positioning context.
The Dismiss action button already handles dismissal — the X was
calling the same onDismiss callback. Removed to avoid duplicate
affordances.
BannerButton now accepts variant='primary' (default, solid fill) or
variant='outline' (transparent with border). ErrorBanner uses outline
for Dismiss to visually differentiate it from the primary Retry action.
The previous outline style used border-current/30 which isn't valid
Tailwind v4 syntax. Added outlineButton to colorMap with explicit
color classes (e.g. border-red-500/40, text-red-400, hover:bg-red-500/10)
for each color.
- secondary: dark gray fill (bg-white/10), neutral — for primary
  actions that shouldn't carry the banner's accent color
- ghost: transparent with subtle text — for de-emphasized actions

ErrorBanner now uses secondary for Retry and ghost for Dismiss,
matching the visual weight of the old Alert-based buttons.
Retry gets the outlined border style, Dismiss stays ghost/transparent.
Remove banner-showcase/, storybook/stories/banner-migration/,
banner-test page, screenshots, and .playwright-mcp artifacts.
Restore pnpm-workspace.yaml and .gitignore to main state.
…ze=lg

Extend the shared Banner component with a size='lg' variant that supports
the deeper color palette, circular icon backgrounds, and larger typography
used by the organization welcome/success card headers.

Migrate OrganizationWelcomeHeader, NewOrganizationWelcomeHeader, and
OrganizationTopupSuccessHeader from bespoke Card+CardContent layouts to
the shared Banner compound component.
return (
<Button asChild className={btnClass}>
<Link href={href}>{children}</Link>
<Link href={href} target={target}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: target="_blank" links lose rel="noopener noreferrer"

Banner.Button now accepts target, but it does not forward a rel prop or set a safe default when opening a new tab. Call sites like OrganizationWelcomeHeader now open an external docs page with window.opener exposed. Please add rel support here and pass noopener noreferrer for _blank links.

@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot bot commented Apr 5, 2026

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0

Fix these issues in Kilo Cloud

Issue Details (click to expand)

WARNING

File Line Issue
src/components/shared/Banner.tsx 306 Banner.Button now opens _blank links without rel="noopener noreferrer", exposing window.opener for external destinations.
src/components/cloud-agent-next/ErrorBanner.tsx 15 Migrating from Alert to Banner drops the implicit role="alert", so this error state is no longer announced to assistive tech.
Other Observations (not in diff)

N/A

Files Reviewed (10 files)
  • src/app/(app)/claw/components/billing/BillingBanner.tsx - 0 issues
  • src/components/cloud-agent-next/ErrorBanner.tsx - 1 issue
  • src/components/cloud-agent-next/OldSessionBanner.tsx - 0 issues
  • src/components/gastown/AdminViewingBanner.tsx - 0 issues
  • src/components/organizations/FreeTrialWarningBanner.tsx - 0 issues
  • src/components/organizations/NewOrganizationWelcomeHeader.tsx - 0 issues
  • src/components/organizations/OrganizationTopupSuccessHeader.tsx - 0 issues
  • src/components/organizations/OrganizationWelcomeHeader.tsx - 0 issues
  • src/components/shared/Banner.tsx - 1 issue
  • src/components/shared/InsufficientBalanceBanner.tsx - 0 issues

Reviewed by gpt-5.4-20260305 · 107,994 tokens

@evanjacobson evanjacobson changed the title refactor(ui): migrate card-based welcome/success headers to shared Banner refactor(ui): standardize scattered banner patterns into shared Banner component Apr 6, 2026

function BannerDismiss({ onDismiss, className }: { onDismiss: () => void; className?: string }) {
return (
<button
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Banner.Dismiss defaults to a submit button

Native <button> elements default to type="submit". If any Banner.Dismiss instance is rendered inside a form, clicking the dismiss icon will submit that form unexpectedly. Set an explicit type="button" here so the shared dismiss control stays side-effect free.

Suggested change
<button
<button type="button"

@jeanduplessis
Copy link
Copy Markdown
Contributor

Due to the monorepo restructure you will need to recreate this PR on a new branch from main. Pass the prompt found at, https://github.com/Kilo-Org/cloud/blob/main/plans/monorepo-migration-prompt.md, to your coding agent while running in this branch. Please close this PR once done or if you don't plan to proceed with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants