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)
"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
"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:
"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)
DropdownMenu
El componente DropdownMenu se usa en el panel de admin para las acciones de usuario:
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>