{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dialog",
  "type": "registry:ui",
  "title": "Dialog",
  "description": "Modal dialog for Tile UI.",
  "dependencies": [
    "@radix-ui/react-slot",
    "react-dom"
  ],
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/core",
    "button",
    "@tile-ui/portal",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/dialog/dialog.tsx",
      "type": "registry:ui",
      "target": "components/ui/dialog/dialog.tsx",
      "content": "import React, { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Slot } from '@radix-ui/react-slot';\nimport { dialogStyleKeys, getDialogState } from '../lib/core';\nimport type { DialogBaseProps } from '../lib/core';\nimport { Button } from '../button';\nimport { usePortalContainer, type PortalContainer } from '../portal';\nimport styles from './dialog.module.scss';\n\ninterface DialogContextValue {\n\topen: boolean;\n\tsetOpen: (open: boolean) => void;\n\tclose: () => void;\n\ttitleId: string;\n\tdescriptionId: string;\n}\n\nconst DialogContext = createContext<DialogContextValue | null>(null);\n\nfunction useDialogContext(): DialogContextValue {\n\tconst context = useContext(DialogContext);\n\tif (!context) {\n\t\tthrow new Error('Dialog sub-components must be used within <Dialog>.');\n\t}\n\treturn context;\n}\n\nexport interface DialogProps extends DialogBaseProps {\n\topen?: boolean;\n\tdefaultOpen?: boolean;\n\tonOpenChange?: (open: boolean) => void;\n\tchildren?: React.ReactNode;\n}\n\nfunction Dialog({ open, defaultOpen = false, onOpenChange, children }: DialogProps) {\n\tconst [internalOpen, setInternalOpen] = useState(defaultOpen);\n\tconst onOpenChangeRef = useRef(onOpenChange);\n\n\tuseEffect(() => {\n\t\tonOpenChangeRef.current = onOpenChange;\n\t});\n\n\tconst setOpen = useCallback(\n\t\t(next: boolean) => {\n\t\t\tif (open === undefined) {\n\t\t\t\tsetInternalOpen(next);\n\t\t\t}\n\n\t\t\tonOpenChangeRef.current?.(next);\n\t\t},\n\t\t[open],\n\t);\n\n\tconst close = useCallback(() => {\n\t\tsetOpen(false);\n\t}, [setOpen]);\n\n\tconst isOpen = open !== undefined ? open : internalOpen;\n\tconst titleId = useId();\n\tconst descriptionId = useId();\n\n\treturn <DialogContext.Provider value={{ open: isOpen, setOpen, close, titleId, descriptionId }}>{children}</DialogContext.Provider>;\n}\n\nDialog.displayName = 'Dialog';\n\nexport interface DialogTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n\tasChild?: boolean;\n}\n\nconst DialogTrigger = React.forwardRef<HTMLButtonElement, DialogTriggerProps>(({ asChild = false, className = '', children, onClick, ...props }, ref) => {\n\tconst context = useDialogContext();\n\tconst Comp = asChild ? Slot : 'button';\n\n\treturn (\n\t\t<Comp\n\t\t\tref={ref}\n\t\t\ttype={asChild ? undefined : 'button'}\n\t\t\tclassName={className}\n\t\t\tonClick={(event: React.MouseEvent<HTMLButtonElement>) => {\n\t\t\t\tonClick?.(event);\n\t\t\t\tcontext.setOpen(true);\n\t\t\t}}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t</Comp>\n\t);\n});\nDialogTrigger.displayName = 'DialogTrigger';\n\nexport interface DialogCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n\tasChild?: boolean;\n}\n\nconst DialogClose = React.forwardRef<HTMLButtonElement, DialogCloseProps>(({ asChild = false, className = '', children, onClick, ...props }, ref) => {\n\tconst context = useDialogContext();\n\tconst Comp = asChild ? Slot : 'button';\n\n\treturn (\n\t\t<Comp\n\t\t\tref={ref}\n\t\t\ttype={asChild ? undefined : 'button'}\n\t\t\tclassName={className}\n\t\t\tonClick={(event: React.MouseEvent<HTMLButtonElement>) => {\n\t\t\t\tonClick?.(event);\n\t\t\t\tcontext.close();\n\t\t\t}}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t</Comp>\n\t);\n});\nDialogClose.displayName = 'DialogClose';\n\nexport interface DialogOverlayProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlayProps>(({ className = '', onClick, ...props }, ref) => {\n\tconst context = useDialogContext();\n\n\tif (!context.open) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tref={ref}\n\t\t\tdata-state={getDialogState(context.open)}\n\t\t\tclassName={`${styles[dialogStyleKeys.overlay]} ${className}`}\n\t\t\tonClick={(event: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\tonClick?.(event);\n\t\t\t\tcontext.close();\n\t\t\t}}\n\t\t\t{...props}\n\t\t/>\n\t);\n});\nDialogOverlay.displayName = 'DialogOverlay';\n\nexport interface DialogContentProps extends React.HTMLAttributes<HTMLDivElement> {\n\tshowCloseButton?: boolean;\n\tcontainer?: PortalContainer;\n}\n\nconst DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(({ className = '', showCloseButton = true, container, children, ...props }, ref) => {\n\tconst { open, close, titleId, descriptionId } = useDialogContext();\n\tconst portalContainer = usePortalContainer(container);\n\tconst contentRef = useRef<HTMLDivElement | null>(null);\n\n\tfunction setContentRef(element: HTMLDivElement | null) {\n\t\tcontentRef.current = element;\n\n\t\tif (typeof ref === 'function') {\n\t\t\tref(element);\n\t\t} else if (ref) {\n\t\t\tref.current = element;\n\t\t}\n\t}\n\n\tuseLayoutEffect(() => {\n\t\tif (!open) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst previouslyFocused = document.activeElement as HTMLElement | null;\n\t\tconst previousOverflow = document.body.style.overflow;\n\t\tdocument.body.style.overflow = 'hidden';\n\n\t\tconst handleKeyDown = (event: KeyboardEvent) => {\n\t\t\tif (event.key === 'Escape') {\n\t\t\t\tclose();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.key === 'Tab') {\n\t\t\t\tconst container = contentRef.current;\n\t\t\t\tif (!container) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst focusables = Array.from(\n\t\t\t\t\tcontainer.querySelectorAll<HTMLElement>(\n\t\t\t\t\t\t'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n\t\t\t\t\t),\n\t\t\t\t).filter((el) => el.getClientRects().length > 0);\n\t\t\t\tif (focusables.length === 0) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst first = focusables[0];\n\t\t\t\tconst last = focusables[focusables.length - 1];\n\t\t\t\tconst active = document.activeElement;\n\n\t\t\t\tif (event.shiftKey) {\n\t\t\t\t\tif (active === first || active === container || !container.contains(active)) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tlast.focus();\n\t\t\t\t\t}\n\t\t\t\t} else if (active === last || !container.contains(active)) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tfirst.focus();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener('keydown', handleKeyDown);\n\n\t\tconst contentEl = contentRef.current;\n\t\tif (contentEl) {\n\t\t\tcontentEl.focus();\n\t\t}\n\n\t\treturn () => {\n\t\t\tdocument.body.style.overflow = previousOverflow;\n\t\t\tdocument.removeEventListener('keydown', handleKeyDown);\n\t\t\tpreviouslyFocused?.focus();\n\t\t};\n\t}, [close, open]);\n\n\tif (!open || !portalContainer) {\n\t\treturn null;\n\t}\n\n\treturn createPortal(\n\t\t<>\n\t\t\t<DialogOverlay />\n\t\t\t<div\n\t\t\t\tref={setContentRef}\n\t\t\t\trole=\"dialog\"\n\t\t\t\taria-modal=\"true\"\n\t\t\t\taria-labelledby={titleId}\n\t\t\t\taria-describedby={descriptionId}\n\t\t\t\ttabIndex={-1}\n\t\t\t\tdata-state={getDialogState(open)}\n\t\t\t\tclassName={`${styles[dialogStyleKeys.content]} ${className}`}\n\t\t\t\t{...props}>\n\t\t\t\t{children}\n\t\t\t\t{showCloseButton && (\n\t\t\t\t\t<button type=\"button\" aria-label=\"关闭\" className={styles[dialogStyleKeys.close]} onClick={() => close()}>\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tclassName={styles[dialogStyleKeys.xIcon]}\n\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\twidth=\"16\"\n\t\t\t\t\t\t\theight=\"16\"\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t\t\t<path d=\"M18 6 6 18\" />\n\t\t\t\t\t\t\t<path d=\"m6 6 12 12\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</>,\n\t\tportalContainer,\n\t);\n});\nDialogContent.displayName = 'DialogContent';\n\nexport interface DialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst DialogHeader = React.forwardRef<HTMLDivElement, DialogHeaderProps>(({ className = '', children, ...props }, ref) => {\n\treturn (\n\t\t<div ref={ref} className={`${styles[dialogStyleKeys.header]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</div>\n\t);\n});\nDialogHeader.displayName = 'DialogHeader';\n\nexport interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n\t/** 是否在底部渲染一个「关闭」按钮 */\n\tshowCloseButton?: boolean;\n}\n\nconst DialogFooter = React.forwardRef<HTMLDivElement, DialogFooterProps>(({ className = '', showCloseButton = false, children, ...props }, ref) => {\n\tconst context = useDialogContext();\n\n\treturn (\n\t\t<div ref={ref} className={`${styles[dialogStyleKeys.footer]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t\t{showCloseButton && (\n\t\t\t\t<Button type=\"button\" variant=\"outline\" onClick={() => context.close()}>\n\t\t\t\t\tClose\n\t\t\t\t</Button>\n\t\t\t)}\n\t\t</div>\n\t);\n});\nDialogFooter.displayName = 'DialogFooter';\n\nexport interface DialogTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {}\n\nconst DialogTitle = React.forwardRef<HTMLHeadingElement, DialogTitleProps>(({ className = '', children, ...props }, ref) => {\n\tconst context = useDialogContext();\n\n\treturn (\n\t\t<h2 ref={ref} id={context.titleId} className={`${styles[dialogStyleKeys.title]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</h2>\n\t);\n});\nDialogTitle.displayName = 'DialogTitle';\n\nexport interface DialogDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}\n\nconst DialogDescription = React.forwardRef<HTMLParagraphElement, DialogDescriptionProps>(({ className = '', children, ...props }, ref) => {\n\tconst context = useDialogContext();\n\n\treturn (\n\t\t<p ref={ref} id={context.descriptionId} className={`${styles[dialogStyleKeys.description]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</p>\n\t);\n});\nDialogDescription.displayName = 'DialogDescription';\n\nexport { Dialog, DialogTrigger, DialogClose, DialogOverlay, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription };\nexport default Dialog;\n"
    },
    {
      "path": "packages/styles/scss/components/dialog.module.scss",
      "type": "registry:file",
      "target": "components/ui/dialog/dialog.module.scss",
      "content": "@use '../../../styles/variables/colors' as *;\n\n// 半透明遮罩\n.overlay {\n\tposition: fixed;\n\tinset: 0;\n\tz-index: $z-modal;\n\tbackground-color: var(--tile-dialog-overlay, $overlay);\n\n\t&[data-state='open'] {\n\t\tanimation: dialog-overlay-in $transition-normal;\n\t}\n\n\t&[data-state='closed'] {\n\t\topacity: 0;\n\t}\n}\n\n@keyframes dialog-overlay-in {\n\tfrom {\n\t\topacity: 0;\n\t}\n\n\tto {\n\t\topacity: 1;\n\t}\n}\n\n// 弹层内容面板\n.content {\n\tposition: fixed;\n\ttop: var(--tile-dialog-top, 50%);\n\tleft: 50%;\n\tz-index: $z-modal;\n\tdisplay: grid;\n\twidth: 100%;\n\tmax-width: calc(100% - 2rem);\n\ttransform: translate(-50%, var(--tile-dialog-translate-y, -50%));\n\tgap: $spacing-4;\n\tborder: 1px solid var(--tile-dialog-border, $border);\n\tborder-radius: var(--tile-dialog-radius, $radius-lg);\n\tbackground-color: var(--tile-dialog-background, $card);\n\tcolor: $card-foreground;\n\tpadding: var(--tile-dialog-padding, $spacing-6);\n\tbox-shadow: var(--tile-dialog-shadow, $shadow-lg);\n\toutline: none;\n\n\t@media (min-width: 640px) {\n\t\ttop: var(--tile-dialog-top-desktop, 50%);\n\t\tmax-width: var(--tile-dialog-max-width, 32rem);\n\t}\n\n\t&[data-state='open'] {\n\t\tanimation: dialog-content-in $transition-normal;\n\t}\n\n\t&[data-state='closed'] {\n\t\topacity: 0;\n\t\ttransform: translate(-50%, var(--tile-dialog-translate-y, -50%)) scale(0.95);\n\t}\n}\n\n@keyframes dialog-content-in {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translate(-50%, var(--tile-dialog-translate-y, -50%)) scale(0.95);\n\t}\n\n\tto {\n\t\topacity: 1;\n\t\ttransform: translate(-50%, var(--tile-dialog-translate-y, -50%)) scale(1);\n\t}\n}\n\n// 右上角关闭按钮\n.close {\n\tposition: absolute;\n\ttop: $spacing-4;\n\tright: $spacing-4;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: $spacing-1;\n\tborder: none;\n\tborder-radius: var(--tile-dialog-close-radius, $radius);\n\tbackground-color: transparent;\n\tcolor: var(--tile-dialog-close-color, $muted-foreground);\n\topacity: var(--tile-dialog-close-opacity, 0.7);\n\tcursor: pointer;\n\ttransition: opacity $transition-fast;\n\toutline: none;\n\n\t&:hover {\n\t\topacity: 1;\n\t}\n\n\t&:focus-visible {\n\t\tbox-shadow: var(--tile-dialog-close-focus-shadow, 0 0 0 2px $background, 0 0 0 4px $ring);\n\t}\n\n\t.xIcon {\n\t\twidth: 1rem;\n\t\theight: 1rem;\n\t\tpointer-events: none;\n\t}\n}\n\n.header {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: $spacing-2;\n\ttext-align: var(--tile-dialog-header-align, center);\n\n\t@media (min-width: 640px) {\n\t\ttext-align: left;\n\t}\n}\n\n.footer {\n\tdisplay: flex;\n\tflex-direction: column-reverse;\n\tgap: $spacing-2;\n\n\t@media (min-width: 640px) {\n\t\tflex-direction: row;\n\t\tjustify-content: flex-end;\n\t}\n}\n\n.title {\n\tmargin: 0;\n\tfont-size: var(--tile-dialog-title-size, $text-lg);\n\tfont-weight: $font-semibold;\n\tline-height: var(--tile-dialog-title-leading, $leading-none);\n\tcolor: $foreground;\n}\n\n.description {\n\tmargin: 0;\n\tfont-size: var(--tile-dialog-description-size, $text-sm);\n\tline-height: $leading-normal;\n\tcolor: var(--tile-dialog-description-color, $muted-foreground);\n}\n"
    },
    {
      "path": "packages/react/src/components/dialog/index.ts",
      "type": "registry:ui",
      "target": "components/ui/dialog/index.ts",
      "content": "export {\n\tDialog,\n\tDialogTrigger,\n\tDialogClose,\n\tDialogOverlay,\n\tDialogContent,\n\tDialogHeader,\n\tDialogFooter,\n\tDialogTitle,\n\tDialogDescription,\n\ttype DialogProps,\n\ttype DialogTriggerProps,\n\ttype DialogCloseProps,\n\ttype DialogOverlayProps,\n\ttype DialogContentProps,\n\ttype DialogHeaderProps,\n\ttype DialogFooterProps,\n\ttype DialogTitleProps,\n\ttype DialogDescriptionProps,\n} from './dialog';\n"
    }
  ]
}
