[]previous and next page

Dialog

shadcn's Dialog re-exports the Radix parts and adds styled DialogContent (portal, overlay, close button), DialogHeader, DialogFooter, DialogTitle and…

For agentsUpdated

shadcn's Dialog re-exports the Radix parts and adds styled DialogContent (portal, overlay, close button), DialogHeader, DialogFooter, DialogTitle and DialogDescription. The port is the same file over the ShadowJS primitive. Everything behavioral comes from Dialog: the focus trap, scroll lock, aria-hidden on the page behind, Escape, outside press and focus return.

src/ui/dialog.tsxtsx
import { splitProps } from "@shadow-js/core";
import { Dialog as DialogPrimitive, mergeElementProps, VisuallyHidden } from "@shadow-js/primitives";
import type { JSX } from "@shadow-js/web";
import { XIcon } from "./icons";

export const Dialog = DialogPrimitive;
export const DialogTrigger = DialogPrimitive.Trigger;
export const DialogPortal = DialogPrimitive.Portal;
export const DialogClose = DialogPrimitive.Close;

export function DialogOverlay(props: JSX.IntrinsicElements["div"]) {
  return (
    <DialogPrimitive.Overlay
      {...mergeElementProps(
        {
          class:
            "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
        },
        props,
      )}
    />
  );
}

export function DialogContent(props: JSX.IntrinsicElements["div"]) {
  const [own, rest] = splitProps(props, ["children"]);
  return (
    <DialogPortal>
      <DialogOverlay />
      <DialogPrimitive.Content
        {...mergeElementProps(
          {
            class:
              "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 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out sm:rounded-lg",
          },
          rest,
        )}
      >
        {own.children}
        <DialogPrimitive.Close class="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
          <XIcon class="h-4 w-4" />
          <VisuallyHidden>Close</VisuallyHidden>
        </DialogPrimitive.Close>
      </DialogPrimitive.Content>
    </DialogPortal>
  );
}

export function DialogHeader(props: JSX.IntrinsicElements["div"]) {
  return <div {...mergeElementProps({ class: "flex flex-col space-y-1.5 text-center sm:text-left" }, props)} />;
}

export function DialogFooter(props: JSX.IntrinsicElements["div"]) {
  return (
    <div
      {...mergeElementProps({ class: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2" }, props)}
    />
  );
}

export function DialogTitle(props: JSX.IntrinsicElements["h2"]) {
  return (
    <DialogPrimitive.Title
      {...mergeElementProps({ class: "text-lg font-semibold leading-none tracking-tight" }, props)}
    />
  );
}

export function DialogDescription(props: JSX.IntrinsicElements["p"]) {
  return <DialogPrimitive.Description {...mergeElementProps({ class: "text-sm text-muted-foreground" }, props)} />;
}