EmpiezaTuSaaS

Modales

Dialogos y sheets para contenido modal

Modales

Componentes para mostrar contenido modal: dialogos centrados y sheets laterales. El boilerplate incluye Dialog y Sheet de shadcn/ui, ambos basados en @radix-ui/react-dialog.

Dialog

El componente Dialog crea un dialogo modal centrado. Incluye variantes AlertDialog para confirmaciónes y Dialog para formularios.

AlertDialog (confirmación)

Ejemplo: dialogo de confirmación
"use client";

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";

export function ConfirmDialog({
  onConfirm,
  children,
}: {
  onConfirm: () => void;
  children: React.ReactNode;
}) {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>¿Estás seguro?</AlertDialogTitle>
          <AlertDialogDescription>
            Esta acción no se puede deshacer.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancelar</AlertDialogCancel>
          <AlertDialogAction onClick={onConfirm}>Confirmar</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
}

Uso:

<ConfirmDialog onConfirm={() => deleteItem(id)}>
  <Button variant="destructive">Eliminar</Button>
</ConfirmDialog>

Dialog con formulario

Ejemplo: dialogo de edicion
"use client";

import { useState } from "react";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export function EditDialog({
  title,
  defaultValue,
  onSave,
}: {
  title: string;
  defaultValue: string;
  onSave: (value: string) => void;
}) {
  const [open, setOpen] = useState(false);
  const [value, setValue] = useState(defaultValue);

  const handleSave = () => {
    onSave(value);
    setOpen(false);
  };

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button variant="outline">Editar</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Editar {title}</DialogTitle>
          <DialogDescription>
            Haz los cambios necesarios y guarda.
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-4 py-4">
          <div className="space-y-2">
            <Label htmlFor="name">{title}</Label>
            <Input
              id="name"
              value={value}
              onChange={(e) => setValue(e.target.value)}
            />
          </div>
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => setOpen(false)}>
            Cancelar
          </Button>
          <Button onClick={handleSave}>Guardar</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Sheet (panel lateral)

El componente Sheet crea un panel que se desliza desde el borde de la pantalla. En el boilerplate se usa para el menu movil del Header.

Nota: Sheet usa @radix-ui/react-dialog internamente (no existe @radix-ui/react-sheet).

Uso en el Header (menu movil)

Asi es como se usa en el boilerplate para el menu movil:

components/landing/header.tsx
"use client";

import { useState } from "react";
import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { Menu } from "lucide-react";
import Link from "next/link";
import { siteConfig } from "@/config/site";

export function Header() {
  const [open, setOpen] = useState(false);

  return (
    <header>
      {/* ... desktop nav ... */}

      {/* Mobile Menu */}
      <Sheet open={open} onOpenChange={setOpen}>
        <SheetTrigger asChild className="md:hidden">
          <Button variant="ghost" size="icon">
            <Menu className="h-5 w-5" />
            <span className="sr-only">Abrir menú</span>
          </Button>
        </SheetTrigger>
        <SheetContent side="right" className="w-72">
          <SheetHeader>
            <SheetTitle className="text-left">
              <span className="text-primary">⚡</span> {siteConfig.name}
            </SheetTitle>
          </SheetHeader>
          <nav className="mt-8 flex flex-col gap-4">
            {siteConfig.navLinks.map((link) => (
              <Link
                key={link.href}
                href={link.href}
                className="text-sm text-muted-foreground hover:text-foreground transition-colors py-2 border-b border-border"
                onClick={() => setOpen(false)}
              >
                {link.label}
              </Link>
            ))}
            <div className="flex flex-col gap-2 mt-4">
              <Button variant="outline" asChild>
                <Link href="/login" onClick={() => setOpen(false)}>
                  Iniciar sesión
                </Link>
              </Button>
              <Button asChild>
                <Link href="/#pricing" onClick={() => setOpen(false)}>
                  Obtener acceso
                </Link>
              </Button>
            </div>
          </nav>
        </SheetContent>
      </Sheet>
    </header>
  );
}

Sheet generico

Puedes usar Sheet para cualquier panel lateral:

import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";

<Sheet>
  <SheetTrigger asChild>
    <Button variant="outline">Abrir panel</Button>
  </SheetTrigger>
  <SheetContent side="right">
    <SheetHeader>
      <SheetTitle>Titulo del panel</SheetTitle>
    </SheetHeader>
    <div className="py-6">
      {/* Contenido del panel */}
    </div>
  </SheetContent>
</Sheet>

Propiedades de SheetContent:

  • side: "top" | "right" | "bottom" | "left" (por defecto "right")
  • className: Clases CSS adicionales (ej: "w-72" para ancho personalizado)

El componente DropdownMenu se usa en el panel de admin para las acciones de usuario:

Ejemplo del panel de admin
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { MoreHorizontal, ShieldCheck, Ban } from "lucide-react";

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="ghost" size="icon" className="h-8 w-8">
      <MoreHorizontal className="h-4 w-4" />
    </Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent align="end">
    <DropdownMenuLabel>Acciones</DropdownMenuLabel>
    <DropdownMenuSeparator />
    <DropdownMenuItem onClick={handleAction}>
      <ShieldCheck className="h-4 w-4 mr-2" />
      Hacer admin
    </DropdownMenuItem>
    <DropdownMenuItem onClick={handleBan} className="text-destructive">
      <Ban className="h-4 w-4 mr-2" />
      Banear usuario
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

On this page