shadimage

Modal

src/components/thegridcn/modal.tsx

"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
  description?: string;
  footer?: React.ReactNode;
  onClose: () => void;
  open: boolean;
  size?: "sm" | "md" | "lg";
  title?: string;
}

export function Modal({
  open,
  onClose,
  title,
  description,
  size = "md",
  footer,
  className,
  children,
  ...props
}: ModalProps) {
  // Close on Escape
  React.useEffect(() => {
    if (!open) {
      return;
    }
    const handler = (e: KeyboardEvent) => {
      if (e.key === "Escape") {
        onClose();
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [open, onClose]);

  // Lock body scroll
  React.useEffect(() => {
    if (open) {
      document.body.style.overflow = "hidden";
      return () => {
        document.body.style.overflow = "";
      };
    }
  }, [open]);

  if (!open) {
    return null;
  }

  return (
    <>
      {/* Backdrop */}
      <div
        className="fixed inset-0 z-50 bg-background/60 backdrop-blur-sm"
        onClick={onClose}
      />

      <div
        data-slot="tron-modal"
        role="dialog"
        aria-modal="true"
        className={cn(
          "fixed top-1/2 left-1/2 z-50 -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded border border-primary/30 bg-card/95 shadow-[0_0_40px_rgba(var(--primary-rgb,0,180,255),0.08)] backdrop-blur-md",
          size === "sm" && "w-full max-w-sm",
          size === "md" && "w-full max-w-lg",
          size === "lg" && "w-full max-w-2xl",
          className
        )}
        {...props}
      >
        {/* Scanline */}
        <div className="pointer-events-none absolute inset-0 bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(0,0,0,0.03)_2px,rgba(0,0,0,0.03)_4px)]" />

        {/* Header */}
        {title || description ? (
          <div className="border-primary/20 border-b px-5 py-3">
            {title ? (
              <h3 className="font-mono text-primary text-xs uppercase tracking-widest">
                {title}
              </h3>
            ) : null}
            {description ? (
              <p className="mt-0.5 font-mono text-[10px] text-foreground/40">
                {description}
              </p>
            ) : null}
          </div>
        ) : null}

        {/* Body */}
        <div className="px-5 py-4">{children}</div>

        {/* Footer */}
        {footer ? (
          <div className="flex items-center justify-end gap-2 border-primary/20 border-t px-5 py-3">
            {footer}
          </div>
        ) : null}

        {/* Close button */}
        <button
          type="button"
          onClick={onClose}
          className="absolute top-3 right-3 flex h-6 w-6 items-center justify-center rounded text-foreground/30 transition-colors hover:bg-primary/10 hover:text-primary"
          aria-label="Close"
        >
          <svg
            aria-hidden="true"
            width="10"
            height="10"
            viewBox="0 0 10 10"
            fill="none"
          >
            <path
              d="M1 1l8 8M9 1l-8 8"
              stroke="currentColor"
              strokeWidth="1.5"
              strokeLinecap="round"
            />
          </svg>
        </button>

        {/* Corner decorations */}
        <div className="pointer-events-none absolute top-0 left-0 h-3 w-3 border-primary/50 border-t-2 border-l-2" />
        <div className="pointer-events-none absolute top-0 right-0 h-3 w-3 border-primary/50 border-t-2 border-r-2" />
        <div className="pointer-events-none absolute bottom-0 left-0 h-3 w-3 border-primary/50 border-b-2 border-l-2" />
        <div className="pointer-events-none absolute right-0 bottom-0 h-3 w-3 border-primary/50 border-r-2 border-b-2" />
      </div>
    </>
  );
}

/** Reusable button for modal footers */
export function ModalButton({
  variant = "default",
  className,
  ...props
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: "default" | "primary" | "danger";
}) {
  return (
    <button
      type="button"
      className={cn(
        "rounded border px-4 py-1.5 font-mono text-[10px] uppercase tracking-widest transition-all",
        variant === "default" &&
          "border-primary/20 text-foreground/50 hover:bg-primary/5 hover:text-foreground/70",
        variant === "primary" &&
          "border-primary/40 bg-primary/10 text-primary hover:bg-primary/20",
        variant === "danger" &&
          "border-red-500/40 bg-red-500/10 text-red-400 hover:bg-red-500/20",
        className
      )}
      {...props}
    />
  );
}
Interaction
modal