@particle-academy/react-fancy 6.11.0 → 6.11.1

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.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [6.11.1] - 2026-11-07
15
+
16
+ ### Fixed
17
+
18
+ - **`FileUpload` text follows the `--fancy-text-*` theme tokens.** The dropzone hint and icon, the list's size meta, the thumbnail's file name and extension, and the remove button were bare `text-zinc-*`, so an app that mapped the tokens themed every other component and left FileUpload grey. On a `#0A2347` surface the hint was 3.23:1, under AA (found by axe at AAA in a consumer app). They now read `--fancy-text-muted` (hint, meta, name, extension, remove), `--fancy-text-soft` (remove on hover) and `--fancy-text-faint` (the decorative file glyphs), with the same fallbacks as every other component. Default contrast also improves where it fell short: the hint in dark mode was 3.67:1 and is now 6.75:1; the size meta in light was 2.62:1 and is now 4.83:1; the list's remove icon was 2.62:1 in light, under the 3:1 a text-less control owes (WCAG 1.4.11), and is now 4.83:1. During drag-over the hint and icon step up to `--fancy-text-subtle`, because muted on the blue drag fill is 4.44:1; subtle is 7.09:1 there. **Consumer action:** none; an app that maps `--fancy-text-muted` now themes the FileUpload hint.
19
+ - **The thumbnail remove button shows on keyboard focus.** It was `opacity-0` until the tile was hovered, so tabbing onto it focused an invisible control. **Consumer action:** none.
20
+ - **Known limit:** the kit's muted token is 4.83:1 on the default light surface and 6.75:1 on the default dark one, below AAA's 7:1 in both. FileUpload uses it as the other components do; the token is unchanged. An app that needs AAA can map `--fancy-text-muted` / `--fancy-text-muted-dark`.
21
+
14
22
  ## [6.11.0] - 2026-11-04
15
23
 
16
24
  ### Added
@@ -26,6 +26,7 @@ function FileUploadDropzone({
26
26
  const inputId = useId();
27
27
  const custom = children !== void 0 && children !== null;
28
28
  const inputLabel = browseLabel ?? (custom ? DEFAULT_BROWSE_LABEL : DEFAULT_COPY);
29
+ const hintColour = dragOver ? "text-[color:var(--fancy-text-subtle,var(--color-zinc-600))] dark:text-[color:var(--fancy-text-subtle-dark,var(--color-zinc-400))]" : "text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]";
29
30
  const handleDrop = useCallback(
30
31
  (e) => {
31
32
  e.preventDefault();
@@ -111,8 +112,8 @@ function FileUploadDropzone({
111
112
  disabled,
112
113
  className: "flex cursor-pointer flex-col items-center rounded-md outline-none disabled:cursor-not-allowed",
113
114
  children: [
114
- /* @__PURE__ */ jsx(Upload, { size: 32, strokeWidth: 1.5, "aria-hidden": "true", className: "mb-2 text-zinc-400" }),
115
- /* @__PURE__ */ jsx("span", { className: "text-sm text-zinc-500", children: DEFAULT_COPY })
115
+ /* @__PURE__ */ jsx(Upload, { size: 32, strokeWidth: 1.5, "aria-hidden": "true", className: cn("mb-2", hintColour) }),
116
+ /* @__PURE__ */ jsx("span", { className: cn("text-sm", hintColour), children: DEFAULT_COPY })
116
117
  ]
117
118
  }
118
119
  )
@@ -130,6 +131,9 @@ function getExtension(name) {
130
131
  const parts = name.split(".");
131
132
  return parts.length > 1 ? parts.pop().toUpperCase() : "";
132
133
  }
134
+ var MUTED = "text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]";
135
+ var FAINT = "text-[color:var(--fancy-text-faint,var(--color-zinc-400))] dark:text-[color:var(--fancy-text-faint-dark,var(--color-zinc-500))]";
136
+ var REMOVE = `${MUTED} hover:text-[color:var(--fancy-text-soft,var(--color-zinc-700))] dark:hover:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))]`;
133
137
  function ThumbnailItem({ file, index, onRemove }) {
134
138
  const isImage = file.type.startsWith("image/");
135
139
  const objectUrl = useMemo(() => isImage ? URL.createObjectURL(file) : null, [file, isImage]);
@@ -141,21 +145,21 @@ function ThumbnailItem({ file, index, onRemove }) {
141
145
  return /* @__PURE__ */ jsxs("div", { className: "group relative flex w-20 flex-col items-center gap-1", children: [
142
146
  /* @__PURE__ */ jsxs("div", { className: "relative h-20 w-20 overflow-hidden rounded-lg border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-zinc-50 dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-zinc-800", children: [
143
147
  isImage && objectUrl ? /* @__PURE__ */ jsx("img", { src: objectUrl, alt: file.name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col items-center justify-center gap-1", children: [
144
- /* @__PURE__ */ jsx(File, { size: 20, className: "text-zinc-400" }),
145
- /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-zinc-500", children: getExtension(file.name) })
148
+ /* @__PURE__ */ jsx(File, { size: 20, "aria-hidden": "true", className: FAINT }),
149
+ /* @__PURE__ */ jsx("span", { className: cn("text-[10px] font-medium", MUTED), children: getExtension(file.name) })
146
150
  ] }),
147
151
  /* @__PURE__ */ jsx(
148
152
  "button",
149
153
  {
150
154
  type: "button",
151
155
  onClick: () => onRemove(index),
152
- className: "absolute top-1 end-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",
156
+ className: "absolute top-1 end-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 focus-visible:opacity-100",
153
157
  "aria-label": `Remove ${file.name}`,
154
- children: /* @__PURE__ */ jsx(X, { size: 10 })
158
+ children: /* @__PURE__ */ jsx(X, { size: 10, "aria-hidden": "true" })
155
159
  }
156
160
  )
157
161
  ] }),
158
- /* @__PURE__ */ jsx("span", { className: "w-full truncate text-center text-xs text-zinc-500", children: file.name })
162
+ /* @__PURE__ */ jsx("span", { className: cn("w-full truncate text-center text-xs", MUTED), children: file.name })
159
163
  ] });
160
164
  }
161
165
  function FileUploadList({ thumbnail, className }) {
@@ -169,17 +173,17 @@ function FileUploadList({ thumbnail, className }) {
169
173
  {
170
174
  className: "flex items-center gap-3 rounded-lg border border-[color:var(--fancy-border,var(--color-zinc-200))] px-3 py-2 text-sm dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))]",
171
175
  children: [
172
- /* @__PURE__ */ jsx(File, { size: 16, className: "shrink-0 text-zinc-400" }),
176
+ /* @__PURE__ */ jsx(File, { size: 16, "aria-hidden": "true", className: cn("shrink-0", FAINT) }),
173
177
  /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: file.name }),
174
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-zinc-400", children: formatSize(file.size) }),
178
+ /* @__PURE__ */ jsx("span", { className: cn("shrink-0 text-xs", MUTED), children: formatSize(file.size) }),
175
179
  /* @__PURE__ */ jsx(
176
180
  "button",
177
181
  {
178
182
  type: "button",
179
183
  onClick: () => removeFile(i),
180
- className: "shrink-0 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300",
184
+ className: cn("shrink-0", REMOVE),
181
185
  "aria-label": `Remove ${file.name}`,
182
- children: /* @__PURE__ */ jsx(X, { size: 14 })
186
+ children: /* @__PURE__ */ jsx(X, { size: 14, "aria-hidden": "true" })
183
187
  }
184
188
  )
185
189
  ]
@@ -273,5 +277,5 @@ var FileUpload = Object.assign(FileUploadRoot, {
273
277
  });
274
278
 
275
279
  export { FileUpload, useFileUpload };
276
- //# sourceMappingURL=chunk-ZHTK4IPQ.js.map
277
- //# sourceMappingURL=chunk-ZHTK4IPQ.js.map
280
+ //# sourceMappingURL=chunk-4676NOI6.js.map
281
+ //# sourceMappingURL=chunk-4676NOI6.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-4676NOI6.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, useId, 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\n/** Accessible name of the browse trigger when the zone has custom children. */\nconst DEFAULT_BROWSE_LABEL = \"Browse files\";\n\n/** The default zone copy; it doubles as the trigger's accessible name. */\nconst DEFAULT_COPY = \"Drop files here or click to browse\";\n\n/*\n * Keyboard access (after 5.59.0). Until then the zone was a clickable div around a\n * display:none input: no tab stop, no role, no key handler, so a keyboard or\n * switch user could not open the picker at all.\n *\n * The trigger is a REAL <button type=\"button\">, not a div with role=\"button\":\n * focus, Enter and Space (Space on keyup, cancelled by moving off) then come\n * from the platform instead of a key handler that has to get those right.\n *\n * - Default content: the button wraps the icon and the copy, so the visible\n * text IS its accessible name and the look is unchanged.\n * - Custom children: they may contain their own interactive content, which a\n * button must not wrap, so the button is visually hidden beside them and the\n * ZONE draws the focus ring (has-focus-visible) — the focus indicator is\n * never invisible.\n *\n * A click anywhere on the zone still opens the picker, as before; a click on\n * the button bubbles to the zone and is handled there once.\n */\nexport function FileUploadDropzone({\n children,\n className,\n browseLabel,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled, accept, multiple } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const custom = children !== undefined && children !== null;\n const inputLabel = browseLabel ?? (custom ? DEFAULT_BROWSE_LABEL : DEFAULT_COPY);\n\n /*\n * The hint (and the icon above it) read the host's text tokens. They were\n * bare zinc until 6.11.1, so an app that mapped `--fancy-text-muted` themed\n * every component but this one: on a #0A2347 surface the hint was 3.23:1.\n *\n * Drag-over steps up to `subtle`: the drag fill (blue-50) is darker than the\n * page, and muted on it is 4.44:1, under AA. Subtle is 7.09:1 there.\n */\n const hintColour = dragOver\n ? \"text-[color:var(--fancy-text-subtle,var(--color-zinc-600))] dark:text-[color:var(--fancy-text-subtle-dark,var(--color-zinc-400))]\"\n : \"text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\";\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 const handleClick = useCallback(\n (e: React.MouseEvent) => {\n if (disabled) return;\n // The input's own click bubbles back up here; re-entering would ask for a\n // second picker.\n if (e.target === inputRef.current) return;\n inputRef.current?.click();\n },\n [disabled],\n );\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n data-drag-over={dragOver ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n aria-disabled={disabled || undefined}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={handleClick}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-[color,background-color,border-color,box-shadow]\",\n // Full opacity on purpose: a translucent 2px ring over a dashed border\n // falls under WCAG 2.2's 3:1 for a focus indicator. The offset gap is\n // painted with the surface token so it matches the page in dark mode.\n \"has-focus-visible:ring-2 has-focus-visible:ring-offset-2 has-focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))] has-focus-visible:ring-offset-[color:var(--fancy-surface,var(--color-white))] dark:has-focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))] dark:has-focus-visible:ring-offset-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-[color:var(--fancy-border-strong,var(--color-zinc-300))] hover:border-zinc-400 dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"hidden\"\n tabIndex={-1}\n aria-label={inputLabel}\n onChange={(e) => {\n if (e.target.files) addFiles(e.target.files);\n\n // Reset, or choosing the SAME file again fires no `change` event at\n // all and the gesture is simply dead — \"remove it, then re-add it\"\n // and \"retry the import that just failed\" both stop working. The\n // component never sees those files to reject them; it is not asked.\n e.target.value = \"\";\n }}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n />\n {custom ? (\n <>\n {children}\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n disabled={disabled}\n className=\"sr-only\"\n >\n {browseLabel ?? DEFAULT_BROWSE_LABEL}\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n aria-label={browseLabel}\n disabled={disabled}\n className=\"flex cursor-pointer flex-col items-center rounded-md outline-none disabled:cursor-not-allowed\"\n >\n <Upload size={32} strokeWidth={1.5} aria-hidden=\"true\" className={cn(\"mb-2\", hintColour)} />\n <span className={cn(\"text-sm\", hintColour)}>{DEFAULT_COPY}</span>\n </button>\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\n/*\n * Text colours read the host's `--fancy-text-*` tokens (bare zinc until\n * 6.11.1). Fallbacks are the kit-wide defaults, so an app that maps nothing\n * sees the same greys every other component uses.\n */\nconst MUTED =\n \"text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\";\n/** Decorative file glyphs (aria-hidden): themed, but owe no text contrast. */\nconst FAINT =\n \"text-[color:var(--fancy-text-faint,var(--color-zinc-400))] dark:text-[color:var(--fancy-text-faint-dark,var(--color-zinc-500))]\";\n/**\n * The list's remove control is an icon with no text, so its glyph owes 3:1\n * (WCAG 1.4.11). Bare zinc-400 was 2.62:1 on white; muted is 4.83:1.\n */\nconst REMOVE =\n `${MUTED} hover:text-[color:var(--fancy-text-soft,var(--color-zinc-700))] dark:hover:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))]`;\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-[color:var(--fancy-border,var(--color-zinc-200))] bg-zinc-50 dark:border-[color:var(--fancy-border-dark,var(--color-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} aria-hidden=\"true\" className={FAINT} />\n <span className={cn(\"text-[10px] font-medium\", MUTED)}>{getExtension(file.name)}</span>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => onRemove(index)}\n // Hidden until the tile is hovered, so keyboard focus must reveal it\n // too, or a keyboard user tabs onto an invisible control.\n className=\"absolute top-1 end-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 focus-visible:opacity-100\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={10} aria-hidden=\"true\" />\n </button>\n </div>\n <span className={cn(\"w-full truncate text-center text-xs\", MUTED)}>{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-[color:var(--fancy-border,var(--color-zinc-200))] px-3 py-2 text-sm dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))]\"\n >\n <File size={16} aria-hidden=\"true\" className={cn(\"shrink-0\", FAINT)} />\n <span className=\"flex-1 truncate\">{file.name}</span>\n <span className={cn(\"shrink-0 text-xs\", MUTED)}>\n {formatSize(file.size)}\n </span>\n <button\n type=\"button\"\n onClick={() => removeFile(i)}\n className={cn(\"shrink-0\", REMOVE)}\n aria-label={`Remove ${file.name}`}\n >\n <X size={14} aria-hidden=\"true\" />\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 {\n FileRejection,\n FileRejectionReason,\n FileUploadProps,\n} from \"./FileUpload.types\";\n\nfunction rejection(file: File, reason: FileRejectionReason): FileRejection {\n return { name: file.name, size: file.size, type: file.type, reason };\n}\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n onReject,\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 const rejections: FileRejection[] = [];\n\n // Each file is reported once, under the first rule it failed.\n const keepOnly = (\n passes: (f: File) => boolean,\n reason: FileRejectionReason,\n ) => {\n toAdd = toAdd.filter((f) => {\n if (passes(f)) return true;\n rejections.push(rejection(f, reason));\n return false;\n });\n };\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 keepOnly((f) => matchesAccept(f, accept), \"accept\");\n }\n\n if (maxSize) {\n keepOnly((f) => f.size <= maxSize, \"maxSize\");\n }\n\n /*\n * `multiple={false}` REPLACES; `maxFiles` refuses the overflow.\n *\n * These used to be the same rule — a cap of one, first-wins — on the\n * reasoning that the two props must not disagree about what a single-file\n * upload means. But they are different statements:\n *\n * - `maxFiles={n}` is a QUOTA. Dropping five files into a three-file\n * field should keep three, not the last three.\n * - `multiple={false}` is a FIELD SHAPE: this input holds one file. Its\n * analogue is a radio group, where choosing again MOVES the selection.\n *\n * Under the old rule a single-file field could never change its mind: the\n * second choice computed `[old, new].slice(0, 1)` and discarded the new\n * file in silence. The YouGene estate worked around it by remounting the\n * component with a `key`, which is the shape of a bug rather than a\n * preference.\n */\n if (multiple === false) {\n /*\n * The FIRST of the incoming batch, not the last.\n *\n * Two separate questions hide here, and only one of them was the bug:\n *\n * - ACROSS gestures — choose a file, then choose another. The second\n * must win, or the field cannot change its mind. That is the fix.\n * - WITHIN one gesture — drop two files at once onto a single-file\n * field. Which one wins is arbitrary, and taking the first is what\n * has always shipped; there is no replacement intent in a single\n * ambiguous drop.\n *\n * Taking `slice(-1)` would have answered both with \"the last\", quietly\n * changing the second. The existing drop test caught it.\n */\n const replacement = toAdd.slice(0, 1);\n // Nothing survived the accept/size filters — leave the field alone\n // rather than clearing a file the user never asked to remove.\n const next = replacement.length > 0 ? replacement : files;\n\n // The file being replaced was not refused; the rest of this batch was,\n // by a field that holds one.\n for (const f of toAdd.slice(1)) rejections.push(rejection(f, \"maxFiles\"));\n\n if (value === undefined) setInternalFiles(next);\n onChange?.(next);\n if (rejections.length > 0) onReject?.(rejections);\n\n return;\n }\n\n const updated = [...files, ...toAdd];\n const limited = maxFiles ? updated.slice(0, maxFiles) : updated;\n\n for (const f of updated.slice(limited.length)) rejections.push(rejection(f, \"maxFiles\"));\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n if (rejections.length > 0) onReject?.(rejections);\n },\n [accept, files, maxFiles, maxSize, multiple, onChange, onReject, 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"]}
@@ -28,6 +28,7 @@ function FileUploadDropzone({
28
28
  const inputId = react.useId();
29
29
  const custom = children !== void 0 && children !== null;
30
30
  const inputLabel = browseLabel ?? (custom ? DEFAULT_BROWSE_LABEL : DEFAULT_COPY);
31
+ const hintColour = dragOver ? "text-[color:var(--fancy-text-subtle,var(--color-zinc-600))] dark:text-[color:var(--fancy-text-subtle-dark,var(--color-zinc-400))]" : "text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]";
31
32
  const handleDrop = react.useCallback(
32
33
  (e) => {
33
34
  e.preventDefault();
@@ -113,8 +114,8 @@ function FileUploadDropzone({
113
114
  disabled,
114
115
  className: "flex cursor-pointer flex-col items-center rounded-md outline-none disabled:cursor-not-allowed",
115
116
  children: [
116
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { size: 32, strokeWidth: 1.5, "aria-hidden": "true", className: "mb-2 text-zinc-400" }),
117
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-zinc-500", children: DEFAULT_COPY })
117
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { size: 32, strokeWidth: 1.5, "aria-hidden": "true", className: chunkZU5Q77OT_cjs.cn("mb-2", hintColour) }),
118
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: chunkZU5Q77OT_cjs.cn("text-sm", hintColour), children: DEFAULT_COPY })
118
119
  ]
119
120
  }
120
121
  )
@@ -132,6 +133,9 @@ function getExtension(name) {
132
133
  const parts = name.split(".");
133
134
  return parts.length > 1 ? parts.pop().toUpperCase() : "";
134
135
  }
136
+ var MUTED = "text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]";
137
+ var FAINT = "text-[color:var(--fancy-text-faint,var(--color-zinc-400))] dark:text-[color:var(--fancy-text-faint-dark,var(--color-zinc-500))]";
138
+ var REMOVE = `${MUTED} hover:text-[color:var(--fancy-text-soft,var(--color-zinc-700))] dark:hover:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))]`;
135
139
  function ThumbnailItem({ file, index, onRemove }) {
136
140
  const isImage = file.type.startsWith("image/");
137
141
  const objectUrl = react.useMemo(() => isImage ? URL.createObjectURL(file) : null, [file, isImage]);
@@ -143,21 +147,21 @@ function ThumbnailItem({ file, index, onRemove }) {
143
147
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "group relative flex w-20 flex-col items-center gap-1", children: [
144
148
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-20 w-20 overflow-hidden rounded-lg border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-zinc-50 dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-zinc-800", children: [
145
149
  isImage && objectUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: objectUrl, alt: file.name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full w-full flex-col items-center justify-center gap-1", children: [
146
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.File, { size: 20, className: "text-zinc-400" }),
147
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium text-zinc-500", children: getExtension(file.name) })
150
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.File, { size: 20, "aria-hidden": "true", className: FAINT }),
151
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: chunkZU5Q77OT_cjs.cn("text-[10px] font-medium", MUTED), children: getExtension(file.name) })
148
152
  ] }),
149
153
  /* @__PURE__ */ jsxRuntime.jsx(
150
154
  "button",
151
155
  {
152
156
  type: "button",
153
157
  onClick: () => onRemove(index),
154
- className: "absolute top-1 end-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",
158
+ className: "absolute top-1 end-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 focus-visible:opacity-100",
155
159
  "aria-label": `Remove ${file.name}`,
156
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 10 })
160
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 10, "aria-hidden": "true" })
157
161
  }
158
162
  )
159
163
  ] }),
160
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-full truncate text-center text-xs text-zinc-500", children: file.name })
164
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: chunkZU5Q77OT_cjs.cn("w-full truncate text-center text-xs", MUTED), children: file.name })
161
165
  ] });
162
166
  }
163
167
  function FileUploadList({ thumbnail, className }) {
@@ -171,17 +175,17 @@ function FileUploadList({ thumbnail, className }) {
171
175
  {
172
176
  className: "flex items-center gap-3 rounded-lg border border-[color:var(--fancy-border,var(--color-zinc-200))] px-3 py-2 text-sm dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))]",
173
177
  children: [
174
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.File, { size: 16, className: "shrink-0 text-zinc-400" }),
178
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.File, { size: 16, "aria-hidden": "true", className: chunkZU5Q77OT_cjs.cn("shrink-0", FAINT) }),
175
179
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: file.name }),
176
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-zinc-400", children: formatSize(file.size) }),
180
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: chunkZU5Q77OT_cjs.cn("shrink-0 text-xs", MUTED), children: formatSize(file.size) }),
177
181
  /* @__PURE__ */ jsxRuntime.jsx(
178
182
  "button",
179
183
  {
180
184
  type: "button",
181
185
  onClick: () => removeFile(i),
182
- className: "shrink-0 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300",
186
+ className: chunkZU5Q77OT_cjs.cn("shrink-0", REMOVE),
183
187
  "aria-label": `Remove ${file.name}`,
184
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14 })
188
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14, "aria-hidden": "true" })
185
189
  }
186
190
  )
187
191
  ]
@@ -276,5 +280,5 @@ var FileUpload = Object.assign(FileUploadRoot, {
276
280
 
277
281
  exports.FileUpload = FileUpload;
278
282
  exports.useFileUpload = useFileUpload;
279
- //# sourceMappingURL=chunk-NIFHAFKK.cjs.map
280
- //# sourceMappingURL=chunk-NIFHAFKK.cjs.map
283
+ //# sourceMappingURL=chunk-JFPBW5MO.cjs.map
284
+ //# sourceMappingURL=chunk-JFPBW5MO.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","useId","useCallback","jsxs","cn","jsx","Fragment","Upload","useMemo","useEffect","File","X"],"mappings":";;;;;;;AAGO,IAAM,iBAAA,GACXA,oBAA6C,IAAI,CAAA;AAE5C,SAAS,aAAA,GAAwC;AACtD,EAAA,MAAM,GAAA,GAAMC,iBAAW,iBAAiB,CAAA;AACxC,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;ACPA,IAAM,oBAAA,GAAuB,cAAA;AAG7B,IAAM,YAAA,GAAe,oCAAA;AAqBd,SAAS,kBAAA,CAAmB;AAAA,EACjC,QAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAA4B;AAC1B,EAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,MAAA,EAAQ,QAAA,KAAa,aAAA,EAAc;AAC/D,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIC,eAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,QAAA,GAAWC,aAAyB,IAAI,CAAA;AAC9C,EAAA,MAAM,UAAUC,WAAA,EAAM;AACtB,EAAA,MAAM,MAAA,GAAS,QAAA,KAAa,MAAA,IAAa,QAAA,KAAa,IAAA;AACtD,EAAA,MAAM,UAAA,GAAa,WAAA,KAAgB,MAAA,GAAS,oBAAA,GAAuB,YAAA,CAAA;AAUnE,EAAA,MAAM,UAAA,GAAa,WACf,mIAAA,GACA,iIAAA;AAEJ,EAAA,MAAM,UAAA,GAAaC,iBAAA;AAAA,IACjB,CAAC,CAAA,KAAuB;AACtB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,WAAA,CAAY,KAAK,CAAA;AACjB,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,QAAA,CAAS,CAAA,CAAE,aAAa,KAAK,CAAA;AAAA,IAC/B,CAAA;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,GACrB;AAEA,EAAA,MAAM,cAAA,GAAiBA,iBAAA,CAAY,CAAC,CAAA,KAAuB;AACzD,IAAA,CAAA,CAAE,cAAA,EAAe;AACjB,IAAA,WAAA,CAAY,IAAI,CAAA;AAAA,EAClB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,kBAAkBA,iBAAA,CAAY,MAAM,YAAY,KAAK,CAAA,EAAG,EAAE,CAAA;AAEhE,EAAA,MAAM,WAAA,GAAcA,iBAAA;AAAA,IAClB,CAAC,CAAA,KAAwB;AACvB,MAAA,IAAI,QAAA,EAAU;AAGd,MAAA,IAAI,CAAA,CAAE,MAAA,KAAW,QAAA,CAAS,OAAA,EAAS;AACnC,MAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AAAA,IAC1B,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AAEA,EAAA,uBACEC,eAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,uCAAA,EAAsC,EAAA;AAAA,MACtC,gBAAA,EAAgB,WAAW,EAAA,GAAK,MAAA;AAAA,MAChC,eAAA,EAAe,WAAW,EAAA,GAAK,MAAA;AAAA,MAC/B,iBAAe,QAAA,IAAY,MAAA;AAAA,MAC3B,MAAA,EAAQ,UAAA;AAAA,MACR,UAAA,EAAY,cAAA;AAAA,MACZ,WAAA,EAAa,eAAA;AAAA,MACb,OAAA,EAAS,WAAA;AAAA,MACT,SAAA,EAAWC,oBAAA;AAAA,QACT,wKAAA;AAAA;AAAA;AAAA;AAAA,QAIA,6XAAA;AAAA,QACA,WACI,kEAAA,GACA,4LAAA;AAAA,QACJ,QAAA,IAAY,+BAAA;AAAA,QACZ;AAAA,OACF;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAAC,cAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,QAAA;AAAA,YACL,EAAA,EAAI,OAAA;AAAA,YACJ,IAAA,EAAK,MAAA;AAAA,YACL,SAAA,EAAU,QAAA;AAAA,YACV,QAAA,EAAU,EAAA;AAAA,YACV,YAAA,EAAY,UAAA;AAAA,YACZ,QAAA,EAAU,CAAC,CAAA,KAAM;AACf,cAAA,IAAI,EAAE,MAAA,CAAO,KAAA,EAAO,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AAM3C,cAAA,CAAA,CAAE,OAAO,KAAA,GAAQ,EAAA;AAAA,YACnB,CAAA;AAAA,YACA,MAAA;AAAA,YACA,QAAA;AAAA,YACA;AAAA;AAAA,SACF;AAAA,QACC,yBACCF,eAAA,CAAAG,mBAAA,EAAA,EACG,QAAA,EAAA;AAAA,UAAA,QAAA;AAAA,0BACDD,cAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,qCAAA,EAAoC,EAAA;AAAA,cACpC,eAAA,EAAe,OAAA;AAAA,cACf,QAAA;AAAA,cACA,SAAA,EAAU,SAAA;AAAA,cAET,QAAA,EAAA,WAAA,IAAe;AAAA;AAAA;AAClB,SAAA,EACF,CAAA,mBAEAF,eAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,qCAAA,EAAoC,EAAA;AAAA,YACpC,eAAA,EAAe,OAAA;AAAA,YACf,YAAA,EAAY,WAAA;AAAA,YACZ,QAAA;AAAA,YACA,SAAA,EAAU,+FAAA;AAAA,YAEV,QAAA,EAAA;AAAA,8BAAAE,cAAA,CAACE,kBAAA,EAAA,EAAO,IAAA,EAAM,EAAA,EAAI,WAAA,EAAa,GAAA,EAAK,aAAA,EAAY,MAAA,EAAO,SAAA,EAAWH,oBAAA,CAAG,MAAA,EAAQ,UAAU,CAAA,EAAG,CAAA;AAAA,6CACzF,MAAA,EAAA,EAAK,SAAA,EAAWA,qBAAG,SAAA,EAAW,UAAU,GAAI,QAAA,EAAA,YAAA,EAAa;AAAA;AAAA;AAAA;AAC5D;AAAA;AAAA,GAEJ;AAEJ;AAEA,kBAAA,CAAmB,WAAA,GAAc,oBAAA;ACtJjC,SAAS,WAAW,KAAA,EAAuB;AACzC,EAAA,IAAI,KAAA,GAAQ,IAAA,EAAM,OAAO,CAAA,EAAG,KAAK,CAAA,EAAA,CAAA;AACjC,EAAA,IAAI,KAAA,GAAQ,OAAO,IAAA,EAAM,OAAO,IAAI,KAAA,GAAQ,IAAA,EAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,GAAA,CAAA;AAC5D,EAAA,OAAO,IAAI,KAAA,IAAS,IAAA,GAAO,IAAA,CAAA,EAAO,OAAA,CAAQ,CAAC,CAAC,CAAA,GAAA,CAAA;AAC9C;AAEA,SAAS,aAAa,IAAA,EAAsB;AAC1C,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC5B,EAAA,OAAO,MAAM,MAAA,GAAS,CAAA,GAAI,MAAM,GAAA,EAAI,CAAG,aAAY,GAAI,EAAA;AACzD;AAOA,IAAM,KAAA,GACJ,iIAAA;AAEF,IAAM,KAAA,GACJ,iIAAA;AAKF,IAAM,MAAA,GACJ,GAAG,KAAK,CAAA,0IAAA,CAAA;AAEV,SAAS,aAAA,CAAc,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,EAAiE;AAC9G,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,UAAA,CAAW,QAAQ,CAAA;AAC7C,EAAA,MAAM,SAAA,GAAYI,aAAA,CAAQ,MAAO,OAAA,GAAU,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA,GAAI,IAAA,EAAO,CAAC,IAAA,EAAM,OAAO,CAAC,CAAA;AAE7F,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,SAAA,EAAW,GAAA,CAAI,eAAA,CAAgB,SAAS,CAAA;AAAA,IAC9C,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAEd,EAAA,uBACEN,eAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,sDAAA,EACb,QAAA,EAAA;AAAA,oBAAAA,eAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8MAAA,EACZ,QAAA,EAAA;AAAA,MAAA,OAAA,IAAW,4BACVE,cAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,WAAW,GAAA,EAAK,IAAA,CAAK,IAAA,EAAM,SAAA,EAAU,8BAA6B,CAAA,mBAE5EF,eAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,+DAAA,EACb,QAAA,EAAA;AAAA,wBAAAE,eAACK,gBAAA,EAAA,EAAK,IAAA,EAAM,IAAI,aAAA,EAAY,MAAA,EAAO,WAAW,KAAA,EAAO,CAAA;AAAA,wBACrDL,cAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAWD,oBAAA,CAAG,yBAAA,EAA2B,KAAK,CAAA,EAAI,QAAA,EAAA,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,EAAE;AAAA,OAAA,EAClF,CAAA;AAAA,sBAEFC,cAAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,OAAA,EAAS,MAAM,QAAA,CAAS,KAAK,CAAA;AAAA,UAG7B,SAAA,EAAU,oMAAA;AAAA,UACV,YAAA,EAAY,CAAA,OAAA,EAAU,IAAA,CAAK,IAAI,CAAA,CAAA;AAAA,UAE/B,0BAAAA,cAAAA,CAACM,aAAA,EAAA,EAAE,IAAA,EAAM,EAAA,EAAI,eAAY,MAAA,EAAO;AAAA;AAAA;AAClC,KAAA,EACF,CAAA;AAAA,oBACAN,eAAC,MAAA,EAAA,EAAK,SAAA,EAAWD,qBAAG,qCAAA,EAAuC,KAAK,CAAA,EAAI,QAAA,EAAA,IAAA,CAAK,IAAA,EAAK;AAAA,GAAA,EAChF,CAAA;AAEJ;AAEO,SAAS,cAAA,CAAe,EAAE,SAAA,EAAW,SAAA,EAAU,EAAwB;AAC5E,EAAA,MAAM,EAAE,KAAA,EAAO,UAAA,EAAW,GAAI,aAAA,EAAc;AAE5C,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAE/B,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,uBACEC,cAAAA,CAAC,KAAA,EAAA,EAAI,mCAAA,EAAkC,EAAA,EAAG,SAAA,EAAWD,oBAAA,CAAG,2BAAA,EAA6B,SAAS,CAAA,EAC3F,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,qBAChBC,cAAAA,CAAC,aAAA,EAAA,EAAwC,IAAA,EAAY,KAAA,EAAO,CAAA,EAAG,QAAA,EAAU,UAAA,EAAA,EAArD,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,CAAA,EAAI,CAAC,CAAA,CAAgD,CACtF,CAAA,EACH,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACEA,cAAAA,CAAC,IAAA,EAAA,EAAG,mCAAA,EAAkC,IAAG,SAAA,EAAWD,oBAAA,CAAG,gBAAA,EAAkB,SAAS,GAC/E,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,sBAChBD,eAAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MAEC,SAAA,EAAU,yLAAA;AAAA,MAEV,QAAA,EAAA;AAAA,wBAAAE,cAAAA,CAACK,gBAAA,EAAA,EAAK,IAAA,EAAM,EAAA,EAAI,aAAA,EAAY,QAAO,SAAA,EAAWN,oBAAA,CAAG,UAAA,EAAY,KAAK,CAAA,EAAG,CAAA;AAAA,wBACrEC,cAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iBAAA,EAAmB,eAAK,IAAA,EAAK,CAAA;AAAA,wBAC7CA,cAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAWD,oBAAA,CAAG,kBAAA,EAAoB,KAAK,CAAA,EAC1C,QAAA,EAAA,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA,EACvB,CAAA;AAAA,wBACAC,cAAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,OAAA,EAAS,MAAM,UAAA,CAAW,CAAC,CAAA;AAAA,YAC3B,SAAA,EAAWD,oBAAA,CAAG,UAAA,EAAY,MAAM,CAAA;AAAA,YAChC,YAAA,EAAY,CAAA,OAAA,EAAU,IAAA,CAAK,IAAI,CAAA,CAAA;AAAA,YAE/B,0BAAAC,cAAAA,CAACM,aAAA,EAAA,EAAE,IAAA,EAAM,EAAA,EAAI,eAAY,MAAA,EAAO;AAAA;AAAA;AAClC;AAAA,KAAA;AAAA,IAfK,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,CAAA,EAAI,CAAC,CAAA;AAAA,GAiBzB,CAAA,EACH,CAAA;AAEJ;AAEA,cAAA,CAAe,WAAA,GAAc,gBAAA;;;AC9FtB,SAAS,aAAA,CAAc,MAAY,MAAA,EAAyB;AACjE,EAAA,MAAM,KAAA,GAAQ,MAAA,CACX,KAAA,CAAM,GAAG,EACT,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,MAAK,CAAE,WAAA,EAAa,CAAA,CACvC,OAAO,OAAO,CAAA;AAIjB,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAE/B,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,WAAA,EAAY;AACnC,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,WAAA,EAAY;AAEnC,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,CAAC,IAAA,KAAS;AAC1B,IAAA,IAAI,KAAK,UAAA,CAAW,GAAG,GAAG,OAAO,IAAA,CAAK,SAAS,IAAI,CAAA;AACnD,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG,OAAO,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA;AACjE,IAAA,OAAO,IAAA,KAAS,IAAA;AAAA,EAClB,CAAC,CAAA;AACH;ACxBA,SAAS,SAAA,CAAU,MAAY,MAAA,EAA4C;AACzE,EAAA,OAAO,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,IAAA,EAAM,KAAK,IAAA,EAAM,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,MAAA,EAAO;AACrE;AAEA,SAAS,cAAA,CAAe;AAAA,EACtB,QAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA,GAAW,IAAA;AAAA,EACX,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX;AACF,CAAA,EAAoB;AAClB,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIZ,cAAAA,CAAiB,EAAE,CAAA;AAC7D,EAAA,MAAM,QAAQ,KAAA,IAAS,aAAA;AAEvB,EAAA,MAAM,QAAA,GAAWG,iBAAAA;AAAA,IACf,CAAC,QAAA,KAAgC;AAC/B,MAAA,IAAI,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAA;AAC/B,MAAA,MAAM,aAA8B,EAAC;AAGrC,MAAA,MAAM,QAAA,GAAW,CACf,MAAA,EACA,MAAA,KACG;AACH,QAAA,KAAA,GAAQ,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAM;AAC1B,UAAA,IAAI,MAAA,CAAO,CAAC,CAAA,EAAG,OAAO,IAAA;AACtB,UAAA,UAAA,CAAW,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,MAAM,CAAC,CAAA;AACpC,UAAA,OAAO,KAAA;AAAA,QACT,CAAC,CAAA;AAAA,MACH,CAAA;AAIA,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,QAAA,CAAS,CAAC,CAAA,KAAM,aAAA,CAAc,CAAA,EAAG,MAAM,GAAG,QAAQ,CAAA;AAAA,MACpD;AAEA,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,QAAA,CAAS,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,IAAQ,SAAS,SAAS,CAAA;AAAA,MAC9C;AAoBA,MAAA,IAAI,aAAa,KAAA,EAAO;AAgBtB,QAAA,MAAM,WAAA,GAAc,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AAGpC,QAAA,MAAM,IAAA,GAAO,WAAA,CAAY,MAAA,GAAS,CAAA,GAAI,WAAA,GAAc,KAAA;AAIpD,QAAA,KAAA,MAAW,CAAA,IAAK,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA,aAAc,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,UAAU,CAAC,CAAA;AAExE,QAAA,IAAI,KAAA,KAAU,MAAA,EAAW,gBAAA,CAAiB,IAAI,CAAA;AAC9C,QAAA,QAAA,GAAW,IAAI,CAAA;AACf,QAAA,IAAI,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG,QAAA,GAAW,UAAU,CAAA;AAEhD,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,CAAC,GAAG,KAAA,EAAO,GAAG,KAAK,CAAA;AACnC,MAAA,MAAM,UAAU,QAAA,GAAW,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA,GAAI,OAAA;AAExD,MAAA,KAAA,MAAW,CAAA,IAAK,OAAA,CAAQ,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,EAAG,UAAA,CAAW,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,UAAU,CAAC,CAAA;AAEvF,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,gBAAA,CAAiB,OAAO,CAAA;AACjD,MAAA,QAAA,GAAW,OAAO,CAAA;AAClB,MAAA,IAAI,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG,QAAA,GAAW,UAAU,CAAA;AAAA,IAClD,CAAA;AAAA,IACA,CAAC,QAAQ,KAAA,EAAO,QAAA,EAAU,SAAS,QAAA,EAAU,QAAA,EAAU,UAAU,KAAK;AAAA,GACxE;AAEA,EAAA,MAAM,UAAA,GAAaA,iBAAAA;AAAA,IACjB,CAAC,KAAA,KAAkB;AACjB,MAAA,MAAM,UAAU,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,MAAM,KAAK,CAAA;AAClD,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,gBAAA,CAAiB,OAAO,CAAA;AACjD,MAAA,QAAA,GAAW,OAAO,CAAA;AAAA,IACpB,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,QAAA,EAAU,KAAK;AAAA,GACzB;AAEA,EAAA,MAAM,GAAA,GAAMM,aAAAA;AAAA,IACV,OAAO,EAAE,KAAA,EAAO,UAAU,UAAA,EAAY,QAAA,EAAU,QAAQ,QAAA,EAAS,CAAA;AAAA,IACjE,CAAC,KAAA,EAAO,QAAA,EAAU,UAAA,EAAY,QAAA,EAAU,QAAQ,QAAQ;AAAA,GAC1D;AAEA,EAAA,uBACEH,cAAAA,CAAC,iBAAA,CAAkB,QAAA,EAAlB,EAA2B,OAAO,GAAA,EACjC,QAAA,kBAAAA,cAAAA,CAAC,KAAA,EAAA,EAAI,gCAA6B,EAAA,EAAG,SAAA,EAAWD,qBAAG,SAAS,CAAA,EAAI,UAAS,CAAA,EAC3E,CAAA;AAEJ;AAEO,IAAM,UAAA,GAAa,MAAA,CAAO,MAAA,CAAO,cAAA,EAAgB;AAAA,EACtD,QAAA,EAAU,kBAAA;AAAA,EACV,IAAA,EAAM;AACR,CAAC","file":"chunk-JFPBW5MO.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, useId, 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\n/** Accessible name of the browse trigger when the zone has custom children. */\nconst DEFAULT_BROWSE_LABEL = \"Browse files\";\n\n/** The default zone copy; it doubles as the trigger's accessible name. */\nconst DEFAULT_COPY = \"Drop files here or click to browse\";\n\n/*\n * Keyboard access (after 5.59.0). Until then the zone was a clickable div around a\n * display:none input: no tab stop, no role, no key handler, so a keyboard or\n * switch user could not open the picker at all.\n *\n * The trigger is a REAL <button type=\"button\">, not a div with role=\"button\":\n * focus, Enter and Space (Space on keyup, cancelled by moving off) then come\n * from the platform instead of a key handler that has to get those right.\n *\n * - Default content: the button wraps the icon and the copy, so the visible\n * text IS its accessible name and the look is unchanged.\n * - Custom children: they may contain their own interactive content, which a\n * button must not wrap, so the button is visually hidden beside them and the\n * ZONE draws the focus ring (has-focus-visible) — the focus indicator is\n * never invisible.\n *\n * A click anywhere on the zone still opens the picker, as before; a click on\n * the button bubbles to the zone and is handled there once.\n */\nexport function FileUploadDropzone({\n children,\n className,\n browseLabel,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled, accept, multiple } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const custom = children !== undefined && children !== null;\n const inputLabel = browseLabel ?? (custom ? DEFAULT_BROWSE_LABEL : DEFAULT_COPY);\n\n /*\n * The hint (and the icon above it) read the host's text tokens. They were\n * bare zinc until 6.11.1, so an app that mapped `--fancy-text-muted` themed\n * every component but this one: on a #0A2347 surface the hint was 3.23:1.\n *\n * Drag-over steps up to `subtle`: the drag fill (blue-50) is darker than the\n * page, and muted on it is 4.44:1, under AA. Subtle is 7.09:1 there.\n */\n const hintColour = dragOver\n ? \"text-[color:var(--fancy-text-subtle,var(--color-zinc-600))] dark:text-[color:var(--fancy-text-subtle-dark,var(--color-zinc-400))]\"\n : \"text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\";\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 const handleClick = useCallback(\n (e: React.MouseEvent) => {\n if (disabled) return;\n // The input's own click bubbles back up here; re-entering would ask for a\n // second picker.\n if (e.target === inputRef.current) return;\n inputRef.current?.click();\n },\n [disabled],\n );\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n data-drag-over={dragOver ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n aria-disabled={disabled || undefined}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={handleClick}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-[color,background-color,border-color,box-shadow]\",\n // Full opacity on purpose: a translucent 2px ring over a dashed border\n // falls under WCAG 2.2's 3:1 for a focus indicator. The offset gap is\n // painted with the surface token so it matches the page in dark mode.\n \"has-focus-visible:ring-2 has-focus-visible:ring-offset-2 has-focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))] has-focus-visible:ring-offset-[color:var(--fancy-surface,var(--color-white))] dark:has-focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))] dark:has-focus-visible:ring-offset-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-[color:var(--fancy-border-strong,var(--color-zinc-300))] hover:border-zinc-400 dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"hidden\"\n tabIndex={-1}\n aria-label={inputLabel}\n onChange={(e) => {\n if (e.target.files) addFiles(e.target.files);\n\n // Reset, or choosing the SAME file again fires no `change` event at\n // all and the gesture is simply dead — \"remove it, then re-add it\"\n // and \"retry the import that just failed\" both stop working. The\n // component never sees those files to reject them; it is not asked.\n e.target.value = \"\";\n }}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n />\n {custom ? (\n <>\n {children}\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n disabled={disabled}\n className=\"sr-only\"\n >\n {browseLabel ?? DEFAULT_BROWSE_LABEL}\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n aria-label={browseLabel}\n disabled={disabled}\n className=\"flex cursor-pointer flex-col items-center rounded-md outline-none disabled:cursor-not-allowed\"\n >\n <Upload size={32} strokeWidth={1.5} aria-hidden=\"true\" className={cn(\"mb-2\", hintColour)} />\n <span className={cn(\"text-sm\", hintColour)}>{DEFAULT_COPY}</span>\n </button>\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\n/*\n * Text colours read the host's `--fancy-text-*` tokens (bare zinc until\n * 6.11.1). Fallbacks are the kit-wide defaults, so an app that maps nothing\n * sees the same greys every other component uses.\n */\nconst MUTED =\n \"text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\";\n/** Decorative file glyphs (aria-hidden): themed, but owe no text contrast. */\nconst FAINT =\n \"text-[color:var(--fancy-text-faint,var(--color-zinc-400))] dark:text-[color:var(--fancy-text-faint-dark,var(--color-zinc-500))]\";\n/**\n * The list's remove control is an icon with no text, so its glyph owes 3:1\n * (WCAG 1.4.11). Bare zinc-400 was 2.62:1 on white; muted is 4.83:1.\n */\nconst REMOVE =\n `${MUTED} hover:text-[color:var(--fancy-text-soft,var(--color-zinc-700))] dark:hover:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))]`;\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-[color:var(--fancy-border,var(--color-zinc-200))] bg-zinc-50 dark:border-[color:var(--fancy-border-dark,var(--color-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} aria-hidden=\"true\" className={FAINT} />\n <span className={cn(\"text-[10px] font-medium\", MUTED)}>{getExtension(file.name)}</span>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => onRemove(index)}\n // Hidden until the tile is hovered, so keyboard focus must reveal it\n // too, or a keyboard user tabs onto an invisible control.\n className=\"absolute top-1 end-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 focus-visible:opacity-100\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={10} aria-hidden=\"true\" />\n </button>\n </div>\n <span className={cn(\"w-full truncate text-center text-xs\", MUTED)}>{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-[color:var(--fancy-border,var(--color-zinc-200))] px-3 py-2 text-sm dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))]\"\n >\n <File size={16} aria-hidden=\"true\" className={cn(\"shrink-0\", FAINT)} />\n <span className=\"flex-1 truncate\">{file.name}</span>\n <span className={cn(\"shrink-0 text-xs\", MUTED)}>\n {formatSize(file.size)}\n </span>\n <button\n type=\"button\"\n onClick={() => removeFile(i)}\n className={cn(\"shrink-0\", REMOVE)}\n aria-label={`Remove ${file.name}`}\n >\n <X size={14} aria-hidden=\"true\" />\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 {\n FileRejection,\n FileRejectionReason,\n FileUploadProps,\n} from \"./FileUpload.types\";\n\nfunction rejection(file: File, reason: FileRejectionReason): FileRejection {\n return { name: file.name, size: file.size, type: file.type, reason };\n}\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n onReject,\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 const rejections: FileRejection[] = [];\n\n // Each file is reported once, under the first rule it failed.\n const keepOnly = (\n passes: (f: File) => boolean,\n reason: FileRejectionReason,\n ) => {\n toAdd = toAdd.filter((f) => {\n if (passes(f)) return true;\n rejections.push(rejection(f, reason));\n return false;\n });\n };\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 keepOnly((f) => matchesAccept(f, accept), \"accept\");\n }\n\n if (maxSize) {\n keepOnly((f) => f.size <= maxSize, \"maxSize\");\n }\n\n /*\n * `multiple={false}` REPLACES; `maxFiles` refuses the overflow.\n *\n * These used to be the same rule — a cap of one, first-wins — on the\n * reasoning that the two props must not disagree about what a single-file\n * upload means. But they are different statements:\n *\n * - `maxFiles={n}` is a QUOTA. Dropping five files into a three-file\n * field should keep three, not the last three.\n * - `multiple={false}` is a FIELD SHAPE: this input holds one file. Its\n * analogue is a radio group, where choosing again MOVES the selection.\n *\n * Under the old rule a single-file field could never change its mind: the\n * second choice computed `[old, new].slice(0, 1)` and discarded the new\n * file in silence. The YouGene estate worked around it by remounting the\n * component with a `key`, which is the shape of a bug rather than a\n * preference.\n */\n if (multiple === false) {\n /*\n * The FIRST of the incoming batch, not the last.\n *\n * Two separate questions hide here, and only one of them was the bug:\n *\n * - ACROSS gestures — choose a file, then choose another. The second\n * must win, or the field cannot change its mind. That is the fix.\n * - WITHIN one gesture — drop two files at once onto a single-file\n * field. Which one wins is arbitrary, and taking the first is what\n * has always shipped; there is no replacement intent in a single\n * ambiguous drop.\n *\n * Taking `slice(-1)` would have answered both with \"the last\", quietly\n * changing the second. The existing drop test caught it.\n */\n const replacement = toAdd.slice(0, 1);\n // Nothing survived the accept/size filters — leave the field alone\n // rather than clearing a file the user never asked to remove.\n const next = replacement.length > 0 ? replacement : files;\n\n // The file being replaced was not refused; the rest of this batch was,\n // by a field that holds one.\n for (const f of toAdd.slice(1)) rejections.push(rejection(f, \"maxFiles\"));\n\n if (value === undefined) setInternalFiles(next);\n onChange?.(next);\n if (rejections.length > 0) onReject?.(rejections);\n\n return;\n }\n\n const updated = [...files, ...toAdd];\n const limited = maxFiles ? updated.slice(0, maxFiles) : updated;\n\n for (const f of updated.slice(limited.length)) rejections.push(rejection(f, \"maxFiles\"));\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n if (rejections.length > 0) onReject?.(rejections);\n },\n [accept, files, maxFiles, maxSize, multiple, onChange, onReject, 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 chunkNIFHAFKK_cjs = require('./chunk-NIFHAFKK.cjs');
3
+ var chunkJFPBW5MO_cjs = require('./chunk-JFPBW5MO.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 chunkNIFHAFKK_cjs.FileUpload; }
10
+ get: function () { return chunkJFPBW5MO_cjs.FileUpload; }
11
11
  });
12
12
  Object.defineProperty(exports, "useFileUpload", {
13
13
  enumerable: true,
14
- get: function () { return chunkNIFHAFKK_cjs.useFileUpload; }
14
+ get: function () { return chunkJFPBW5MO_cjs.useFileUpload; }
15
15
  });
16
16
  //# sourceMappingURL=file-upload.cjs.map
17
17
  //# sourceMappingURL=file-upload.cjs.map
@@ -1,4 +1,4 @@
1
- export { FileUpload, useFileUpload } from './chunk-ZHTK4IPQ.js';
1
+ export { FileUpload, useFileUpload } from './chunk-4676NOI6.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
@@ -75,7 +75,7 @@ var chunk7K2C744H_cjs = require('./chunk-7K2C744H.cjs');
75
75
  var chunkVH34IZ7R_cjs = require('./chunk-VH34IZ7R.cjs');
76
76
  var chunkZDHLEHYR_cjs = require('./chunk-ZDHLEHYR.cjs');
77
77
  var chunkXQF4HCOV_cjs = require('./chunk-XQF4HCOV.cjs');
78
- var chunkNIFHAFKK_cjs = require('./chunk-NIFHAFKK.cjs');
78
+ var chunkJFPBW5MO_cjs = require('./chunk-JFPBW5MO.cjs');
79
79
  var chunkWVSHAJKI_cjs = require('./chunk-WVSHAJKI.cjs');
80
80
  var chunkOOZCJLEF_cjs = require('./chunk-OOZCJLEF.cjs');
81
81
  var chunkNFUKNVB7_cjs = require('./chunk-NFUKNVB7.cjs');
@@ -661,11 +661,11 @@ Object.defineProperty(exports, "validateFolderName", {
661
661
  });
662
662
  Object.defineProperty(exports, "FileUpload", {
663
663
  enumerable: true,
664
- get: function () { return chunkNIFHAFKK_cjs.FileUpload; }
664
+ get: function () { return chunkJFPBW5MO_cjs.FileUpload; }
665
665
  });
666
666
  Object.defineProperty(exports, "useFileUpload", {
667
667
  enumerable: true,
668
- get: function () { return chunkNIFHAFKK_cjs.useFileUpload; }
668
+ get: function () { return chunkJFPBW5MO_cjs.useFileUpload; }
669
669
  });
670
670
  Object.defineProperty(exports, "FullScreen", {
671
671
  enumerable: true,
package/dist/index.js CHANGED
@@ -73,7 +73,7 @@ export { EmojiSelect } from './chunk-R3PCZSBQ.js';
73
73
  export { Eyebrow } from './chunk-FRUHEDZN.js';
74
74
  export { FauxClient } from './chunk-XH24BGEK.js';
75
75
  export { FileBrowser, FileBrowserNewFolder, useFileBrowser, validateFolderName } from './chunk-YZW7HQXH.js';
76
- export { FileUpload, useFileUpload } from './chunk-ZHTK4IPQ.js';
76
+ export { FileUpload, useFileUpload } from './chunk-4676NOI6.js';
77
77
  export { FullScreen } from './chunk-GZ5Z56QK.js';
78
78
  export { Grid } from './chunk-TM3RTWKI.js';
79
79
  export { Command, useCommand } from './chunk-LG2O26CG.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/react-fancy",
3
- "version": "6.11.0",
3
+ "version": "6.11.1",
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/matchesAccept.ts","../src/components/FileUpload/FileUpload.tsx"],"names":["createContext","useContext","useState","useRef","useId","useCallback","jsxs","cn","jsx","Fragment","Upload","useMemo","useEffect","File","X"],"mappings":"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","file":"chunk-NIFHAFKK.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, useId, 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\n/** Accessible name of the browse trigger when the zone has custom children. */\nconst DEFAULT_BROWSE_LABEL = \"Browse files\";\n\n/** The default zone copy; it doubles as the trigger's accessible name. */\nconst DEFAULT_COPY = \"Drop files here or click to browse\";\n\n/*\n * Keyboard access (after 5.59.0). Until then the zone was a clickable div around a\n * display:none input: no tab stop, no role, no key handler, so a keyboard or\n * switch user could not open the picker at all.\n *\n * The trigger is a REAL <button type=\"button\">, not a div with role=\"button\":\n * focus, Enter and Space (Space on keyup, cancelled by moving off) then come\n * from the platform instead of a key handler that has to get those right.\n *\n * - Default content: the button wraps the icon and the copy, so the visible\n * text IS its accessible name and the look is unchanged.\n * - Custom children: they may contain their own interactive content, which a\n * button must not wrap, so the button is visually hidden beside them and the\n * ZONE draws the focus ring (has-focus-visible) — the focus indicator is\n * never invisible.\n *\n * A click anywhere on the zone still opens the picker, as before; a click on\n * the button bubbles to the zone and is handled there once.\n */\nexport function FileUploadDropzone({\n children,\n className,\n browseLabel,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled, accept, multiple } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const custom = children !== undefined && children !== null;\n const inputLabel = browseLabel ?? (custom ? DEFAULT_BROWSE_LABEL : DEFAULT_COPY);\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 const handleClick = useCallback(\n (e: React.MouseEvent) => {\n if (disabled) return;\n // The input's own click bubbles back up here; re-entering would ask for a\n // second picker.\n if (e.target === inputRef.current) return;\n inputRef.current?.click();\n },\n [disabled],\n );\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n data-drag-over={dragOver ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n aria-disabled={disabled || undefined}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={handleClick}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-[color,background-color,border-color,box-shadow]\",\n // Full opacity on purpose: a translucent 2px ring over a dashed border\n // falls under WCAG 2.2's 3:1 for a focus indicator. The offset gap is\n // painted with the surface token so it matches the page in dark mode.\n \"has-focus-visible:ring-2 has-focus-visible:ring-offset-2 has-focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))] has-focus-visible:ring-offset-[color:var(--fancy-surface,var(--color-white))] dark:has-focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))] dark:has-focus-visible:ring-offset-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-[color:var(--fancy-border-strong,var(--color-zinc-300))] hover:border-zinc-400 dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"hidden\"\n tabIndex={-1}\n aria-label={inputLabel}\n onChange={(e) => {\n if (e.target.files) addFiles(e.target.files);\n\n // Reset, or choosing the SAME file again fires no `change` event at\n // all and the gesture is simply dead — \"remove it, then re-add it\"\n // and \"retry the import that just failed\" both stop working. The\n // component never sees those files to reject them; it is not asked.\n e.target.value = \"\";\n }}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n />\n {custom ? (\n <>\n {children}\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n disabled={disabled}\n className=\"sr-only\"\n >\n {browseLabel ?? DEFAULT_BROWSE_LABEL}\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n aria-label={browseLabel}\n disabled={disabled}\n className=\"flex cursor-pointer flex-col items-center rounded-md outline-none disabled:cursor-not-allowed\"\n >\n <Upload size={32} strokeWidth={1.5} aria-hidden=\"true\" className=\"mb-2 text-zinc-400\" />\n <span className=\"text-sm text-zinc-500\">{DEFAULT_COPY}</span>\n </button>\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-[color:var(--fancy-border,var(--color-zinc-200))] bg-zinc-50 dark:border-[color:var(--fancy-border-dark,var(--color-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 end-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-[color:var(--fancy-border,var(--color-zinc-200))] px-3 py-2 text-sm dark:border-[color:var(--fancy-border-dark,var(--color-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 {\n FileRejection,\n FileRejectionReason,\n FileUploadProps,\n} from \"./FileUpload.types\";\n\nfunction rejection(file: File, reason: FileRejectionReason): FileRejection {\n return { name: file.name, size: file.size, type: file.type, reason };\n}\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n onReject,\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 const rejections: FileRejection[] = [];\n\n // Each file is reported once, under the first rule it failed.\n const keepOnly = (\n passes: (f: File) => boolean,\n reason: FileRejectionReason,\n ) => {\n toAdd = toAdd.filter((f) => {\n if (passes(f)) return true;\n rejections.push(rejection(f, reason));\n return false;\n });\n };\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 keepOnly((f) => matchesAccept(f, accept), \"accept\");\n }\n\n if (maxSize) {\n keepOnly((f) => f.size <= maxSize, \"maxSize\");\n }\n\n /*\n * `multiple={false}` REPLACES; `maxFiles` refuses the overflow.\n *\n * These used to be the same rule — a cap of one, first-wins — on the\n * reasoning that the two props must not disagree about what a single-file\n * upload means. But they are different statements:\n *\n * - `maxFiles={n}` is a QUOTA. Dropping five files into a three-file\n * field should keep three, not the last three.\n * - `multiple={false}` is a FIELD SHAPE: this input holds one file. Its\n * analogue is a radio group, where choosing again MOVES the selection.\n *\n * Under the old rule a single-file field could never change its mind: the\n * second choice computed `[old, new].slice(0, 1)` and discarded the new\n * file in silence. The YouGene estate worked around it by remounting the\n * component with a `key`, which is the shape of a bug rather than a\n * preference.\n */\n if (multiple === false) {\n /*\n * The FIRST of the incoming batch, not the last.\n *\n * Two separate questions hide here, and only one of them was the bug:\n *\n * - ACROSS gestures — choose a file, then choose another. The second\n * must win, or the field cannot change its mind. That is the fix.\n * - WITHIN one gesture — drop two files at once onto a single-file\n * field. Which one wins is arbitrary, and taking the first is what\n * has always shipped; there is no replacement intent in a single\n * ambiguous drop.\n *\n * Taking `slice(-1)` would have answered both with \"the last\", quietly\n * changing the second. The existing drop test caught it.\n */\n const replacement = toAdd.slice(0, 1);\n // Nothing survived the accept/size filters — leave the field alone\n // rather than clearing a file the user never asked to remove.\n const next = replacement.length > 0 ? replacement : files;\n\n // The file being replaced was not refused; the rest of this batch was,\n // by a field that holds one.\n for (const f of toAdd.slice(1)) rejections.push(rejection(f, \"maxFiles\"));\n\n if (value === undefined) setInternalFiles(next);\n onChange?.(next);\n if (rejections.length > 0) onReject?.(rejections);\n\n return;\n }\n\n const updated = [...files, ...toAdd];\n const limited = maxFiles ? updated.slice(0, maxFiles) : updated;\n\n for (const f of updated.slice(limited.length)) rejections.push(rejection(f, \"maxFiles\"));\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n if (rejections.length > 0) onReject?.(rejections);\n },\n [accept, files, maxFiles, maxSize, multiple, onChange, onReject, 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 +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/matchesAccept.ts","../src/components/FileUpload/FileUpload.tsx"],"names":["jsxs","jsx","useState","useCallback","useMemo"],"mappings":"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","file":"chunk-ZHTK4IPQ.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, useId, 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\n/** Accessible name of the browse trigger when the zone has custom children. */\nconst DEFAULT_BROWSE_LABEL = \"Browse files\";\n\n/** The default zone copy; it doubles as the trigger's accessible name. */\nconst DEFAULT_COPY = \"Drop files here or click to browse\";\n\n/*\n * Keyboard access (after 5.59.0). Until then the zone was a clickable div around a\n * display:none input: no tab stop, no role, no key handler, so a keyboard or\n * switch user could not open the picker at all.\n *\n * The trigger is a REAL <button type=\"button\">, not a div with role=\"button\":\n * focus, Enter and Space (Space on keyup, cancelled by moving off) then come\n * from the platform instead of a key handler that has to get those right.\n *\n * - Default content: the button wraps the icon and the copy, so the visible\n * text IS its accessible name and the look is unchanged.\n * - Custom children: they may contain their own interactive content, which a\n * button must not wrap, so the button is visually hidden beside them and the\n * ZONE draws the focus ring (has-focus-visible) — the focus indicator is\n * never invisible.\n *\n * A click anywhere on the zone still opens the picker, as before; a click on\n * the button bubbles to the zone and is handled there once.\n */\nexport function FileUploadDropzone({\n children,\n className,\n browseLabel,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled, accept, multiple } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const custom = children !== undefined && children !== null;\n const inputLabel = browseLabel ?? (custom ? DEFAULT_BROWSE_LABEL : DEFAULT_COPY);\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 const handleClick = useCallback(\n (e: React.MouseEvent) => {\n if (disabled) return;\n // The input's own click bubbles back up here; re-entering would ask for a\n // second picker.\n if (e.target === inputRef.current) return;\n inputRef.current?.click();\n },\n [disabled],\n );\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n data-drag-over={dragOver ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n aria-disabled={disabled || undefined}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={handleClick}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-[color,background-color,border-color,box-shadow]\",\n // Full opacity on purpose: a translucent 2px ring over a dashed border\n // falls under WCAG 2.2's 3:1 for a focus indicator. The offset gap is\n // painted with the surface token so it matches the page in dark mode.\n \"has-focus-visible:ring-2 has-focus-visible:ring-offset-2 has-focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))] has-focus-visible:ring-offset-[color:var(--fancy-surface,var(--color-white))] dark:has-focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))] dark:has-focus-visible:ring-offset-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-[color:var(--fancy-border-strong,var(--color-zinc-300))] hover:border-zinc-400 dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"hidden\"\n tabIndex={-1}\n aria-label={inputLabel}\n onChange={(e) => {\n if (e.target.files) addFiles(e.target.files);\n\n // Reset, or choosing the SAME file again fires no `change` event at\n // all and the gesture is simply dead — \"remove it, then re-add it\"\n // and \"retry the import that just failed\" both stop working. The\n // component never sees those files to reject them; it is not asked.\n e.target.value = \"\";\n }}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n />\n {custom ? (\n <>\n {children}\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n disabled={disabled}\n className=\"sr-only\"\n >\n {browseLabel ?? DEFAULT_BROWSE_LABEL}\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n aria-label={browseLabel}\n disabled={disabled}\n className=\"flex cursor-pointer flex-col items-center rounded-md outline-none disabled:cursor-not-allowed\"\n >\n <Upload size={32} strokeWidth={1.5} aria-hidden=\"true\" className=\"mb-2 text-zinc-400\" />\n <span className=\"text-sm text-zinc-500\">{DEFAULT_COPY}</span>\n </button>\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-[color:var(--fancy-border,var(--color-zinc-200))] bg-zinc-50 dark:border-[color:var(--fancy-border-dark,var(--color-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 end-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-[color:var(--fancy-border,var(--color-zinc-200))] px-3 py-2 text-sm dark:border-[color:var(--fancy-border-dark,var(--color-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 {\n FileRejection,\n FileRejectionReason,\n FileUploadProps,\n} from \"./FileUpload.types\";\n\nfunction rejection(file: File, reason: FileRejectionReason): FileRejection {\n return { name: file.name, size: file.size, type: file.type, reason };\n}\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n onReject,\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 const rejections: FileRejection[] = [];\n\n // Each file is reported once, under the first rule it failed.\n const keepOnly = (\n passes: (f: File) => boolean,\n reason: FileRejectionReason,\n ) => {\n toAdd = toAdd.filter((f) => {\n if (passes(f)) return true;\n rejections.push(rejection(f, reason));\n return false;\n });\n };\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 keepOnly((f) => matchesAccept(f, accept), \"accept\");\n }\n\n if (maxSize) {\n keepOnly((f) => f.size <= maxSize, \"maxSize\");\n }\n\n /*\n * `multiple={false}` REPLACES; `maxFiles` refuses the overflow.\n *\n * These used to be the same rule — a cap of one, first-wins — on the\n * reasoning that the two props must not disagree about what a single-file\n * upload means. But they are different statements:\n *\n * - `maxFiles={n}` is a QUOTA. Dropping five files into a three-file\n * field should keep three, not the last three.\n * - `multiple={false}` is a FIELD SHAPE: this input holds one file. Its\n * analogue is a radio group, where choosing again MOVES the selection.\n *\n * Under the old rule a single-file field could never change its mind: the\n * second choice computed `[old, new].slice(0, 1)` and discarded the new\n * file in silence. The YouGene estate worked around it by remounting the\n * component with a `key`, which is the shape of a bug rather than a\n * preference.\n */\n if (multiple === false) {\n /*\n * The FIRST of the incoming batch, not the last.\n *\n * Two separate questions hide here, and only one of them was the bug:\n *\n * - ACROSS gestures — choose a file, then choose another. The second\n * must win, or the field cannot change its mind. That is the fix.\n * - WITHIN one gesture — drop two files at once onto a single-file\n * field. Which one wins is arbitrary, and taking the first is what\n * has always shipped; there is no replacement intent in a single\n * ambiguous drop.\n *\n * Taking `slice(-1)` would have answered both with \"the last\", quietly\n * changing the second. The existing drop test caught it.\n */\n const replacement = toAdd.slice(0, 1);\n // Nothing survived the accept/size filters — leave the field alone\n // rather than clearing a file the user never asked to remove.\n const next = replacement.length > 0 ? replacement : files;\n\n // The file being replaced was not refused; the rest of this batch was,\n // by a field that holds one.\n for (const f of toAdd.slice(1)) rejections.push(rejection(f, \"maxFiles\"));\n\n if (value === undefined) setInternalFiles(next);\n onChange?.(next);\n if (rejections.length > 0) onReject?.(rejections);\n\n return;\n }\n\n const updated = [...files, ...toAdd];\n const limited = maxFiles ? updated.slice(0, maxFiles) : updated;\n\n for (const f of updated.slice(limited.length)) rejections.push(rejection(f, \"maxFiles\"));\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n if (rejections.length > 0) onReject?.(rejections);\n },\n [accept, files, maxFiles, maxSize, multiple, onChange, onReject, 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"]}