# All files / src/components/notifications Callout.tsx

100% Statements 38/38
50% Branches 1/2
50% Functions 1/2
100% Lines 38/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    632x
    2x

    2x
    2x

    2x
    2x
    2x
    2x
    2x
    2x

    2x
    3x
    3x
    3x
    3x
    3x
    3x

    3x
    3x
    3x
    3x
    3x
    3x

    3x
    3x
    3x
    3x
    3x
    3x
    3x
    3x
    3x
    3x

    3x
    3x

    3x

    3x

    3x
     import { useState, type ReactNode, type JSX } from "react";
    import { AlertTriangle, Check, Info, X } from "lucide-react";
    import { type CalloutBuiltinProps, type CalloutVariant } from "@grackle-ai/common";
    import styles from "./Callout.module.scss";
    import { ICON_LG, ICON_MD } from "../../utils/iconSize.js";

    // `variant` now lives in the built-in's zod schema (@grackle-ai/common);
    // re-export the union so the package barrel keeps exposing CalloutVariant.
    export type { CalloutVariant };

    interface CalloutProps extends CalloutBuiltinProps {
      children: ReactNode;
      /** Optional extra class name for layout overrides. */
      className?: string;
    }

    const VARIANT_ICONS: Record<CalloutVariant, ReactNode> = {
      success: <Check size={ICON_LG} />,
      error: <X size={ICON_LG} />,
      warning: <AlertTriangle size={ICON_LG} />,
      info: <Info size={ICON_LG} />,
    };

    /**
     * Inline contextual alert for persistent, location-specific information.
     * Use for things like blocked dependencies, validation errors, or
     * status messages that belong within a specific panel rather than a toast.
     */
    export function Callout({
      variant = "info",
      children,
      dismissible = false,
      className,
    }: CalloutProps): JSX.Element {
      const [dismissed, setDismissed] = useState(false);

      return (
        <>
          {!dismissed && (
            <div
              className={[styles.callout, styles[variant], className].filter(Boolean).join(" ")}
              role={variant === "error" || variant === "warning" ? "alert" : "status"}
            >
              <span className={styles.icon} aria-hidden="true">
                {VARIANT_ICONS[variant]}
              </span>
              <span className={styles.content}>{children}</span>
              {dismissible && (
                <button
                  type="button"
                  className={styles.close}
                  onClick={() => setDismissed(true)}
                  aria-label="Dismiss"
                >
                  <X size={ICON_MD} aria-hidden="true" />
                </button>
              )}
            </div>
          )}
        </>
      );
    }