{
  "name": "dialog",
  "type": "registry:ui",
  "description": "Modal dialog with overlay, header, footer, and close parts",
  "files": [
    {
      "path": "ui/dialog.tsx",
      "type": "registry:ui",
      "content": "import { fv } from \"@facet-ui/react-variants\";\nimport { Dialog as DialogPrimitive } from \"@lattice-ui/react-dialog\";\nimport type { LayerInteractEvent } from \"@lattice-ui/react-layer\";\nimport { getPassthroughProps, type PassthroughProps, React, toSlotProps } from \"@lattice-ui/react-runtime\";\nimport { type ClassName, cn } from \"~/lib/utils\";\n\n/**\n * The first layered component, and the layering is entirely Lattice's: the\n * portal, the dim's own `ScreenGui`, the focus trap and the outside-press\n * dismissal all come from `@lattice-ui/react-dialog`. This file says what the\n * panel looks like and where it sits.\n *\n * **A `PortalProvider` has to be above this.** `Dialog.Portal` reads a strict\n * context for the `BasePlayerGui` it renders into, so an app that mounts a\n * dialog wraps its tree once:\n *\n * ```tsx\n * <PortalProvider container={Players.LocalPlayer.WaitForChild(\"PlayerGui\")}>\n *   <App />\n * </PortalProvider>\n * ```\n *\n * Without it the dialog throws on open rather than rendering nowhere, which is\n * the better of the two failures but still surprising the first time.\n *\n * One recipe object rather than seven exports: every exported name costs a\n * Luau register once Vela inlines its runtime. See\n * docs/decisions/luau-register-limit.md.\n */\nexport const dialogVariants = {\n  // The one class in the registry that names a colour instead of a role. A\n  // scrim is not a themed surface — it is the absence of light, black under\n  // shadcn's light theme and its dark one alike, and every role that stays dark\n  // in both modes stays dark by coincidence. `overlayClassName` is the way out.\n  // See docs/decisions/overlay-scrim.md.\n  overlay: fv(\"bg-black/50\"),\n  // `mx-auto my-auto` is the centring: Vela lowers each to `AnchorPoint` 0.5\n  // plus `Position` 0.5 on that axis. It works because the primitive's content\n  // host spans the layer and lays nothing out, so this frame positions itself\n  // inside it.\n  content: fv(\"flex-col gap-4 w-128 h-fit mx-auto my-auto rounded-lg border border-border bg-background p-6 shadow-lg\"),\n  close: fv(\"size-6 self-end rounded-md text-sm font-normal text-muted-foreground hover:bg-accent\"),\n  header: fv(\"flex-col w-full h-fit gap-2\"),\n  footer: fv(\"flex-row items-center justify-end w-full h-fit gap-2\"),\n  title: fv(\"w-full h-fit whitespace-normal leading-none 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\n/**\n * Re-exported unstyled, as shadcn does: the trigger is whatever the consumer\n * puts in it.\n *\n * ```tsx\n * <DialogTrigger asChild>\n *   <Button Text=\"Open\" />\n * </DialogTrigger>\n * ```\n *\n * Reached for bare it is a `textbutton` with Roblox's defaults neutralized and\n * no size of its own, so give it a `className` with both axes resolved.\n */\nexport const Dialog = DialogPrimitive.Root;\nexport const DialogTrigger = DialogPrimitive.Trigger;\nexport const DialogPortal = DialogPrimitive.Portal;\nexport const DialogClose = DialogPrimitive.Close;\n\nexport type DialogOverlayProps = {\n  className?: ClassName;\n} & PassthroughProps<TextButton>;\n\nexport type DialogContentProps = {\n  className?: ClassName;\n  /** Styles the dim behind the panel. `DialogContent` renders its own overlay. */\n  overlayClassName?: ClassName;\n  /** The ✕ in the panel's top-right. Pass `false` when the only way out is a footer button. */\n  showCloseButton?: boolean;\n  /** Fires before an outside press dismisses; `event.preventDefault()` keeps the dialog open. */\n  onPointerDownOutside?: (event: LayerInteractEvent) => void;\n  onInteractOutside?: (event: LayerInteractEvent) => void;\n  children?: React.ReactNode;\n} & PassthroughProps<Frame>;\n\nexport type DialogSectionProps = {\n  className?: ClassName;\n  children?: React.ReactNode;\n} & PassthroughProps<Frame>;\n\nexport type DialogTextProps = {\n  className?: ClassName;\n  Text?: string;\n} & PassthroughProps<TextLabel>;\n\nconst NEUTRAL_PROPS = {\n  BackgroundTransparency: 1,\n  BorderSizePixel: 0,\n};\n\nconst OVERLAY_OWN_PROPS = [\"className\", \"children\"] as const;\nconst CONTENT_OWN_PROPS = [\n  \"className\",\n  \"overlayClassName\",\n  \"showCloseButton\",\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;\n\n// The forwarded bag is widened by `toSlotProps` and then has `children`\n// dropped from its *type*: the overlay types `children` as the single element\n// `asChild` merges onto, and the bag never carries one — `children` is listed\n// as an own prop — so only the type needs narrowing.\nfunction forwardProps(props: object, ownKeys: readonly string[]): { key?: React.Key } & { [index: string]: unknown } {\n  return toSlotProps(getPassthroughProps(props, ownKeys));\n}\n\nexport function DialogOverlay(props: DialogOverlayProps) {\n  return (\n    <DialogPrimitive.Overlay\n      className={cn(dialogVariants.overlay({ className: props.className }))}\n      {...forwardProps(props, OVERLAY_OWN_PROPS)}\n    />\n  );\n}\n\n/**\n * Portal, overlay and panel in one part, like shadcn's — the composition is the\n * same every time, and `DialogOverlay` is exported for the time it is not.\n *\n * The panel is a frame *inside* `Dialog.Content` rather than `Dialog.Content`\n * itself, and that is structural rather than stylistic. The primitive forces\n * `Size` on its own host so the layer spans the screen, and it takes the first\n * host element under it as the boundary an outside press is measured against.\n * A `className` here would fight the first and — through the `UICorner` Vela\n * prepends for `rounded-lg` — quietly become the second.\n */\nexport function DialogContent(props: DialogContentProps) {\n  return (\n    <DialogPrimitive.Portal>\n      {/* The primitive rather than `DialogOverlay`, so `overlayClassName` and\n          the recipe meet in one `className` expression. Vela resolves a\n          `className` at the call site and hands the component the resolved\n          properties instead of the string, so routing it through a second\n          component would let this file's `bg-black/80` land on top of the\n          consumer's — the same trap `TextSlot` avoids by taking no `className`\n          at all. `overlayClassName` is not spelled `className`, so it arrives\n          here intact. */}\n      <DialogPrimitive.Overlay className={cn(dialogVariants.overlay({ className: props.overlayClassName }))} />\n      <DialogPrimitive.Content\n        onInteractOutside={props.onInteractOutside}\n        onPointerDownOutside={props.onPointerDownOutside}\n      >\n        <frame\n          className={cn(dialogVariants.content({ className: props.className }))}\n          {...NEUTRAL_PROPS}\n          {...getPassthroughProps<Frame>(props, CONTENT_OWN_PROPS)}\n        >\n          {/* Not the corner overlay shadcn draws: a `UIListLayout` positions\n              every child it has, so a floating ✕ inside a `flex-col` panel is\n              not expressible without a second frame to escape the layout. It\n              takes its own line at the top instead, pushed right by `self-end`\n              (a `UIFlexItem`), and the glyph is text — replace it to use your\n              own artwork. */}\n          {props.showCloseButton === false ? undefined : (\n            <DialogPrimitive.Close className={cn(dialogVariants.close())} Text=\"✕\" />\n          )}\n          {props.children}\n        </frame>\n      </DialogPrimitive.Content>\n    </DialogPrimitive.Portal>\n  );\n}\n\nexport function DialogHeader(props: DialogSectionProps) {\n  return (\n    <frame\n      className={cn(dialogVariants.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 DialogFooter(props: DialogSectionProps) {\n  return (\n    <frame\n      className={cn(dialogVariants.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 DialogTitle(props: DialogTextProps) {\n  return (\n    <textlabel\n      className={cn(dialogVariants.title({ className: props.className }))}\n      Text={props.Text ?? \"\"}\n      {...NEUTRAL_PROPS}\n      {...getPassthroughProps<TextLabel>(props, TEXT_OWN_PROPS)}\n    />\n  );\n}\n\nexport function DialogDescription(props: DialogTextProps) {\n  return (\n    <textlabel\n      className={cn(dialogVariants.description({ className: props.className }))}\n      Text={props.Text ?? \"\"}\n      {...NEUTRAL_PROPS}\n      {...getPassthroughProps<TextLabel>(props, TEXT_OWN_PROPS)}\n    />\n  );\n}\n"
    }
  ],
  "registryDependencies": [
    "utils"
  ],
  "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",
    "accent"
  ]
}
