{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "dialog", "title": "Dialog", "description": "A modal dialog using the native browser dialog element with a rough-drawn border that redraws fresh on every open.", "dependencies": [ "roughjs" ], "registryDependencies": [ "https://www.bydefaulthuman.fun/r/use-rough.json", "utils", "https://www.bydefaulthuman.fun/r/crumble-theme.json", "https://www.bydefaulthuman.fun/r/rough-lib.json" ], "files": [ { "path": "registry/new-york/ui/dialog.tsx", "content": "\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { useRough } from \"@/hooks/use-rough\";\nimport { cn } from \"@/lib/utils\";\nimport {\n CrumbleContext,\n randomSeed,\n resolveRoughVars,\n type CrumbleColorProps,\n type CrumbleTheme,\n} from \"@/lib/rough\";\n\ninterface DialogContextValue {\n onClose: () => void;\n theme: CrumbleTheme;\n}\n\nconst DialogContext = createContext({\n onClose: () => {},\n theme: \"pencil\",\n});\n\nexport interface DialogProps extends CrumbleColorProps {\n children: ReactNode;\n onOpenChange?: (open: boolean) => void;\n open: boolean;\n theme?: CrumbleTheme;\n}\n\nexport function Dialog({\n children,\n fill,\n onOpenChange,\n open,\n stroke,\n strokeMuted,\n theme: themeProp,\n}: DialogProps) {\n const { theme: contextTheme } = useContext(CrumbleContext);\n const theme = themeProp ?? contextTheme;\n const onClose = () => onOpenChange?.(false);\n const roughStyle = resolveRoughVars({ stroke, strokeMuted, fill });\n return (\n \n
{children}
\n
\n );\n}\n\nexport function DialogTrigger({\n children,\n className,\n ...props\n}: HTMLAttributes) {\n return (\n \n {children}\n \n );\n}\n\nexport type DialogSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nexport interface DialogContentProps extends HTMLAttributes {\n open: boolean;\n showClose?: boolean;\n size?: DialogSize;\n}\n\nconst sizeMap: Record = {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n xl: \"max-w-xl\",\n};\n\nexport function DialogContent({\n children,\n className,\n open,\n showClose = true,\n size = \"md\",\n ...props\n}: DialogContentProps) {\n const { onClose, theme } = useContext(DialogContext);\n const dialogRef = useRef(null);\n const panelRef = useRef(null);\n const externalSvgRef = useRef(null);\n const { drawRect, svgRef } = useRough({\n svgRef: externalSvgRef,\n theme,\n variant: \"border\",\n });\n\n useEffect(() => {\n const el = dialogRef.current;\n if (!el) return;\n if (open) {\n if (!el.open) el.showModal();\n } else if (el.open) {\n el.close();\n }\n }, [open]);\n\n useEffect(() => {\n const el = dialogRef.current;\n if (!el) return;\n const handler = (e: Event) => {\n e.preventDefault();\n onClose();\n };\n el.addEventListener(\"cancel\", handler);\n return () => el.removeEventListener(\"cancel\", handler);\n }, [onClose]);\n\n const handleDialogClick = (e: React.MouseEvent) => {\n if (e.target === dialogRef.current) onClose();\n };\n\n const draw = useCallback(() => {\n const panel = panelRef.current;\n const svg = svgRef.current;\n if (!panel || !svg) return;\n\n svg.replaceChildren();\n const w = panel.offsetWidth;\n const h = panel.offsetHeight;\n svg.setAttribute(\"width\", String(w));\n svg.setAttribute(\"height\", String(h));\n svg.setAttribute(\"viewBox\", `0 0 ${w} ${h}`);\n\n const rect = drawRect(1, 1, w - 2, h - 2, {\n fill: \"none\",\n seed: randomSeed(),\n stroke: \"var(--cr-stroke)\",\n strokeWidth: theme === \"crayon\" ? 2.5 : theme === \"ink\" ? 1.5 : 1,\n });\n if (rect) svg.appendChild(rect);\n }, [drawRect, svgRef, theme]);\n\n useEffect(() => {\n if (open) {\n const id = requestAnimationFrame(() => draw());\n return () => cancelAnimationFrame(id);\n }\n }, [draw, open]);\n\n useEffect(() => {\n const panel = panelRef.current;\n if (!panel) return;\n const ro = new ResizeObserver(() => draw());\n ro.observe(panel);\n return () => ro.disconnect();\n }, [draw]);\n\n return (\n <>\n \n\n \n \n \n
\n {showClose ? (\n \n \n \n \n \n \n ) : null}\n {children}\n
\n \n \n \n );\n}\n\nexport function DialogHeader({\n children,\n className,\n ...props\n}: HTMLAttributes) {\n return (\n
\n {children}\n
\n );\n}\n\nexport function DialogTitle({\n children,\n className,\n ...props\n}: HTMLAttributes) {\n return (\n \n {children}\n \n );\n}\n\nexport function DialogDescription({\n children,\n className,\n ...props\n}: HTMLAttributes) {\n return (\n

\n {children}\n

\n );\n}\n\nexport function DialogFooter({\n children,\n className,\n ...props\n}: HTMLAttributes) {\n return (\n
\n {children}\n
\n );\n}\n", "type": "registry:ui", "target": "components/crumble/ui/dialog.tsx" } ], "type": "registry:ui" }