{
  "name": "alert-dialog",
  "type": "registry:ui",
  "description": "Confirmation dialog that cannot be dismissed without answering",
  "files": [
    {
      "path": "ui/alert-dialog.tsx",
      "type": "registry:ui",
      "content": "import { fv, type VariantProps } from \"@facet-ui/react-variants\";\nimport { Dialog as DialogPrimitive } from \"@lattice-ui/react-dialog\";\nimport type { LayerInteractEvent } from \"@lattice-ui/react-layer\";\nimport {\n  composeEvents,\n  getPassthroughProps,\n  type PassthroughProps,\n  React,\n  toSlotProps,\n} from \"@lattice-ui/react-runtime\";\nimport { TextSlot } from \"~/lib/text\";\nimport { type ClassName, cn } from \"~/lib/utils\";\nimport { buttonLabelVariants, buttonVariants } from \"~/ui/button\";\n\n/**\n * `dialog` again, with the two things that make an alert dialog one: no ✕, and\n * no way to leave without answering. A destructive confirm that a stray click\n * dismisses is a dialog wearing a different name.\n *\n * **The dim is a child of `Content` here, not `Dialog.Overlay`.** That is the\n * whole trick, and it is worth understanding before editing it.\n *\n * `Dialog.Overlay` is a `textbutton` that closes the dialog on `Activated`, and\n * the primitive composes that handler in itself — a consumer cannot take it off.\n * Built on it, this would dismiss on a stray click no matter what else we did.\n *\n * What replaces it leans on how the layer decides a press is \"outside\". It looks\n * for a content boundary among `Content`'s **host** children, and a Facet child\n * is never one: every element carrying a `className` compiles to Vela's runtime\n * host, which is a component. So the boundary falls back to the layer's own\n * full-screen canvas, and a press counts as inside when anything under the\n * pointer is a *descendant* of that canvas.\n *\n * `dialog`'s panel is such a descendant over its own rect, which is exactly why\n * pressing its dim dismisses it. This one adds a descendant that covers the\n * screen, so every press lands on content and the dismissal path never runs.\n * That same frame is the dim. The panel is the second child and says `z-10` so\n * it draws above rather than by sibling order.\n *\n * Everything else — the portal, the focus trap, presence — is Lattice's, and a\n * `PortalProvider` has to sit above the app exactly as it does for `dialog`.\n *\n * One recipe object rather than six exports: every exported name costs a Luau\n * register. See docs/decisions/luau-register-limit.md.\n */\nexport const alertDialogVariants = {\n  // `size-full` is not decoration — it is what leaves no position where the\n  // press misses. Shrink it and the alert dialog becomes dismissable again.\n  //\n  // `bg-black/80` is the one class in the registry that names a colour instead\n  // of a role, for the reason `dialog` gives: a scrim is the absence of light in\n  // both themes. See docs/decisions/overlay-scrim.md.\n  overlay: fv(\"size-full bg-black/50\"),\n  content: fv(\n    \"flex-col gap-4 w-128 h-fit mx-auto my-auto z-10 rounded-lg border border-border bg-background p-6 shadow-lg\",\n  ),\n  header: fv(\"flex-col w-full h-fit gap-2\"),\n  // The buttons sit at the end of the line, cancel first, as they do on the web.\n  footer: fv(\"flex-row items-center justify-end w-full h-fit gap-2\"),\n  title: fv(\"w-full h-fit whitespace-normal text-left text-lg font-semibold text-foreground\"),\n  description: fv(\"w-full h-fit whitespace-normal leading-tight text-left text-sm font-normal text-muted-foreground\"),\n};\n\nexport const AlertDialog = DialogPrimitive.Root;\nexport const AlertDialogTrigger = DialogPrimitive.Trigger;\nexport const AlertDialogPortal = DialogPrimitive.Portal;\n\nexport type AlertDialogContentProps = {\n  className?: ClassName;\n  /** Styles the dim behind the panel. `AlertDialogContent` renders its own. */\n  overlayClassName?: ClassName;\n  /**\n   * Fires when a press lands outside the panel. The dialog does not dismiss on\n   * one — the dim absorbs it — so this is a notification, not a veto, and\n   * `event.preventDefault()` has nothing left to prevent.\n   */\n  onPointerDownOutside?: (event: LayerInteractEvent) => void;\n  onInteractOutside?: (event: LayerInteractEvent) => void;\n  children?: React.ReactNode;\n} & PassthroughProps<Frame>;\n\nexport type AlertDialogSectionProps = {\n  className?: ClassName;\n  children?: React.ReactNode;\n} & PassthroughProps<Frame>;\n\nexport type AlertDialogTextProps = {\n  className?: ClassName;\n  Text?: string;\n} & PassthroughProps<TextLabel>;\n\ntype ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>[\"variant\"]>;\n\n/**\n * The two buttons are `Dialog.Close` wearing `button`'s recipes, which is what\n * shadcn does and why this entry depends on `button`: a project that restyles\n * its buttons restyles these with them.\n *\n * `variant` and `size` are `button`'s own, so a cancel can be `ghost` and an\n * action can be `destructive` without this file knowing what either means.\n */\nexport type AlertDialogActionProps = VariantProps<typeof buttonVariants> & {\n  className?: ClassName;\n  Text?: string;\n  /** Runs before the dialog closes. Closing is the primitive's and always happens. */\n  onClick?: () => void;\n  children?: React.ReactNode;\n} & PassthroughProps<TextButton>;\n\nconst NEUTRAL_PROPS = {\n  BackgroundTransparency: 1,\n  BorderSizePixel: 0,\n};\n\nconst CONTENT_OWN_PROPS = [\n  \"className\",\n  \"overlayClassName\",\n  \"onPointerDownOutside\",\n  \"onInteractOutside\",\n  \"children\",\n] as const;\nconst SECTION_OWN_PROPS = [\"className\", \"children\"] as const;\nconst TEXT_OWN_PROPS = [\"className\", \"Text\"] as const;\nconst CLOSE_BUTTON_OWN_PROPS = [\n  \"variant\",\n  \"size\",\n  \"className\",\n  \"Text\",\n  \"onClick\",\n  \"children\",\n  \"fallbackVariant\",\n] as const;\n\nexport function AlertDialogContent(props: AlertDialogContentProps) {\n  return (\n    <DialogPrimitive.Portal>\n      <DialogPrimitive.Content\n        onInteractOutside={props.onInteractOutside}\n        onPointerDownOutside={props.onPointerDownOutside}\n      >\n        {/* The dim, and the reason no press reads as outside. Both roles belong\n            to this one frame — see the note above the recipe. */}\n        <frame className={cn(alertDialogVariants.overlay({ className: props.overlayClassName }))} BorderSizePixel={0} />\n        <frame\n          className={cn(alertDialogVariants.content({ className: props.className }))}\n          {...NEUTRAL_PROPS}\n          {...getPassthroughProps<Frame>(props, CONTENT_OWN_PROPS)}\n        >\n          {props.children}\n        </frame>\n      </DialogPrimitive.Content>\n    </DialogPrimitive.Portal>\n  );\n}\n\nexport function AlertDialogHeader(props: AlertDialogSectionProps) {\n  return (\n    <frame\n      className={cn(alertDialogVariants.header({ className: props.className }))}\n      {...NEUTRAL_PROPS}\n      {...getPassthroughProps<Frame>(props, SECTION_OWN_PROPS)}\n    >\n      {props.children}\n    </frame>\n  );\n}\n\nexport function AlertDialogFooter(props: AlertDialogSectionProps) {\n  return (\n    <frame\n      className={cn(alertDialogVariants.footer({ className: props.className }))}\n      {...NEUTRAL_PROPS}\n      {...getPassthroughProps<Frame>(props, SECTION_OWN_PROPS)}\n    >\n      {props.children}\n    </frame>\n  );\n}\n\nexport function AlertDialogTitle(props: AlertDialogTextProps) {\n  return (\n    <textlabel\n      className={cn(alertDialogVariants.title({ className: props.className }))}\n      Text={props.Text ?? \"\"}\n      {...NEUTRAL_PROPS}\n      {...getPassthroughProps<TextLabel>(props, TEXT_OWN_PROPS)}\n    />\n  );\n}\n\nexport function AlertDialogDescription(props: AlertDialogTextProps) {\n  return (\n    <textlabel\n      className={cn(alertDialogVariants.description({ className: props.className }))}\n      Text={props.Text ?? \"\"}\n      {...NEUTRAL_PROPS}\n      {...getPassthroughProps<TextLabel>(props, TEXT_OWN_PROPS)}\n    />\n  );\n}\n\n/**\n * Both buttons, differing only in which variant they fall back to. Written once\n * because the alternative is thirty duplicated lines that have to stay in step.\n *\n * The label is a child `textlabel` with its own recipe, exactly as in `button`:\n * `Dialog.Close` clears its own `Text`, and nothing inherits, so a class on the\n * button never reaches the string. `buttonLabelVariants` is why this is not\n * `buttonVariants` twice.\n */\nfunction AlertDialogCloseButton(props: AlertDialogActionProps & { fallbackVariant: ButtonVariant }) {\n  const variant = props.variant ?? props.fallbackVariant;\n\n  const handleActivated = React.useCallback(() => {\n    props.onClick?.();\n  }, [props.onClick]);\n\n  const passthrough = getPassthroughProps<TextButton>(props, CLOSE_BUTTON_OWN_PROPS);\n\n  return (\n    <DialogPrimitive.Close\n      className={cn(buttonVariants({ variant, size: props.size, className: props.className }))}\n      // `toSlotProps` is the crossing point: the typed bag carries a\n      // `Ref<TextButton>`, and the runtime host Vela wraps this call site in\n      // types its own `ref` as `Ref<unknown>`. Widening here is what the roadmap\n      // calls the boundary a forwarded bag crosses.\n      {...toSlotProps(passthrough)}\n      Event={composeEvents(passthrough.Event, { Activated: handleActivated })}\n    >\n      <TextSlot Text={props.Text} className={buttonLabelVariants({ variant, size: props.size })}>\n        {props.children}\n      </TextSlot>\n    </DialogPrimitive.Close>\n  );\n}\n\nexport function AlertDialogAction(props: AlertDialogActionProps) {\n  return <AlertDialogCloseButton {...props} fallbackVariant=\"default\" />;\n}\n\n/**\n * `outline` by default, so the destructive answer is never the quiet one. Pass\n * `variant` to say otherwise.\n */\nexport function AlertDialogCancel(props: AlertDialogActionProps) {\n  return <AlertDialogCloseButton {...props} fallbackVariant=\"outline\" />;\n}\n"
    }
  ],
  "registryDependencies": [
    "utils",
    "text",
    "button"
  ],
  "dependencies": [
    "@facet-ui/react-variants@^0.1.1",
    "@lattice-ui/react-runtime@^0.8.0",
    "@lattice-ui/react-dialog@^0.8.0",
    "@lattice-ui/react-layer@^0.8.0"
  ],
  "providers": [
    {
      "name": "PortalProvider",
      "package": "@lattice-ui/react-layer",
      "props": {
        "container": "player-gui"
      },
      "reason": "Lattice reads the portal target from it, and throws when a dialog opens without one"
    }
  ],
  "tokens": [
    "background",
    "border",
    "foreground",
    "muted-foreground",
    "primary",
    "primary-foreground",
    "destructive",
    "destructive-foreground",
    "secondary",
    "secondary-foreground",
    "accent",
    "accent-foreground",
    "input"
  ]
}
