Saltar al contenido
VEXA
Overlays

Dialog

Hoja cliente

A 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.json

Se 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.