{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dialog",
  "type": "registry:ui",
  "title": "Dialog",
  "description": "현재 작업 위에 떠서 사용자의 입력이나 확인을 받는 모달입니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/button",
    "@atom/icons",
    "@atom/lib-cn",
    "@atom/lib-context"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/dialog.tsx",
      "content": "\"use client\";\n\nimport {\n  Children,\n  type HTMLAttributes,\n  type KeyboardEvent as ReactKeyboardEvent,\n  type ReactNode,\n  type Ref,\n  type RefObject,\n  isValidElement,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/cn\";\nimport { createStrictContext } from \"../lib/context\";\nimport { Button } from \"./button\";\nimport { CloseIcon } from \"./icons\";\n\n/* -------------------------------------------------------------------------- */\n/*  Dialog (components/dialog.md, styles/atom/elements/dialog.css)            */\n/*    <Dialog open={open} onOpenChange={setOpen}                              */\n/*            title=\"이 항목을 삭제하면 되돌릴 수 없어요\"                          */\n/*            description=\"삭제한 글은 복구할 수 없어요.\" dismissOnScrim={false}>  */\n/*      <Dialog.Footer>                                                       */\n/*        <Button variant=\"neutralWeak\" onClick={close}>취소</Button>          */\n/*        <Button variant=\"criticalSolid\">삭제</Button>                        */\n/*      </Dialog.Footer>                                                      */\n/*    </Dialog>                                                               */\n/* -------------------------------------------------------------------------- */\n\nexport type DialogSize = \"medium\" | \"large\";\nexport type DialogRole = \"dialog\" | \"alertdialog\";\n\nconst B = \"atom-dialog\";\n\nconst [DialogContext, useDialogContext] = createStrictContext<{ stackActions: boolean }>(\"Dialog\");\n\nexport type DialogProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\" | \"role\"> & {\n  ref?: Ref<HTMLDivElement>;\n  /** 열림 상태입니다. Scrim 의 `data-state` 로 내려갑니다. */\n  open?: boolean;\n  /** 제목입니다. `<h2 class=\"atom-dialog__title\">` 로 렌더하고 aria-labelledby 가 가리킵니다. */\n  title: ReactNode;\n  /** 제목 아래 설명입니다. Body 첫 문단(atom-dialog__description)이 됩니다. */\n  description?: ReactNode;\n  /** 최대 폭 480 · 800px 입니다. */\n  size?: DialogSize;\n  /** 확인이 강제되는 경고면 `alertdialog`. */\n  role?: DialogRole;\n  /** 헤더 닫기 버튼입니다. `alertdialog` 는 `false`. */\n  showClose?: boolean;\n  /** Scrim 클릭으로 닫습니다. 파괴적 작업과 `alertdialog` 는 `false`. */\n  dismissOnScrim?: boolean;\n  /** `Esc` 로 닫습니다. */\n  dismissOnEsc?: boolean;\n  /** Footer 를 세로로 쌓습니다. 주요 액션이 맨 아래로 갑니다. */\n  stackActions?: boolean;\n  /** 열림 상태가 바뀔 때 호출됩니다. */\n  onOpenChange?: (open: boolean) => void;\n  /** Scrim 을 뷰포트가 아니라 가장 가까운 positioned 조상 안에 가둡니다(`atom-dialog-scrim--contained`). 포털을 쓰지 않습니다. */\n  contained?: boolean;\n};\n\nconst FOCUSABLE =\n  'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), iframe, [contenteditable=\"true\"], [tabindex]:not([tabindex=\"-1\"])';\n\nfunction focusables(root: HTMLElement): HTMLElement[] {\n  return [...root.querySelectorAll<HTMLElement>(FOCUSABLE)].filter((el) => !el.closest(\"[inert]\"));\n}\n\n/**\n * 모달 진입/퇴장: 트리거를 기억 → (포털 모드면) 배경 inert + 스크롤 잠금 → Panel 로 포커스.\n * 닫을 때는 inert 를 먼저 풀고 트리거로 포커스를 돌립니다(inert 요소는 포커스를 받지 못함).\n */\nfunction useModal(\n  active: boolean,\n  isolate: boolean,\n  scrim: RefObject<HTMLElement | null>,\n  panel: RefObject<HTMLElement | null>,\n) {\n  useEffect(() => {\n    if (!active) return;\n    const returnTo = document.activeElement as HTMLElement | null;\n    const changed: Element[] = [];\n    let overflow: string | null = null;\n    if (isolate) {\n      const node = scrim.current;\n      for (const el of document.body.children) {\n        if ((node && el.contains(node)) || el.hasAttribute(\"inert\") || el.tagName === \"SCRIPT\") continue;\n        el.setAttribute(\"inert\", \"\");\n        changed.push(el);\n      }\n      overflow = document.body.style.overflow;\n      document.body.style.overflow = \"hidden\";\n    }\n    panel.current?.focus({ preventScroll: true });\n    return () => {\n      for (const el of changed) el.removeAttribute(\"inert\");\n      if (overflow !== null) document.body.style.overflow = overflow;\n      if (returnTo && returnTo.isConnected && returnTo !== document.body) returnTo.focus();\n    };\n  }, [active, isolate, scrim, panel]);\n}\n\nfunction DialogRoot({\n  ref,\n  open = false,\n  title,\n  description,\n  size = \"medium\",\n  role = \"dialog\",\n  showClose = true,\n  dismissOnScrim = true,\n  dismissOnEsc = true,\n  stackActions = false,\n  onOpenChange,\n  contained = false,\n  className,\n  children,\n  onKeyDown,\n  ...rest\n}: DialogProps) {\n  const id = useId();\n  const titleId = `${id}-title`;\n  const bodyId = `${id}-desc`;\n  const scrimRef = useRef<HTMLDivElement>(null);\n  const panelRef = useRef<HTMLDivElement>(null);\n  const bodyRef = useRef<HTMLDivElement>(null);\n  const pressedOnScrim = useRef(false);\n  const [bodyScroll, setBodyScroll] = useState(false);\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => setMounted(true), []);\n\n  const close = () => onOpenChange?.(false);\n  const closeRef = useRef(close);\n  useLayoutEffect(() => {\n    closeRef.current = close;\n  });\n\n  useModal(open && mounted, !contained, scrimRef, panelRef);\n\n  // Esc — 포커스가 어디 있든(스크림 클릭 직후 등) 받습니다.\n  useEffect(() => {\n    if (!open || !dismissOnEsc) return;\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\" && !e.defaultPrevented) {\n        e.preventDefault();\n        closeRef.current();\n      }\n    };\n    document.addEventListener(\"keydown\", onKey);\n    return () => document.removeEventListener(\"keydown\", onKey);\n  }, [open, dismissOnEsc]);\n\n  // Body 가 실제로 넘칠 때만 data-body-scroll\n  useEffect(() => {\n    const body = bodyRef.current;\n    if (!open || !body) return;\n    const measure = () => setBodyScroll(body.scrollHeight > body.clientHeight + 1);\n    measure();\n    if (typeof ResizeObserver === \"undefined\") return;\n    const ro = new ResizeObserver(measure);\n    ro.observe(body);\n    for (const child of body.children) ro.observe(child);\n    return () => ro.disconnect();\n  }, [open, mounted, children, description]);\n\n  const trapTab = (e: ReactKeyboardEvent<HTMLDivElement>) => {\n    onKeyDown?.(e);\n    if (e.key !== \"Tab\" || e.defaultPrevented || !panelRef.current) return;\n    const list = focusables(panelRef.current);\n    if (list.length === 0) {\n      e.preventDefault();\n      panelRef.current.focus();\n      return;\n    }\n    const first = list[0]!;\n    const last = list[list.length - 1]!;\n    const active = document.activeElement;\n    if (e.shiftKey && (active === first || active === panelRef.current)) {\n      e.preventDefault();\n      last.focus();\n    } else if (!e.shiftKey && active === last) {\n      e.preventDefault();\n      first.focus();\n    }\n  };\n\n  // children 중 Footer 는 Panel 끝으로, 나머지는 Body 로\n  let footer: ReactNode = null;\n  const bodyContent: ReactNode[] = [];\n  Children.forEach(children, (child) => {\n    if (isValidElement(child) && child.type === DialogFooter) footer = child;\n    else if (child !== null && child !== undefined && child !== false) bodyContent.push(child);\n  });\n  const hasBody = description !== undefined || bodyContent.length > 0;\n\n  const scrim = (\n    <div\n      ref={scrimRef}\n      className={cn(`${B}-scrim`, contained && `${B}-scrim--contained`)}\n      data-state={open ? \"open\" : \"closed\"}\n      hidden={!open}\n      onPointerDown={(e) => {\n        pressedOnScrim.current = e.target === e.currentTarget;\n      }}\n      onClick={(e) => {\n        if (e.target !== e.currentTarget || !pressedOnScrim.current) return;\n        pressedOnScrim.current = false;\n        if (dismissOnScrim) close();\n      }}\n    >\n      <div\n        {...rest}\n        ref={(node) => {\n          panelRef.current = node;\n          if (typeof ref === \"function\") ref(node);\n          else if (ref) ref.current = node;\n        }}\n        className={cn(B, size === \"large\" && `${B}--size_large`, className)}\n        role={role}\n        aria-modal=\"true\"\n        tabIndex={-1}\n        aria-labelledby={titleId}\n        aria-describedby={hasBody ? bodyId : undefined}\n        data-body-scroll={bodyScroll ? \"\" : undefined}\n        onKeyDown={trapTab}\n      >\n        <div className={`${B}__header`}>\n          <h2 className={`${B}__title`} id={titleId}>\n            {title}\n          </h2>\n          {showClose && (\n            <Button\n              className={`${B}__close`}\n              variant=\"ghost\"\n              layout=\"iconOnly\"\n              size=\"small\"\n              icon={<CloseIcon />}\n              aria-label=\"닫기\"\n              onClick={close}\n            />\n          )}\n        </div>\n        {hasBody && (\n          <div className={`${B}__body`} id={bodyId} ref={bodyRef}>\n            {description !== undefined && <DialogDescription>{description}</DialogDescription>}\n            {bodyContent}\n          </div>\n        )}\n        {footer}\n      </div>\n    </div>\n  );\n\n  return (\n    <DialogContext value={{ stackActions }}>\n      {contained || !mounted ? (contained ? scrim : null) : createPortal(scrim, document.body)}\n    </DialogContext>\n  );\n}\n\nexport type DialogDescriptionProps = HTMLAttributes<HTMLParagraphElement> & {\n  ref?: Ref<HTMLParagraphElement>;\n};\n\n/** Body 안의 설명 문단(`atom-dialog__description atom-break-keep`). 여러 문단이면 여러 번 씁니다. */\nfunction DialogDescription({ ref, className, ...rest }: DialogDescriptionProps) {\n  useDialogContext(\"Dialog.Description\");\n  return <p {...rest} ref={ref} className={cn(`${B}__description`, \"atom-break-keep\", className)} />;\n}\n\nexport type DialogFooterProps = HTMLAttributes<HTMLDivElement> & {\n  ref?: Ref<HTMLDivElement>;\n};\n\n/** 액션 버튼 영역. 왼쪽부터 취소 → 보조 → 주요 순서로 둡니다. stackActions 면 `--stack`. */\nfunction DialogFooter({ ref, className, ...rest }: DialogFooterProps) {\n  const { stackActions } = useDialogContext(\"Dialog.Footer\");\n  return (\n    <div\n      {...rest}\n      ref={ref}\n      className={cn(`${B}__footer`, stackActions && `${B}__footer--stack`, className)}\n    />\n  );\n}\n\nexport const Dialog = Object.assign(DialogRoot, { Description: DialogDescription, Footer: DialogFooter });\n",
      "type": "registry:ui",
      "target": "components/ui/dialog.tsx"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/button.css",
      "content": "/* ============================================================================\n   Element — Button\n   ----------------------------------------------------------------------------\n   atom-button\n     --size_{xsmall|small|medium|large}      medium is the default\n     --variant_{brand-solid|neutral-solid|neutral-weak|critical-solid\n                |brand-outline|neutral-outline|ghost}\n     --layout_icon-only\n     --width_full\n   states: :hover  [data-pressed]  [disabled]  [data-loading]\n\n   Loaded by both the card preview and the detail page, so\n   this file is the only definition of the component.\n   ========================================================================== */\n\n.atom-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--atom-x2);\n  height: 47px;\n  padding-inline: var(--atom-x5);\n  border-radius: var(--atom-radius-r2);\n  font-size: 15px;\n  font-weight: 600;\n  line-height: 1;\n  white-space: nowrap;\n  user-select: none;\n  transition: filter var(--atom-duration-fast) ease,\n    background-color var(--atom-duration-fast) ease;\n}\n\n.atom-button__icon {\n  width: 18px;\n  height: 18px;\n  flex-shrink: 0;\n}\n\n.atom-button__label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* --- variants ------------------------------------------------------------- */\n.atom-button--variant_neutral-solid {\n  background: var(--atom-bg-neutral-solid);\n  color: var(--atom-fg-on-solid);\n}\n\n.atom-button--variant_brand-solid {\n  background: var(--atom-primary-600);\n  color: #fff;\n}\n\n.atom-button--variant_critical-solid {\n  background: var(--atom-bg-critical-solid);\n  color: #fff;\n}\n\n.atom-button--variant_neutral-weak {\n  background: var(--atom-bg-neutral-weak);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-button--variant_brand-outline {\n  background: transparent;\n  color: var(--atom-primary-600);\n  box-shadow: inset 0 0 0 1px currentColor;\n}\n\n.atom-button--variant_neutral-outline {\n  background: transparent;\n  color: var(--atom-fg-neutral);\n  box-shadow: inset 0 0 0 1px var(--atom-border-default);\n}\n\n.atom-button--variant_ghost {\n  background: transparent;\n  color: var(--atom-fg-neutral);\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-button--size_xsmall {\n  height: 32px;\n  gap: var(--atom-x1);\n  padding-inline: var(--atom-x3);\n  border-radius: var(--atom-radius-full);\n  font-size: 13px;\n}\n\n.atom-button--size_xsmall .atom-button__icon {\n  width: 14px;\n  height: 14px;\n}\n\n.atom-button--size_small {\n  height: 40px;\n  padding-inline: var(--atom-x4);\n  font-size: 14px;\n}\n\n.atom-button--size_small .atom-button__icon {\n  width: 16px;\n  height: 16px;\n}\n\n.atom-button--size_large {\n  height: 54px;\n  padding-inline: var(--atom-x6);\n  border-radius: var(--atom-radius-r3);\n  font-size: 16px;\n}\n\n.atom-button--size_large .atom-button__icon {\n  width: 20px;\n  height: 20px;\n}\n\n/* --- layout -------------------------------------------------------------- */\n.atom-button--layout_icon-only {\n  width: 47px;\n  padding-inline: 0;\n}\n\n.atom-button--layout_icon-only.atom-button--size_xsmall {\n  width: 32px;\n}\n\n.atom-button--layout_icon-only.atom-button--size_small {\n  width: 40px;\n}\n\n.atom-button--layout_icon-only.atom-button--size_large {\n  width: 54px;\n}\n\n.atom-button--width_full {\n  width: 100%;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-button:hover:not([disabled]):not([data-loading]) {\n  filter: brightness(0.94);\n}\n\n.atom-button--variant_ghost:hover:not([disabled]),\n.atom-button--variant_neutral-outline:hover:not([disabled]),\n.atom-button--variant_brand-outline:hover:not([disabled]) {\n  filter: none;\n  background: var(--atom-bg-neutral-weak);\n}\n\n.atom-button[data-pressed] {\n  filter: brightness(0.88);\n}\n\n.atom-button[disabled] {\n  opacity: 0.35;\n  cursor: not-allowed;\n  filter: none;\n}\n\n.atom-button[data-loading] {\n  cursor: progress;\n}\n\n.atom-button[data-loading] .atom-button__label {\n  opacity: 0.45;\n}\n\n.atom-button__spinner {\n  width: 16px;\n  height: 16px;\n  flex-shrink: 0;\n  border-radius: var(--atom-radius-full);\n  border: 2px solid currentColor;\n  border-top-color: transparent;\n  animation: atom-button-spin 700ms linear infinite;\n}\n\n@keyframes atom-button-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/button.css"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/dialog.css",
      "content": "/* ============================================================================\n   Component — Dialog\n   ----------------------------------------------------------------------------\n   A panel that stops the current task until it is answered or dismissed.\n\n   The scrim and the panel are two blocks, not a block and its element. The\n   panel is shown on its own in the docs stages, and a dialog confined to an\n   embedded region keeps the same panel while the scrim swaps to --contained.\n\n   The layout exists for one rule: when content outgrows the panel, __body is\n   the only part that scrolls. Header and footer stay where they were so the\n   actions never travel.\n\n   atom-dialog-scrim\n     --contained                          fills a positioned ancestor, not the viewport\n     [data-state=\"open|closed\"]           closed is display: none\n   atom-dialog\n     --size_{medium|large}                medium is the default\n     [data-body-scroll]                   header/footer divider. JS sets it\n     __header  __title  __description  __close\n     __body\n     __footer  (--stack)\n   atom-dialog-frame                      a stand-in viewport for the docs stages\n   ========================================================================== */\n\n.atom-dialog-scrim {\n  position: fixed;\n  inset: 0;\n  z-index: 100;\n  display: grid;\n  place-items: center;\n  /* Doubles as the narrow-screen gutter — at 375px it leaves the panel 91% of\n     the screen. One value rather than a breakpoint on purpose: the card preview\n     is a 400px canvas inside a desktop viewport, so a min-width query would\n     fire there and squeeze the panel out of the thumbnail. */\n  padding: var(--atom-x4);\n  background: var(--atom-bg-scrim);\n}\n\n/* Docs stages and embedded regions: the dialog belongs to its container, so the\n   nearest positioned ancestor becomes the \"viewport\" it dims. */\n.atom-dialog-scrim--contained {\n  position: absolute;\n}\n\n.atom-dialog-scrim[data-state=\"closed\"],\n.atom-dialog[data-state=\"closed\"] {\n  display: none;\n}\n\n/* --- panel --------------------------------------------------------------- */\n/* No padding of its own. With the edge inset on the panel, a scrolling __body\n   would clip its content 24px inside the border and read as a rendering bug —\n   header, body and footer each own that inset instead. */\n.atom-dialog {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: 480px;\n  /* 80vh keeps the layer reading as a layer: background stays visible above and\n     below. The 100% term is the scrim's grid area, so the panel also never\n     outgrows a --contained scrim. */\n  max-height: min(80vh, 100%);\n  border-radius: var(--atom-radius-r3);\n  background: var(--atom-bg-layer);\n  box-shadow: var(--atom-shadow-s3);\n  overflow: hidden;\n  text-align: left;\n}\n\n/* The panel takes focus when it opens and is not itself interactive, so a ring\n   around the whole thing would only read as an error. */\n.atom-dialog:focus {\n  outline: none;\n}\n\n/* --- header -------------------------------------------------------------- */\n.atom-dialog__header {\n  display: flex;\n  align-items: flex-start;\n  justify-content: space-between;\n  gap: var(--atom-x3);\n  flex-shrink: 0;\n  padding: var(--atom-x6) var(--atom-x6) var(--atom-x3);\n}\n\n.atom-dialog__title {\n  font-size: 18px;\n  font-weight: 700;\n  line-height: 1.4;\n  color: var(--atom-fg-neutral);\n  word-break: keep-all;\n}\n\n/* Pulled back to the panel edge and out of the title's line box: an icon\n   button's tap target is wider than its glyph, so aligning the boxes leaves the\n   x visibly inset while the title sits flush. */\n.atom-dialog__close {\n  flex-shrink: 0;\n  margin: -6px -8px -6px 0;\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-dialog__close .atom-button__icon {\n  width: 24px;\n  height: 24px;\n}\n\n/* --- body ---------------------------------------------------------------- */\n.atom-dialog__body {\n  flex: 1;\n  /* Without this the body refuses to shrink below its content and the panel\n     grows past max-height instead of scrolling. */\n  min-height: 0;\n  overflow-y: auto;\n  /* Reaching the end of the body must not hand the scroll to the page behind. */\n  overscroll-behavior: contain;\n  padding-inline: var(--atom-x6);\n  font-size: 15px;\n  line-height: 1.7;\n  color: var(--atom-fg-neutral);\n}\n\n/* A body with no header or no footer next to it has to supply that edge inset\n   itself. */\n.atom-dialog__body:first-child {\n  padding-top: var(--atom-x6);\n}\n\n.atom-dialog__body:last-child {\n  padding-bottom: var(--atom-x6);\n}\n\n.atom-dialog__description {\n  font-size: 15px;\n  line-height: 1.7;\n  color: var(--atom-fg-neutral-muted);\n  word-break: keep-all;\n}\n\n/* --- footer -------------------------------------------------------------- */\n.atom-dialog__footer {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  gap: var(--atom-x2);\n  flex-shrink: 0;\n  padding: var(--atom-x6);\n}\n\n/* Labels too long for one line stack instead. DOM order is unchanged, so the\n   primary action stays last — rightmost in a row, bottom in a stack, which is\n   where the thumb rests. */\n.atom-dialog__footer--stack {\n  flex-direction: column;\n  align-items: stretch;\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-dialog--size_large {\n  max-width: 800px;\n}\n\n/* --- states -------------------------------------------------------------- */\n/* Set when __body actually overflows. Without the lines, a sentence cut off at\n   the header edge reads as a clipping bug rather than as \"there is more\". */\n.atom-dialog[data-body-scroll] .atom-dialog__header {\n  box-shadow: inset 0 -1px 0 var(--atom-border-subtle);\n}\n\n.atom-dialog[data-body-scroll] .atom-dialog__footer {\n  box-shadow: inset 0 1px 0 var(--atom-border-subtle);\n}\n\n/* --- motion -------------------------------------------------------------- */\n@keyframes atom-dialog-scrim-in {\n  from {\n    opacity: 0;\n  }\n}\n\n@keyframes atom-dialog-panel-in {\n  from {\n    opacity: 0;\n    transform: translateY(8px) scale(0.97);\n  }\n}\n\n.atom-dialog-scrim[data-state=\"open\"] {\n  animation: atom-dialog-scrim-in var(--atom-duration-base) var(--atom-ease-out);\n}\n\n.atom-dialog-scrim[data-state=\"open\"] > .atom-dialog {\n  animation: atom-dialog-panel-in var(--atom-duration-base) var(--atom-ease-out);\n}\n\n/* base.css collapses every animation to 0.01ms, which stops the motion but not\n   the first painted frame — the panel would still flash 8px low and\n   transparent. Reduced motion gets no keyframes at all. */\n@media (prefers-reduced-motion: reduce) {\n  .atom-dialog-scrim[data-state=\"open\"],\n  .atom-dialog-scrim[data-state=\"open\"] > .atom-dialog {\n    animation: none;\n  }\n}\n\n/* --- docs host ----------------------------------------------------------- */\n/* A stand-in viewport so a --contained scrim has a region to dim. Only the\n   demo stages use it; no part of the component depends on it. */\n.atom-dialog-frame {\n  position: relative;\n  overflow: hidden;\n  border-radius: var(--atom-radius-r3);\n  background: var(--atom-bg-canvas);\n  box-shadow: inset 0 0 0 1px var(--atom-border-subtle);\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/dialog.css"
    }
  ],
  "categories": [
    "overlay"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "component",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "Dialog",
      "Dialog.Description",
      "Dialog.Footer"
    ],
    "docs": "poc/design-system/components/dialog.md"
  }
}
