@carlonicora/nextjs-jsonapi 1.140.2 → 1.141.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.
@@ -14,7 +14,15 @@ import {
14
14
  useRef,
15
15
  useState,
16
16
  } from "react";
17
- import { Accept, DropzoneOptions, DropzoneState, FileRejection, useDropzone } from "react-dropzone";
17
+ import {
18
+ Accept,
19
+ DropzoneOptions,
20
+ DropzoneState,
21
+ ErrorCode,
22
+ FileRejection,
23
+ FileWithPath,
24
+ useDropzone,
25
+ } from "react-dropzone";
18
26
  import { showError } from "../../utils/toast";
19
27
  import { buttonVariants, Input, Tooltip, TooltipContent, TooltipTrigger } from "../../shadcnui";
20
28
  import { cn } from "../../utils";
@@ -51,11 +59,29 @@ type FileUploaderProps = {
51
59
  onValueChange: (value: File[] | null) => void;
52
60
  dropzoneOptions: DropzoneOptions;
53
61
  orientation?: "horizontal" | "vertical";
62
+ /**
63
+ * Opt-in rejection reporting. When supplied, the component hands every
64
+ * rejection — react-dropzone's own plus the files it had to drop because the
65
+ * `maxFiles` cap was already reached — to the consumer and raises no toast of
66
+ * its own. When absent, the built-in toast behaviour is unchanged.
67
+ */
68
+ onFilesRejected?: (rejections: FileRejection[]) => void;
54
69
  };
55
70
 
56
71
  export const FileUploader = forwardRef<HTMLDivElement, FileUploaderProps & React.HTMLAttributes<HTMLDivElement>>(
57
72
  (
58
- { className, dropzoneOptions, value, onValueChange, reSelect, orientation = "vertical", children, dir, ...props },
73
+ {
74
+ className,
75
+ dropzoneOptions,
76
+ value,
77
+ onValueChange,
78
+ reSelect,
79
+ orientation = "vertical",
80
+ children,
81
+ dir,
82
+ onFilesRejected,
83
+ ...props
84
+ },
59
85
  ref,
60
86
  ) => {
61
87
  const [isFileTooBig, setIsFileTooBig] = useState(false);
@@ -138,14 +164,26 @@ export const FileUploader = forwardRef<HTMLDivElement, FileUploaderProps & React
138
164
  newValues.splice(0, newValues.length);
139
165
  }
140
166
 
141
- files.forEach((file) => {
142
- if (newValues.length < maxFiles) {
143
- newValues.push(file);
144
- }
145
- });
167
+ // Split at the remaining capacity instead of silently dropping the tail:
168
+ // the overflow is a rejection the consumer may want to show.
169
+ const remaining = Math.max(0, maxFiles - newValues.length);
170
+ const admitted = files.slice(0, remaining);
171
+ const overflow = files.slice(remaining);
172
+
173
+ admitted.forEach((file) => newValues.push(file));
146
174
 
147
175
  onValueChange(newValues);
148
176
 
177
+ if (onFilesRejected) {
178
+ const overflowRejections: FileRejection[] = overflow.map((file) => ({
179
+ file: file as FileWithPath,
180
+ errors: [{ code: ErrorCode.TooManyFiles, message: `Too many files. Only ${maxFiles} allowed.` }],
181
+ }));
182
+ const allRejections = [...rejectedFiles, ...overflowRejections];
183
+ if (allRejections.length > 0) onFilesRejected(allRejections);
184
+ return;
185
+ }
186
+
149
187
  if (rejectedFiles.length > 0) {
150
188
  for (let i = 0; i < rejectedFiles.length; i++) {
151
189
  if (rejectedFiles[i].errors[0]?.code === "file-too-large") {
@@ -163,7 +201,7 @@ export const FileUploader = forwardRef<HTMLDivElement, FileUploaderProps & React
163
201
  }
164
202
  }
165
203
  },
166
- [reSelectAll, value],
204
+ [reSelectAll, value, maxFiles, maxSize, onValueChange, onFilesRejected, t],
167
205
  );
168
206
 
169
207
  useEffect(() => {