{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "file-upload", "title": "File Upload", "description": "A hand-drawn dropzone that becomes more expressive when a file is dragged over it.", "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/file-upload.tsx", "content": "\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ChangeEvent,\n type DragEvent,\n} from \"react\";\nimport { useRough } from \"@/hooks/use-rough\";\nimport { cn } from \"@/lib/utils\";\nimport {\n resolveRoughVars,\n type CrumbleColorProps,\n type CrumbleTheme,\n} from \"@/lib/rough\";\n\nexport interface FileUploadProps extends CrumbleColorProps {\n accept?: string;\n className?: string;\n disabled?: boolean;\n id?: string;\n label?: string;\n multiple?: boolean;\n onChange?: (files: FileList) => void;\n theme?: CrumbleTheme;\n}\n\nexport function FileUpload({\n accept,\n className,\n disabled = false,\n fill,\n id,\n label,\n multiple = false,\n onChange,\n stroke,\n strokeMuted,\n theme: themeProp,\n}: FileUploadProps) {\n const [dragging, setDragging] = useState(false);\n const [files, setFiles] = useState([]);\n const containerRef = useRef(null);\n const externalSvgRef = useRef(null);\n const inputRef = useRef(null);\n const uploadId = id ?? \"file-upload\";\n const roughStyle = resolveRoughVars({ stroke, strokeMuted, fill });\n const { drawRect, svgRef, theme } = useRough({\n stableId: uploadId,\n svgRef: externalSvgRef,\n theme: themeProp,\n variant: \"border\",\n });\n\n const draw = useCallback(() => {\n const svg = svgRef.current;\n const container = containerRef.current;\n if (!svg || !container) return;\n\n svg.replaceChildren();\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n svg.setAttribute(\"width\", String(w));\n svg.setAttribute(\"height\", String(h));\n svg.setAttribute(\"viewBox\", `0 0 ${w} ${h}`);\n\n const roughness = dragging ? 2.5 : theme === \"crayon\" ? 2 : 1;\n const currentStroke = dragging\n ? \"var(--cr-stroke)\"\n : \"var(--cr-stroke-muted)\";\n\n const rect = drawRect(2, 2, w - 4, h - 4, {\n fill: \"none\",\n roughness,\n stroke: currentStroke,\n strokeLineDash: [8, 6],\n });\n if (rect) svg.appendChild(rect);\n }, [disabled, dragging, drawRect, svgRef, theme]);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => draw());\n return () => cancelAnimationFrame(id);\n }, [draw]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n const ro = new ResizeObserver(() => draw());\n ro.observe(container);\n return () => ro.disconnect();\n }, [draw]);\n\n const handleFiles = (fileList: FileList) => {\n const arr = Array.from(fileList);\n setFiles(multiple ? arr : arr.slice(0, 1));\n onChange?.(fileList);\n };\n\n const handleDrop = (e: DragEvent) => {\n e.preventDefault();\n setDragging(false);\n if (disabled || !e.dataTransfer.files.length) return;\n handleFiles(e.dataTransfer.files);\n };\n\n const handleChange = (e: ChangeEvent) => {\n if (e.target.files?.length) handleFiles(e.target.files);\n };\n\n return (\n
\n {label ? (\n \n {label}\n \n ) : null}\n !disabled && inputRef.current?.click()}\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={handleDrop}\n >\n \n\n \n \n \n \n \n\n
\n {files.length > 0 ? (\n
\n {files.map((f, i) => (\n \n {f.name}\n \n ))}\n
\n ) : (\n <>\n

\n {dragging ? \"Drop to upload\" : \"Click or drag files here\"}\n

\n {accept ? (\n

{accept}

\n ) : null}\n \n )}\n
\n\n \n
\n \n );\n}\n", "type": "registry:ui", "target": "components/crumble/ui/file-upload.tsx" } ], "type": "registry:ui" }