Dialog
Hoja clienteA modal overlay for focused tasks.
Vista previa en vivo
Renderizado desde el código fuente instalado de VEXA — los mismos archivos que recibes.
Escritorio
Instalación
Añade el código del componente a tu proyecto. Las dependencias del registry se resuelven automáticamente.
npx shadcn@latest add https://vexa.valfiguer.com/r/dialog.jsonSe instala en components/vexa/dialog.tsx
Dependencias npm
- @radix-ui/react-dialog
Dependencias del registry
- @vexa/utils
Otros items de VEXA que necesita este componente. Instálalos primero — la CLI los resuelve automáticamente.
Código
Cópialo a su ruta destino, o instálalo con el comando de arriba.
components/vexa/dialog.tsx
"use client";
import type * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { cn } from "@/lib/utils";
function CloseIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
className={className}
>
<path
d="M6 6l12 12M18 6 6 18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);
}
/**
* Dialog — a modal overlay for focused tasks.
*
* Client leaf. Radix handles the modal contract required by §14.2: focus moves
* in on open and is trapped, Escape closes, focus returns to the trigger on
* close, and background content is inert to assistive technology. Every dialog
* needs a `DialogTitle`; add a `DialogDescription` (referenced automatically)
* when extra context helps.
*/
export function Dialog(
props: React.ComponentProps<typeof DialogPrimitive.Root>,
) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
export function DialogTrigger(
props: React.ComponentProps<typeof DialogPrimitive.Trigger>,
) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
export function DialogPortal(
props: React.ComponentProps<typeof DialogPrimitive.Portal>,
) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
export function DialogClose(
props: React.ComponentProps<typeof DialogPrimitive.Close>,
) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
export function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 z-50 bg-vexa-background/70 backdrop-blur-sm",
"transition-opacity duration-[var(--vexa-duration-base)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none",
"data-[state=closed]:opacity-0 data-[state=open]:opacity-100",
className,
)}
{...props}
/>
);
}
export function DialogContent({
className,
children,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4",
"rounded-vexa-lg border border-vexa-border bg-vexa-surface-raised p-6 text-vexa-foreground shadow-vexa-lg",
"transition-[opacity,transform] duration-[var(--vexa-duration-base)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none",
"data-[state=closed]:scale-95 data-[state=closed]:opacity-0 data-[state=open]:scale-100 data-[state=open]:opacity-100",
"focus-visible:outline-none",
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close
data-slot="dialog-close-button"
className={cn(
"absolute right-4 top-4 inline-flex size-8 items-center justify-center rounded-vexa-sm text-vexa-muted-foreground",
"transition-colors duration-[var(--vexa-duration-fast)] hover:bg-vexa-accent hover:text-vexa-foreground",
"outline-none focus-visible:ring-2 focus-visible:ring-vexa-ring focus-visible:ring-offset-2 focus-visible:ring-offset-vexa-background",
"disabled:pointer-events-none",
)}
>
<CloseIcon className="size-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
);
}
export function DialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-1.5 text-left", className)}
{...props}
/>
);
}
export function DialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className,
)}
{...props}
/>
);
}
export function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
);
}
export function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-sm text-vexa-muted-foreground", className)}
{...props}
/>
);
}
JSON del registryr/dialog.json
r/dialog.json
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "dialog",
"type": "registry:ui",
"title": "Dialog",
"description": "A modal overlay for focused tasks.",
"dependencies": [
"@radix-ui/react-dialog"
],
"registryDependencies": [
"@vexa/utils"
],
"files": [
{
"path": "registry/default/ui/dialog.tsx",
"type": "registry:ui",
"target": "components/vexa/dialog.tsx",
"content": "\"use client\";\n\nimport type * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction CloseIcon({ className }: { className?: string }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n className={className}\n >\n <path\n d=\"M6 6l12 12M18 6 6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n\n/**\n * Dialog — a modal overlay for focused tasks.\n *\n * Client leaf. Radix handles the modal contract required by §14.2: focus moves\n * in on open and is trapped, Escape closes, focus returns to the trigger on\n * close, and background content is inert to assistive technology. Every dialog\n * needs a `DialogTitle`; add a `DialogDescription` (referenced automatically)\n * when extra context helps.\n */\nexport function Dialog(\n props: React.ComponentProps<typeof DialogPrimitive.Root>,\n) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nexport function DialogTrigger(\n props: React.ComponentProps<typeof DialogPrimitive.Trigger>,\n) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nexport function DialogPortal(\n props: React.ComponentProps<typeof DialogPrimitive.Portal>,\n) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nexport function DialogClose(\n props: React.ComponentProps<typeof DialogPrimitive.Close>,\n) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nexport function DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n data-slot=\"dialog-overlay\"\n className={cn(\n \"fixed inset-0 z-50 bg-vexa-background/70 backdrop-blur-sm\",\n \"transition-opacity duration-[var(--vexa-duration-base)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none\",\n \"data-[state=closed]:opacity-0 data-[state=open]:opacity-100\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function DialogContent({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content>) {\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n data-slot=\"dialog-content\"\n className={cn(\n \"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4\",\n \"rounded-vexa-lg border border-vexa-border bg-vexa-surface-raised p-6 text-vexa-foreground shadow-vexa-lg\",\n \"transition-[opacity,transform] duration-[var(--vexa-duration-base)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none\",\n \"data-[state=closed]:scale-95 data-[state=closed]:opacity-0 data-[state=open]:scale-100 data-[state=open]:opacity-100\",\n \"focus-visible:outline-none\",\n className,\n )}\n {...props}\n >\n {children}\n <DialogPrimitive.Close\n data-slot=\"dialog-close-button\"\n className={cn(\n \"absolute right-4 top-4 inline-flex size-8 items-center justify-center rounded-vexa-sm text-vexa-muted-foreground\",\n \"transition-colors duration-[var(--vexa-duration-fast)] hover:bg-vexa-accent hover:text-vexa-foreground\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-vexa-ring focus-visible:ring-offset-2 focus-visible:ring-offset-vexa-background\",\n \"disabled:pointer-events-none\",\n )}\n >\n <CloseIcon className=\"size-4\" />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n </DialogPrimitive.Content>\n </DialogPortal>\n );\n}\n\nexport function DialogHeader({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn(\"flex flex-col gap-1.5 text-left\", className)}\n {...props}\n />\n );\n}\n\nexport function DialogFooter({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\n \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function DialogTitle({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn(\n \"text-lg font-semibold leading-none tracking-tight\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn(\"text-sm text-vexa-muted-foreground\", className)}\n {...props}\n />\n );\n}\n"
}
],
"meta": {
"frameworks": [
"react",
"next"
],
"rsc": "client",
"exports": [
"Dialog",
"DialogTrigger",
"DialogPortal",
"DialogClose",
"DialogOverlay",
"DialogContent",
"DialogHeader",
"DialogFooter",
"DialogTitle",
"DialogDescription"
],
"cssVars": [
"--vexa-background",
"--vexa-border",
"--vexa-surface-raised",
"--vexa-foreground",
"--vexa-muted-foreground",
"--vexa-accent",
"--vexa-ring"
],
"a11y": "Modal contract (§14.2): focus moves in and is trapped, Escape closes, focus returns to the trigger, background is inert. Requires a DialogTitle. Overlay/content animation respects reduced motion.",
"example": "<Dialog><DialogTrigger asChild><Button variant=\"secondary\">Open settings</Button></DialogTrigger><DialogContent><DialogHeader><DialogTitle>Settings</DialogTitle><DialogDescription>Update your workspace preferences.</DialogDescription></DialogHeader></DialogContent></Dialog>",
"tests": "registry/tests/dialog.test.tsx"
}
}Detalles
Accesibilidad
Modal contract (§14.2): focus moves in and is trapped, Escape closes, focus returns to the trigger, background is inert. Requires a DialogTitle. Overlay/content animation respects reduced motion.