@particle-academy/react-fancy 5.27.1 → 5.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ function FileUploadDropzone({
17
17
  children,
18
18
  className
19
19
  }) {
20
- const { addFiles, disabled } = useFileUpload();
20
+ const { addFiles, disabled, accept, multiple } = useFileUpload();
21
21
  const [dragOver, setDragOver] = useState(false);
22
22
  const inputRef = useRef(null);
23
23
  const handleDrop = useCallback(
@@ -56,7 +56,8 @@ function FileUploadDropzone({
56
56
  type: "file",
57
57
  className: "hidden",
58
58
  onChange: (e) => e.target.files && addFiles(e.target.files),
59
- multiple: true,
59
+ accept,
60
+ multiple,
60
61
  disabled
61
62
  }
62
63
  ),
@@ -136,10 +137,25 @@ function FileUploadList({ thumbnail, className }) {
136
137
  )) });
137
138
  }
138
139
  FileUploadList.displayName = "FileUploadList";
140
+
141
+ // src/components/FileUpload/matchesAccept.ts
142
+ function matchesAccept(file, accept) {
143
+ const rules = accept.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean);
144
+ if (rules.length === 0) return true;
145
+ const type = file.type.toLowerCase();
146
+ const name = file.name.toLowerCase();
147
+ return rules.some((rule) => {
148
+ if (rule.startsWith(".")) return name.endsWith(rule);
149
+ if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1));
150
+ return type === rule;
151
+ });
152
+ }
139
153
  function FileUploadRoot({
140
154
  children,
141
155
  value,
142
156
  onChange,
157
+ accept,
158
+ multiple = true,
143
159
  maxFiles,
144
160
  maxSize,
145
161
  disabled = false,
@@ -150,15 +166,19 @@ function FileUploadRoot({
150
166
  const addFiles = useCallback(
151
167
  (newFiles) => {
152
168
  let toAdd = Array.from(newFiles);
169
+ if (accept) {
170
+ toAdd = toAdd.filter((f) => matchesAccept(f, accept));
171
+ }
153
172
  if (maxSize) {
154
173
  toAdd = toAdd.filter((f) => f.size <= maxSize);
155
174
  }
175
+ const cap = multiple === false ? 1 : maxFiles;
156
176
  const updated = [...files, ...toAdd];
157
- const limited = maxFiles ? updated.slice(0, maxFiles) : updated;
177
+ const limited = cap ? updated.slice(0, cap) : updated;
158
178
  if (value === void 0) setInternalFiles(limited);
159
179
  onChange?.(limited);
160
180
  },
161
- [files, maxFiles, maxSize, onChange, value]
181
+ [accept, files, maxFiles, maxSize, multiple, onChange, value]
162
182
  );
163
183
  const removeFile = useCallback(
164
184
  (index) => {
@@ -169,8 +189,8 @@ function FileUploadRoot({
169
189
  [files, onChange, value]
170
190
  );
171
191
  const ctx = useMemo(
172
- () => ({ files, addFiles, removeFile, disabled }),
173
- [files, addFiles, removeFile, disabled]
192
+ () => ({ files, addFiles, removeFile, disabled, accept, multiple }),
193
+ [files, addFiles, removeFile, disabled, accept, multiple]
174
194
  );
175
195
  return /* @__PURE__ */ jsx(FileUploadContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx("div", { "data-react-fancy-file-upload": "", className: cn(className), children }) });
176
196
  }
@@ -180,5 +200,5 @@ var FileUpload = Object.assign(FileUploadRoot, {
180
200
  });
181
201
 
182
202
  export { FileUpload, useFileUpload };
183
- //# sourceMappingURL=chunk-JJBOKZWV.js.map
184
- //# sourceMappingURL=chunk-JJBOKZWV.js.map
203
+ //# sourceMappingURL=chunk-32HCJ5GD.js.map
204
+ //# sourceMappingURL=chunk-32HCJ5GD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/FileUpload/FileUpload.context.ts","../src/components/FileUpload/FileUploadDropzone.tsx","../src/components/FileUpload/FileUploadList.tsx","../src/components/FileUpload/matchesAccept.ts","../src/components/FileUpload/FileUpload.tsx"],"names":["jsxs","jsx","useState","useCallback","useMemo"],"mappings":"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","file":"chunk-32HCJ5GD.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { FileUploadContextValue } from \"./FileUpload.types\";\n\nexport const FileUploadContext =\n createContext<FileUploadContextValue | null>(null);\n\nexport function useFileUpload(): FileUploadContextValue {\n const ctx = useContext(FileUploadContext);\n if (!ctx) {\n throw new Error(\n \"FileUpload compound components must be used within <FileUpload>\",\n );\n }\n return ctx;\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport { Upload } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadDropzoneProps } from \"./FileUpload.types\";\n\nexport function FileUploadDropzone({\n children,\n className,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled, accept, multiple } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(false);\n if (disabled) return;\n addFiles(e.dataTransfer.files);\n },\n [addFiles, disabled],\n );\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(true);\n }, []);\n\n const handleDragLeave = useCallback(() => setDragOver(false), []);\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={() => !disabled && inputRef.current?.click()}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-zinc-300 hover:border-zinc-400 dark:border-zinc-700 dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n type=\"file\"\n className=\"hidden\"\n onChange={(e) => e.target.files && addFiles(e.target.files)}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n />\n {children ?? (\n <>\n <Upload size={32} strokeWidth={1.5} className=\"mb-2 text-zinc-400\" />\n <p className=\"text-sm text-zinc-500\">\n Drop files here or click to browse\n </p>\n </>\n )}\n </div>\n );\n}\n\nFileUploadDropzone.displayName = \"FileUploadDropzone\";\n","import { useEffect, useMemo } from \"react\";\nimport { File, X } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadListProps } from \"./FileUpload.types\";\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction getExtension(name: string): string {\n const parts = name.split(\".\");\n return parts.length > 1 ? parts.pop()!.toUpperCase() : \"\";\n}\n\nfunction ThumbnailItem({ file, index, onRemove }: { file: File; index: number; onRemove: (i: number) => void }) {\n const isImage = file.type.startsWith(\"image/\");\n const objectUrl = useMemo(() => (isImage ? URL.createObjectURL(file) : null), [file, isImage]);\n\n useEffect(() => {\n return () => {\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n }, [objectUrl]);\n\n return (\n <div className=\"group relative flex w-20 flex-col items-center gap-1\">\n <div className=\"relative h-20 w-20 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800\">\n {isImage && objectUrl ? (\n <img src={objectUrl} alt={file.name} className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full w-full flex-col items-center justify-center gap-1\">\n <File size={20} className=\"text-zinc-400\" />\n <span className=\"text-[10px] font-medium text-zinc-500\">{getExtension(file.name)}</span>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => onRemove(index)}\n className=\"absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white opacity-0 transition-opacity hover:bg-black/80 group-hover:opacity-100\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={10} />\n </button>\n </div>\n <span className=\"w-full truncate text-center text-xs text-zinc-500\">{file.name}</span>\n </div>\n );\n}\n\nexport function FileUploadList({ thumbnail, className }: FileUploadListProps) {\n const { files, removeFile } = useFileUpload();\n\n if (files.length === 0) return null;\n\n if (thumbnail) {\n return (\n <div data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 flex flex-wrap gap-3\", className)}>\n {files.map((file, i) => (\n <ThumbnailItem key={`${file.name}-${i}`} file={file} index={i} onRemove={removeFile} />\n ))}\n </div>\n );\n }\n\n return (\n <ul data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 space-y-2\", className)}>\n {files.map((file, i) => (\n <li\n key={`${file.name}-${i}`}\n className=\"flex items-center gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700\"\n >\n <File size={16} className=\"shrink-0 text-zinc-400\" />\n <span className=\"flex-1 truncate\">{file.name}</span>\n <span className=\"shrink-0 text-xs text-zinc-400\">\n {formatSize(file.size)}\n </span>\n <button\n type=\"button\"\n onClick={() => removeFile(i)}\n className=\"shrink-0 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={14} />\n </button>\n </li>\n ))}\n </ul>\n );\n}\n\nFileUploadList.displayName = \"FileUploadList\";\n","/**\n * Does a file satisfy an `accept` string?\n *\n * The browser enforces `accept` in the FILE PICKER only. A drag-and-drop never\n * opens a picker, so on the drop path this is the only thing standing between an\n * image-only dropzone and a dropped executable. Forwarding the attribute to the\n * hidden input is half the job; this is the other half.\n *\n * Follows the HTML `accept` grammar: a comma-separated list of extensions\n * (`.csv`), exact MIME types (`text/plain`) and type wildcards (`image/*`).\n * Matching is case-insensitive, because a drop reports whatever the OS says and\n * `TEXT/PLAIN` is a real thing to receive.\n *\n * A dropped file often carries an EMPTY `type` — the OS may not know it and\n * `File.type` is a hint, never a guarantee. That is why extension rules are\n * checked against the name and not the type: an `accept=\".csv\"` zone has to work\n * for a .csv whose type is `\"\"`, which is the common case rather than the edge.\n */\nexport function matchesAccept(file: File, accept: string): boolean {\n const rules = accept\n .split(\",\")\n .map((rule) => rule.trim().toLowerCase())\n .filter(Boolean);\n\n // An accept string that lists nothing constrains nothing. Failing closed here\n // would turn `accept=\"\"` into a dropzone that silently swallows every file.\n if (rules.length === 0) return true;\n\n const type = file.type.toLowerCase();\n const name = file.name.toLowerCase();\n\n return rules.some((rule) => {\n if (rule.startsWith(\".\")) return name.endsWith(rule);\n if (rule.endsWith(\"/*\")) return type.startsWith(rule.slice(0, -1));\n return type === rule;\n });\n}\n","import { useCallback, useMemo, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FileUploadContext } from \"./FileUpload.context\";\nimport { FileUploadDropzone } from \"./FileUploadDropzone\";\nimport { FileUploadList } from \"./FileUploadList\";\nimport { matchesAccept } from \"./matchesAccept\";\nimport type { FileUploadProps } from \"./FileUpload.types\";\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n accept,\n multiple = true,\n maxFiles,\n maxSize,\n disabled = false,\n className,\n}: FileUploadProps) {\n const [internalFiles, setInternalFiles] = useState<File[]>([]);\n const files = value ?? internalFiles;\n\n const addFiles = useCallback(\n (newFiles: FileList | File[]) => {\n let toAdd = Array.from(newFiles);\n\n // `accept` is enforced HERE, not only on the input: the input's attribute\n // governs the picker, and a drop never opens one.\n if (accept) {\n toAdd = toAdd.filter((f) => matchesAccept(f, accept));\n }\n\n if (maxSize) {\n toAdd = toAdd.filter((f) => f.size <= maxSize);\n }\n\n // `multiple={false}` is a cap of one, applied the same way `maxFiles` is —\n // first-wins rather than replace-last, so the two props cannot disagree\n // about what a single-file upload means.\n const cap = multiple === false ? 1 : maxFiles;\n\n const updated = [...files, ...toAdd];\n const limited = cap ? updated.slice(0, cap) : updated;\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n },\n [accept, files, maxFiles, maxSize, multiple, onChange, value],\n );\n\n const removeFile = useCallback(\n (index: number) => {\n const updated = files.filter((_, i) => i !== index);\n if (value === undefined) setInternalFiles(updated);\n onChange?.(updated);\n },\n [files, onChange, value],\n );\n\n const ctx = useMemo(\n () => ({ files, addFiles, removeFile, disabled, accept, multiple }),\n [files, addFiles, removeFile, disabled, accept, multiple],\n );\n\n return (\n <FileUploadContext.Provider value={ctx}>\n <div data-react-fancy-file-upload=\"\" className={cn(className)}>{children}</div>\n </FileUploadContext.Provider>\n );\n}\n\nexport const FileUpload = Object.assign(FileUploadRoot, {\n Dropzone: FileUploadDropzone,\n List: FileUploadList,\n});\n"]}
@@ -19,7 +19,7 @@ function FileUploadDropzone({
19
19
  children,
20
20
  className
21
21
  }) {
22
- const { addFiles, disabled } = useFileUpload();
22
+ const { addFiles, disabled, accept, multiple } = useFileUpload();
23
23
  const [dragOver, setDragOver] = react.useState(false);
24
24
  const inputRef = react.useRef(null);
25
25
  const handleDrop = react.useCallback(
@@ -58,7 +58,8 @@ function FileUploadDropzone({
58
58
  type: "file",
59
59
  className: "hidden",
60
60
  onChange: (e) => e.target.files && addFiles(e.target.files),
61
- multiple: true,
61
+ accept,
62
+ multiple,
62
63
  disabled
63
64
  }
64
65
  ),
@@ -138,10 +139,25 @@ function FileUploadList({ thumbnail, className }) {
138
139
  )) });
139
140
  }
140
141
  FileUploadList.displayName = "FileUploadList";
142
+
143
+ // src/components/FileUpload/matchesAccept.ts
144
+ function matchesAccept(file, accept) {
145
+ const rules = accept.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean);
146
+ if (rules.length === 0) return true;
147
+ const type = file.type.toLowerCase();
148
+ const name = file.name.toLowerCase();
149
+ return rules.some((rule) => {
150
+ if (rule.startsWith(".")) return name.endsWith(rule);
151
+ if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1));
152
+ return type === rule;
153
+ });
154
+ }
141
155
  function FileUploadRoot({
142
156
  children,
143
157
  value,
144
158
  onChange,
159
+ accept,
160
+ multiple = true,
145
161
  maxFiles,
146
162
  maxSize,
147
163
  disabled = false,
@@ -152,15 +168,19 @@ function FileUploadRoot({
152
168
  const addFiles = react.useCallback(
153
169
  (newFiles) => {
154
170
  let toAdd = Array.from(newFiles);
171
+ if (accept) {
172
+ toAdd = toAdd.filter((f) => matchesAccept(f, accept));
173
+ }
155
174
  if (maxSize) {
156
175
  toAdd = toAdd.filter((f) => f.size <= maxSize);
157
176
  }
177
+ const cap = multiple === false ? 1 : maxFiles;
158
178
  const updated = [...files, ...toAdd];
159
- const limited = maxFiles ? updated.slice(0, maxFiles) : updated;
179
+ const limited = cap ? updated.slice(0, cap) : updated;
160
180
  if (value === void 0) setInternalFiles(limited);
161
181
  onChange?.(limited);
162
182
  },
163
- [files, maxFiles, maxSize, onChange, value]
183
+ [accept, files, maxFiles, maxSize, multiple, onChange, value]
164
184
  );
165
185
  const removeFile = react.useCallback(
166
186
  (index) => {
@@ -171,8 +191,8 @@ function FileUploadRoot({
171
191
  [files, onChange, value]
172
192
  );
173
193
  const ctx = react.useMemo(
174
- () => ({ files, addFiles, removeFile, disabled }),
175
- [files, addFiles, removeFile, disabled]
194
+ () => ({ files, addFiles, removeFile, disabled, accept, multiple }),
195
+ [files, addFiles, removeFile, disabled, accept, multiple]
176
196
  );
177
197
  return /* @__PURE__ */ jsxRuntime.jsx(FileUploadContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-react-fancy-file-upload": "", className: chunkZU5Q77OT_cjs.cn(className), children }) });
178
198
  }
@@ -183,5 +203,5 @@ var FileUpload = Object.assign(FileUploadRoot, {
183
203
 
184
204
  exports.FileUpload = FileUpload;
185
205
  exports.useFileUpload = useFileUpload;
186
- //# sourceMappingURL=chunk-ZVRVOKXH.cjs.map
187
- //# sourceMappingURL=chunk-ZVRVOKXH.cjs.map
206
+ //# sourceMappingURL=chunk-LAQGBLIU.cjs.map
207
+ //# sourceMappingURL=chunk-LAQGBLIU.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/FileUpload/FileUpload.context.ts","../src/components/FileUpload/FileUploadDropzone.tsx","../src/components/FileUpload/FileUploadList.tsx","../src/components/FileUpload/matchesAccept.ts","../src/components/FileUpload/FileUpload.tsx"],"names":["createContext","useContext","useState","useRef","useCallback","jsxs","cn","jsx","Fragment","Upload","useMemo","useEffect","File","X"],"mappings":"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","file":"chunk-LAQGBLIU.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { FileUploadContextValue } from \"./FileUpload.types\";\n\nexport const FileUploadContext =\n createContext<FileUploadContextValue | null>(null);\n\nexport function useFileUpload(): FileUploadContextValue {\n const ctx = useContext(FileUploadContext);\n if (!ctx) {\n throw new Error(\n \"FileUpload compound components must be used within <FileUpload>\",\n );\n }\n return ctx;\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport { Upload } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadDropzoneProps } from \"./FileUpload.types\";\n\nexport function FileUploadDropzone({\n children,\n className,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled, accept, multiple } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(false);\n if (disabled) return;\n addFiles(e.dataTransfer.files);\n },\n [addFiles, disabled],\n );\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(true);\n }, []);\n\n const handleDragLeave = useCallback(() => setDragOver(false), []);\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={() => !disabled && inputRef.current?.click()}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-zinc-300 hover:border-zinc-400 dark:border-zinc-700 dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n type=\"file\"\n className=\"hidden\"\n onChange={(e) => e.target.files && addFiles(e.target.files)}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n />\n {children ?? (\n <>\n <Upload size={32} strokeWidth={1.5} className=\"mb-2 text-zinc-400\" />\n <p className=\"text-sm text-zinc-500\">\n Drop files here or click to browse\n </p>\n </>\n )}\n </div>\n );\n}\n\nFileUploadDropzone.displayName = \"FileUploadDropzone\";\n","import { useEffect, useMemo } from \"react\";\nimport { File, X } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadListProps } from \"./FileUpload.types\";\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction getExtension(name: string): string {\n const parts = name.split(\".\");\n return parts.length > 1 ? parts.pop()!.toUpperCase() : \"\";\n}\n\nfunction ThumbnailItem({ file, index, onRemove }: { file: File; index: number; onRemove: (i: number) => void }) {\n const isImage = file.type.startsWith(\"image/\");\n const objectUrl = useMemo(() => (isImage ? URL.createObjectURL(file) : null), [file, isImage]);\n\n useEffect(() => {\n return () => {\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n }, [objectUrl]);\n\n return (\n <div className=\"group relative flex w-20 flex-col items-center gap-1\">\n <div className=\"relative h-20 w-20 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800\">\n {isImage && objectUrl ? (\n <img src={objectUrl} alt={file.name} className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full w-full flex-col items-center justify-center gap-1\">\n <File size={20} className=\"text-zinc-400\" />\n <span className=\"text-[10px] font-medium text-zinc-500\">{getExtension(file.name)}</span>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => onRemove(index)}\n className=\"absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white opacity-0 transition-opacity hover:bg-black/80 group-hover:opacity-100\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={10} />\n </button>\n </div>\n <span className=\"w-full truncate text-center text-xs text-zinc-500\">{file.name}</span>\n </div>\n );\n}\n\nexport function FileUploadList({ thumbnail, className }: FileUploadListProps) {\n const { files, removeFile } = useFileUpload();\n\n if (files.length === 0) return null;\n\n if (thumbnail) {\n return (\n <div data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 flex flex-wrap gap-3\", className)}>\n {files.map((file, i) => (\n <ThumbnailItem key={`${file.name}-${i}`} file={file} index={i} onRemove={removeFile} />\n ))}\n </div>\n );\n }\n\n return (\n <ul data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 space-y-2\", className)}>\n {files.map((file, i) => (\n <li\n key={`${file.name}-${i}`}\n className=\"flex items-center gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700\"\n >\n <File size={16} className=\"shrink-0 text-zinc-400\" />\n <span className=\"flex-1 truncate\">{file.name}</span>\n <span className=\"shrink-0 text-xs text-zinc-400\">\n {formatSize(file.size)}\n </span>\n <button\n type=\"button\"\n onClick={() => removeFile(i)}\n className=\"shrink-0 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={14} />\n </button>\n </li>\n ))}\n </ul>\n );\n}\n\nFileUploadList.displayName = \"FileUploadList\";\n","/**\n * Does a file satisfy an `accept` string?\n *\n * The browser enforces `accept` in the FILE PICKER only. A drag-and-drop never\n * opens a picker, so on the drop path this is the only thing standing between an\n * image-only dropzone and a dropped executable. Forwarding the attribute to the\n * hidden input is half the job; this is the other half.\n *\n * Follows the HTML `accept` grammar: a comma-separated list of extensions\n * (`.csv`), exact MIME types (`text/plain`) and type wildcards (`image/*`).\n * Matching is case-insensitive, because a drop reports whatever the OS says and\n * `TEXT/PLAIN` is a real thing to receive.\n *\n * A dropped file often carries an EMPTY `type` — the OS may not know it and\n * `File.type` is a hint, never a guarantee. That is why extension rules are\n * checked against the name and not the type: an `accept=\".csv\"` zone has to work\n * for a .csv whose type is `\"\"`, which is the common case rather than the edge.\n */\nexport function matchesAccept(file: File, accept: string): boolean {\n const rules = accept\n .split(\",\")\n .map((rule) => rule.trim().toLowerCase())\n .filter(Boolean);\n\n // An accept string that lists nothing constrains nothing. Failing closed here\n // would turn `accept=\"\"` into a dropzone that silently swallows every file.\n if (rules.length === 0) return true;\n\n const type = file.type.toLowerCase();\n const name = file.name.toLowerCase();\n\n return rules.some((rule) => {\n if (rule.startsWith(\".\")) return name.endsWith(rule);\n if (rule.endsWith(\"/*\")) return type.startsWith(rule.slice(0, -1));\n return type === rule;\n });\n}\n","import { useCallback, useMemo, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FileUploadContext } from \"./FileUpload.context\";\nimport { FileUploadDropzone } from \"./FileUploadDropzone\";\nimport { FileUploadList } from \"./FileUploadList\";\nimport { matchesAccept } from \"./matchesAccept\";\nimport type { FileUploadProps } from \"./FileUpload.types\";\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n accept,\n multiple = true,\n maxFiles,\n maxSize,\n disabled = false,\n className,\n}: FileUploadProps) {\n const [internalFiles, setInternalFiles] = useState<File[]>([]);\n const files = value ?? internalFiles;\n\n const addFiles = useCallback(\n (newFiles: FileList | File[]) => {\n let toAdd = Array.from(newFiles);\n\n // `accept` is enforced HERE, not only on the input: the input's attribute\n // governs the picker, and a drop never opens one.\n if (accept) {\n toAdd = toAdd.filter((f) => matchesAccept(f, accept));\n }\n\n if (maxSize) {\n toAdd = toAdd.filter((f) => f.size <= maxSize);\n }\n\n // `multiple={false}` is a cap of one, applied the same way `maxFiles` is —\n // first-wins rather than replace-last, so the two props cannot disagree\n // about what a single-file upload means.\n const cap = multiple === false ? 1 : maxFiles;\n\n const updated = [...files, ...toAdd];\n const limited = cap ? updated.slice(0, cap) : updated;\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n },\n [accept, files, maxFiles, maxSize, multiple, onChange, value],\n );\n\n const removeFile = useCallback(\n (index: number) => {\n const updated = files.filter((_, i) => i !== index);\n if (value === undefined) setInternalFiles(updated);\n onChange?.(updated);\n },\n [files, onChange, value],\n );\n\n const ctx = useMemo(\n () => ({ files, addFiles, removeFile, disabled, accept, multiple }),\n [files, addFiles, removeFile, disabled, accept, multiple],\n );\n\n return (\n <FileUploadContext.Provider value={ctx}>\n <div data-react-fancy-file-upload=\"\" className={cn(className)}>{children}</div>\n </FileUploadContext.Provider>\n );\n}\n\nexport const FileUpload = Object.assign(FileUploadRoot, {\n Dropzone: FileUploadDropzone,\n List: FileUploadList,\n});\n"]}
@@ -1,17 +1,17 @@
1
1
  'use strict';
2
2
 
3
- var chunkZVRVOKXH_cjs = require('./chunk-ZVRVOKXH.cjs');
3
+ var chunkLAQGBLIU_cjs = require('./chunk-LAQGBLIU.cjs');
4
4
  require('./chunk-ZU5Q77OT.cjs');
5
5
 
6
6
 
7
7
 
8
8
  Object.defineProperty(exports, "FileUpload", {
9
9
  enumerable: true,
10
- get: function () { return chunkZVRVOKXH_cjs.FileUpload; }
10
+ get: function () { return chunkLAQGBLIU_cjs.FileUpload; }
11
11
  });
12
12
  Object.defineProperty(exports, "useFileUpload", {
13
13
  enumerable: true,
14
- get: function () { return chunkZVRVOKXH_cjs.useFileUpload; }
14
+ get: function () { return chunkLAQGBLIU_cjs.useFileUpload; }
15
15
  });
16
16
  //# sourceMappingURL=file-upload.cjs.map
17
17
  //# sourceMappingURL=file-upload.cjs.map
@@ -6,6 +6,9 @@ interface FileUploadContextValue {
6
6
  addFiles: (files: FileList | File[]) => void;
7
7
  removeFile: (index: number) => void;
8
8
  disabled: boolean;
9
+ /** Mirrored onto the hidden input so the browse dialog filters too. */
10
+ accept?: string;
11
+ multiple: boolean;
9
12
  }
10
13
  interface FileUploadProps {
11
14
  children: ReactNode;
@@ -37,7 +40,7 @@ declare namespace FileUploadList {
37
40
  var displayName: string;
38
41
  }
39
42
 
40
- declare function FileUploadRoot({ children, value, onChange, maxFiles, maxSize, disabled, className, }: FileUploadProps): react.JSX.Element;
43
+ declare function FileUploadRoot({ children, value, onChange, accept, multiple, maxFiles, maxSize, disabled, className, }: FileUploadProps): react.JSX.Element;
41
44
  declare const FileUpload: typeof FileUploadRoot & {
42
45
  Dropzone: typeof FileUploadDropzone;
43
46
  List: typeof FileUploadList;
@@ -6,6 +6,9 @@ interface FileUploadContextValue {
6
6
  addFiles: (files: FileList | File[]) => void;
7
7
  removeFile: (index: number) => void;
8
8
  disabled: boolean;
9
+ /** Mirrored onto the hidden input so the browse dialog filters too. */
10
+ accept?: string;
11
+ multiple: boolean;
9
12
  }
10
13
  interface FileUploadProps {
11
14
  children: ReactNode;
@@ -37,7 +40,7 @@ declare namespace FileUploadList {
37
40
  var displayName: string;
38
41
  }
39
42
 
40
- declare function FileUploadRoot({ children, value, onChange, maxFiles, maxSize, disabled, className, }: FileUploadProps): react.JSX.Element;
43
+ declare function FileUploadRoot({ children, value, onChange, accept, multiple, maxFiles, maxSize, disabled, className, }: FileUploadProps): react.JSX.Element;
41
44
  declare const FileUpload: typeof FileUploadRoot & {
42
45
  Dropzone: typeof FileUploadDropzone;
43
46
  List: typeof FileUploadList;
@@ -1,4 +1,4 @@
1
- export { FileUpload, useFileUpload } from './chunk-JJBOKZWV.js';
1
+ export { FileUpload, useFileUpload } from './chunk-32HCJ5GD.js';
2
2
  import './chunk-MMW2JGKZ.js';
3
3
  //# sourceMappingURL=file-upload.js.map
4
4
  //# sourceMappingURL=file-upload.js.map
package/dist/index.cjs CHANGED
@@ -61,7 +61,7 @@ var chunkV35A6B7A_cjs = require('./chunk-V35A6B7A.cjs');
61
61
  var chunkRPVCMW64_cjs = require('./chunk-RPVCMW64.cjs');
62
62
  var chunkGBGJWAXZ_cjs = require('./chunk-GBGJWAXZ.cjs');
63
63
  var chunkJV6RDIA6_cjs = require('./chunk-JV6RDIA6.cjs');
64
- var chunkZVRVOKXH_cjs = require('./chunk-ZVRVOKXH.cjs');
64
+ var chunkLAQGBLIU_cjs = require('./chunk-LAQGBLIU.cjs');
65
65
  var chunkOOZCJLEF_cjs = require('./chunk-OOZCJLEF.cjs');
66
66
  var chunkY3T7WMT6_cjs = require('./chunk-Y3T7WMT6.cjs');
67
67
  var chunkQDVDBUJM_cjs = require('./chunk-QDVDBUJM.cjs');
@@ -438,11 +438,11 @@ Object.defineProperty(exports, "validateFolderName", {
438
438
  });
439
439
  Object.defineProperty(exports, "FileUpload", {
440
440
  enumerable: true,
441
- get: function () { return chunkZVRVOKXH_cjs.FileUpload; }
441
+ get: function () { return chunkLAQGBLIU_cjs.FileUpload; }
442
442
  });
443
443
  Object.defineProperty(exports, "useFileUpload", {
444
444
  enumerable: true,
445
- get: function () { return chunkZVRVOKXH_cjs.useFileUpload; }
445
+ get: function () { return chunkLAQGBLIU_cjs.useFileUpload; }
446
446
  });
447
447
  Object.defineProperty(exports, "Grid", {
448
448
  enumerable: true,
package/dist/index.js CHANGED
@@ -59,7 +59,7 @@ export { PdfViewer } from './chunk-SOYORSLZ.js';
59
59
  export { Menu, useMenu } from './chunk-RA2VOQ46.js';
60
60
  export { MobileMenu, useMobileMenu } from './chunk-P7F32HIH.js';
61
61
  export { FileBrowser, FileBrowserNewFolder, useFileBrowser, validateFolderName } from './chunk-TQFQUNV3.js';
62
- export { FileUpload, useFileUpload } from './chunk-JJBOKZWV.js';
62
+ export { FileUpload, useFileUpload } from './chunk-32HCJ5GD.js';
63
63
  export { Grid } from './chunk-TM3RTWKI.js';
64
64
  export { Heading } from './chunk-6GWOB2Z4.js';
65
65
  export { ImageViewer } from './chunk-MUCXXYDN.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/react-fancy",
3
- "version": "5.27.1",
3
+ "version": "5.28.0",
4
4
  "description": "React UI component library for Human+ UX — controlled, agent-bridgeable primitives",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/FileUpload/FileUpload.context.ts","../src/components/FileUpload/FileUploadDropzone.tsx","../src/components/FileUpload/FileUploadList.tsx","../src/components/FileUpload/FileUpload.tsx"],"names":["jsxs","jsx","useState","useCallback","useMemo"],"mappings":"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","file":"chunk-JJBOKZWV.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { FileUploadContextValue } from \"./FileUpload.types\";\n\nexport const FileUploadContext =\n createContext<FileUploadContextValue | null>(null);\n\nexport function useFileUpload(): FileUploadContextValue {\n const ctx = useContext(FileUploadContext);\n if (!ctx) {\n throw new Error(\n \"FileUpload compound components must be used within <FileUpload>\",\n );\n }\n return ctx;\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport { Upload } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadDropzoneProps } from \"./FileUpload.types\";\n\nexport function FileUploadDropzone({\n children,\n className,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(false);\n if (disabled) return;\n addFiles(e.dataTransfer.files);\n },\n [addFiles, disabled],\n );\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(true);\n }, []);\n\n const handleDragLeave = useCallback(() => setDragOver(false), []);\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={() => !disabled && inputRef.current?.click()}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-zinc-300 hover:border-zinc-400 dark:border-zinc-700 dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n type=\"file\"\n className=\"hidden\"\n onChange={(e) => e.target.files && addFiles(e.target.files)}\n multiple\n disabled={disabled}\n />\n {children ?? (\n <>\n <Upload size={32} strokeWidth={1.5} className=\"mb-2 text-zinc-400\" />\n <p className=\"text-sm text-zinc-500\">\n Drop files here or click to browse\n </p>\n </>\n )}\n </div>\n );\n}\n\nFileUploadDropzone.displayName = \"FileUploadDropzone\";\n","import { useEffect, useMemo } from \"react\";\nimport { File, X } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadListProps } from \"./FileUpload.types\";\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction getExtension(name: string): string {\n const parts = name.split(\".\");\n return parts.length > 1 ? parts.pop()!.toUpperCase() : \"\";\n}\n\nfunction ThumbnailItem({ file, index, onRemove }: { file: File; index: number; onRemove: (i: number) => void }) {\n const isImage = file.type.startsWith(\"image/\");\n const objectUrl = useMemo(() => (isImage ? URL.createObjectURL(file) : null), [file, isImage]);\n\n useEffect(() => {\n return () => {\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n }, [objectUrl]);\n\n return (\n <div className=\"group relative flex w-20 flex-col items-center gap-1\">\n <div className=\"relative h-20 w-20 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800\">\n {isImage && objectUrl ? (\n <img src={objectUrl} alt={file.name} className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full w-full flex-col items-center justify-center gap-1\">\n <File size={20} className=\"text-zinc-400\" />\n <span className=\"text-[10px] font-medium text-zinc-500\">{getExtension(file.name)}</span>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => onRemove(index)}\n className=\"absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white opacity-0 transition-opacity hover:bg-black/80 group-hover:opacity-100\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={10} />\n </button>\n </div>\n <span className=\"w-full truncate text-center text-xs text-zinc-500\">{file.name}</span>\n </div>\n );\n}\n\nexport function FileUploadList({ thumbnail, className }: FileUploadListProps) {\n const { files, removeFile } = useFileUpload();\n\n if (files.length === 0) return null;\n\n if (thumbnail) {\n return (\n <div data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 flex flex-wrap gap-3\", className)}>\n {files.map((file, i) => (\n <ThumbnailItem key={`${file.name}-${i}`} file={file} index={i} onRemove={removeFile} />\n ))}\n </div>\n );\n }\n\n return (\n <ul data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 space-y-2\", className)}>\n {files.map((file, i) => (\n <li\n key={`${file.name}-${i}`}\n className=\"flex items-center gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700\"\n >\n <File size={16} className=\"shrink-0 text-zinc-400\" />\n <span className=\"flex-1 truncate\">{file.name}</span>\n <span className=\"shrink-0 text-xs text-zinc-400\">\n {formatSize(file.size)}\n </span>\n <button\n type=\"button\"\n onClick={() => removeFile(i)}\n className=\"shrink-0 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={14} />\n </button>\n </li>\n ))}\n </ul>\n );\n}\n\nFileUploadList.displayName = \"FileUploadList\";\n","import { useCallback, useMemo, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FileUploadContext } from \"./FileUpload.context\";\nimport { FileUploadDropzone } from \"./FileUploadDropzone\";\nimport { FileUploadList } from \"./FileUploadList\";\nimport type { FileUploadProps } from \"./FileUpload.types\";\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n maxFiles,\n maxSize,\n disabled = false,\n className,\n}: FileUploadProps) {\n const [internalFiles, setInternalFiles] = useState<File[]>([]);\n const files = value ?? internalFiles;\n\n const addFiles = useCallback(\n (newFiles: FileList | File[]) => {\n let toAdd = Array.from(newFiles);\n\n if (maxSize) {\n toAdd = toAdd.filter((f) => f.size <= maxSize);\n }\n\n const updated = [...files, ...toAdd];\n const limited = maxFiles ? updated.slice(0, maxFiles) : updated;\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n },\n [files, maxFiles, maxSize, onChange, value],\n );\n\n const removeFile = useCallback(\n (index: number) => {\n const updated = files.filter((_, i) => i !== index);\n if (value === undefined) setInternalFiles(updated);\n onChange?.(updated);\n },\n [files, onChange, value],\n );\n\n const ctx = useMemo(\n () => ({ files, addFiles, removeFile, disabled }),\n [files, addFiles, removeFile, disabled],\n );\n\n return (\n <FileUploadContext.Provider value={ctx}>\n <div data-react-fancy-file-upload=\"\" className={cn(className)}>{children}</div>\n </FileUploadContext.Provider>\n );\n}\n\nexport const FileUpload = Object.assign(FileUploadRoot, {\n Dropzone: FileUploadDropzone,\n List: FileUploadList,\n});\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/FileUpload/FileUpload.context.ts","../src/components/FileUpload/FileUploadDropzone.tsx","../src/components/FileUpload/FileUploadList.tsx","../src/components/FileUpload/FileUpload.tsx"],"names":["createContext","useContext","useState","useRef","useCallback","jsxs","cn","jsx","Fragment","Upload","useMemo","useEffect","File","X"],"mappings":"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","file":"chunk-ZVRVOKXH.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { FileUploadContextValue } from \"./FileUpload.types\";\n\nexport const FileUploadContext =\n createContext<FileUploadContextValue | null>(null);\n\nexport function useFileUpload(): FileUploadContextValue {\n const ctx = useContext(FileUploadContext);\n if (!ctx) {\n throw new Error(\n \"FileUpload compound components must be used within <FileUpload>\",\n );\n }\n return ctx;\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport { Upload } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadDropzoneProps } from \"./FileUpload.types\";\n\nexport function FileUploadDropzone({\n children,\n className,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(false);\n if (disabled) return;\n addFiles(e.dataTransfer.files);\n },\n [addFiles, disabled],\n );\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(true);\n }, []);\n\n const handleDragLeave = useCallback(() => setDragOver(false), []);\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={() => !disabled && inputRef.current?.click()}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-zinc-300 hover:border-zinc-400 dark:border-zinc-700 dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n type=\"file\"\n className=\"hidden\"\n onChange={(e) => e.target.files && addFiles(e.target.files)}\n multiple\n disabled={disabled}\n />\n {children ?? (\n <>\n <Upload size={32} strokeWidth={1.5} className=\"mb-2 text-zinc-400\" />\n <p className=\"text-sm text-zinc-500\">\n Drop files here or click to browse\n </p>\n </>\n )}\n </div>\n );\n}\n\nFileUploadDropzone.displayName = \"FileUploadDropzone\";\n","import { useEffect, useMemo } from \"react\";\nimport { File, X } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadListProps } from \"./FileUpload.types\";\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction getExtension(name: string): string {\n const parts = name.split(\".\");\n return parts.length > 1 ? parts.pop()!.toUpperCase() : \"\";\n}\n\nfunction ThumbnailItem({ file, index, onRemove }: { file: File; index: number; onRemove: (i: number) => void }) {\n const isImage = file.type.startsWith(\"image/\");\n const objectUrl = useMemo(() => (isImage ? URL.createObjectURL(file) : null), [file, isImage]);\n\n useEffect(() => {\n return () => {\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n }, [objectUrl]);\n\n return (\n <div className=\"group relative flex w-20 flex-col items-center gap-1\">\n <div className=\"relative h-20 w-20 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800\">\n {isImage && objectUrl ? (\n <img src={objectUrl} alt={file.name} className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full w-full flex-col items-center justify-center gap-1\">\n <File size={20} className=\"text-zinc-400\" />\n <span className=\"text-[10px] font-medium text-zinc-500\">{getExtension(file.name)}</span>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => onRemove(index)}\n className=\"absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white opacity-0 transition-opacity hover:bg-black/80 group-hover:opacity-100\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={10} />\n </button>\n </div>\n <span className=\"w-full truncate text-center text-xs text-zinc-500\">{file.name}</span>\n </div>\n );\n}\n\nexport function FileUploadList({ thumbnail, className }: FileUploadListProps) {\n const { files, removeFile } = useFileUpload();\n\n if (files.length === 0) return null;\n\n if (thumbnail) {\n return (\n <div data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 flex flex-wrap gap-3\", className)}>\n {files.map((file, i) => (\n <ThumbnailItem key={`${file.name}-${i}`} file={file} index={i} onRemove={removeFile} />\n ))}\n </div>\n );\n }\n\n return (\n <ul data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 space-y-2\", className)}>\n {files.map((file, i) => (\n <li\n key={`${file.name}-${i}`}\n className=\"flex items-center gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700\"\n >\n <File size={16} className=\"shrink-0 text-zinc-400\" />\n <span className=\"flex-1 truncate\">{file.name}</span>\n <span className=\"shrink-0 text-xs text-zinc-400\">\n {formatSize(file.size)}\n </span>\n <button\n type=\"button\"\n onClick={() => removeFile(i)}\n className=\"shrink-0 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={14} />\n </button>\n </li>\n ))}\n </ul>\n );\n}\n\nFileUploadList.displayName = \"FileUploadList\";\n","import { useCallback, useMemo, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FileUploadContext } from \"./FileUpload.context\";\nimport { FileUploadDropzone } from \"./FileUploadDropzone\";\nimport { FileUploadList } from \"./FileUploadList\";\nimport type { FileUploadProps } from \"./FileUpload.types\";\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n maxFiles,\n maxSize,\n disabled = false,\n className,\n}: FileUploadProps) {\n const [internalFiles, setInternalFiles] = useState<File[]>([]);\n const files = value ?? internalFiles;\n\n const addFiles = useCallback(\n (newFiles: FileList | File[]) => {\n let toAdd = Array.from(newFiles);\n\n if (maxSize) {\n toAdd = toAdd.filter((f) => f.size <= maxSize);\n }\n\n const updated = [...files, ...toAdd];\n const limited = maxFiles ? updated.slice(0, maxFiles) : updated;\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n },\n [files, maxFiles, maxSize, onChange, value],\n );\n\n const removeFile = useCallback(\n (index: number) => {\n const updated = files.filter((_, i) => i !== index);\n if (value === undefined) setInternalFiles(updated);\n onChange?.(updated);\n },\n [files, onChange, value],\n );\n\n const ctx = useMemo(\n () => ({ files, addFiles, removeFile, disabled }),\n [files, addFiles, removeFile, disabled],\n );\n\n return (\n <FileUploadContext.Provider value={ctx}>\n <div data-react-fancy-file-upload=\"\" className={cn(className)}>{children}</div>\n </FileUploadContext.Provider>\n );\n}\n\nexport const FileUpload = Object.assign(FileUploadRoot, {\n Dropzone: FileUploadDropzone,\n List: FileUploadList,\n});\n"]}