# Banner

Full-width announcement banner

## Installation

  <TabsTrigger value="cli">Command</TabsTrigger>
  <TabsTrigger value="manual">Manual</TabsTrigger>

```bash
npx shadcn@latest add @termcn/banner
```

<Step>Copy and paste the following code into your project.</Step>

```tsx
/* @jsxImportSource @opentui/react */
import { useKeyboard } from "@opentui/react";
import { useState } from "react";
import type { ReactNode } from "react";

import { useTheme } from "@/components/ui/theme-provider";

export type BannerVariant =
  | "info"
  | "warning"
  | "error"
  | "success"
  | "neutral";

const ICONS: Record<BannerVariant, string> = {
  error: "✗",
  info: "ℹ",
  neutral: "·",
  success: "✓",
  warning: "⚠",
};

export interface BannerProps {
  children: ReactNode;
  variant?: BannerVariant;
  icon?: string;
  title?: string;
  dismissible?: boolean;
  onDismiss?: () => void;
  color?: string;
  accentChar?: string;
  gap?: number;
}

export const Banner = ({
  children,
  variant = "info",
  icon,
  title,
  dismissible = false,
  onDismiss,
  color,
  accentChar = "┃",
  gap = 1,
}: BannerProps) => {
  const theme = useTheme();
  const [dismissed, setDismissed] = useState(false);

  const variantColor =
    color ??
    (() => {
      switch (variant) {
        case "success": {
          return theme.colors.success;
        }
        case "error": {
          return theme.colors.error;
        }
        case "warning": {
          return theme.colors.warning;
        }
        case "neutral": {
          return theme.colors.muted;
        }
        default: {
          return theme.colors.info;
        }
      }
    })();

  useKeyboard((key) => {
    if (dismissible && key.name === "escape") {
      setDismissed(true);
      onDismiss?.();
    }
  });

  if (dismissed) {
    return null;
  }

  const resolvedIcon = icon ?? ICONS[variant];

  return (
    <box flexDirection="column">
      <box flexDirection="row">
        <text fg={variantColor}>{accentChar}</text>
        <box flexDirection="column">
          <box flexDirection="row" gap={1}>
            <text fg={variantColor}>{resolvedIcon}</text>
            {title && (
              <text fg={variantColor}>
                <b>{`${title}:`}</b>
              </text>
            )}
            <text>{children}</text>
          </box>
          {dismissible && <text fg="#666">press Esc to dismiss</text>}
        </box>
      </box>
    </box>
  );
};
```

<Step>Update the import paths to match your project setup.</Step>

## Usage

```tsx
import { Banner } from "@/components/ui/banner";
```

```tsx
<Banner variant="info" title="Update">
  A new version is available. Run npx update to upgrade.
</Banner>
```

## Examples

### Success Banner

### Error with Custom Icon

## API Reference

### Banner

| Prop          | Type                                                       | Default    |
| ------------- | ---------------------------------------------------------- | ---------- |
| `children`    | `ReactNode`                                                | `required` |
| `variant`     | `"info" \| "warning" \| "error" \| "success" \| "neutral"` | `"info"`   |
| `icon`        | `string`                                                   | -          |
| `title`       | `string`                                                   | -          |
| `dismissible` | `boolean`                                                  | `false`    |
| `onDismiss`   | `() => void`                                               | -          |
| `color`       | `string`                                                   | -          |
| `accentChar`  | `string`                                                   | `"┃"`      |
| `gap`         | `number`                                                   | `1`        |