@mbao01/common 0.0.35 → 0.0.37

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.
@@ -5,7 +5,7 @@ export declare const FileUploader: {
5
5
  displayName: string;
6
6
  Content: import("react").ForwardRefExoticComponent<import("react").HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
7
7
  Input: {
8
- ({ className, children, ...props }: FileUploaderInputProps): import("react/jsx-runtime").JSX.Element;
8
+ ({ classes, children, ...props }: FileUploaderInputProps): import("react/jsx-runtime").JSX.Element;
9
9
  displayName: string;
10
10
  };
11
11
  Item: import("react").ForwardRefExoticComponent<{
@@ -1,4 +1,4 @@
1
- import type { Dispatch, SetStateAction } from "react";
1
+ import type { Dispatch, Ref, SetStateAction } from "react";
2
2
  import type { DropzoneState, DropzoneOptions } from "react-dropzone";
3
3
  export type FileUploaderProps = {
4
4
  value: File[] | null;
@@ -7,7 +7,14 @@ export type FileUploaderProps = {
7
7
  dropzoneOptions: DropzoneOptions;
8
8
  orientation?: "horizontal" | "vertical";
9
9
  } & React.HTMLAttributes<HTMLDivElement>;
10
- export type FileUploaderInputProps = React.InputHTMLAttributes<HTMLInputElement>;
10
+ export type FileUploaderInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "className"> & {
11
+ classes?: Partial<{
12
+ all: string;
13
+ accepted: string;
14
+ rejected: string;
15
+ default: string;
16
+ }>;
17
+ };
11
18
  export type DirectionOptions = "rtl" | "ltr" | undefined;
12
19
  export type FileUploaderContextType = {
13
20
  dropzoneState: DropzoneState;
@@ -19,4 +26,5 @@ export type FileUploaderContextType = {
19
26
  setActiveIndex: Dispatch<SetStateAction<number>>;
20
27
  orientation: "horizontal" | "vertical";
21
28
  direction: DirectionOptions;
29
+ hiddenInputRef: Ref<HTMLInputElement>;
22
30
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mbao01/common",
3
3
  "private": false,
4
- "version": "0.0.35",
4
+ "version": "0.0.37",
5
5
  "type": "module",
6
6
  "author": "Ayomide Bakare",
7
7
  "license": "MIT",
@@ -145,5 +145,5 @@
145
145
  "react-dom": "^18.2.0",
146
146
  "typescript": "^5.2.2"
147
147
  },
148
- "gitHead": "4e024b19d151c05b6c1580f461e348f5376c33b9"
148
+ "gitHead": "dc6e10ed230647d7e4626895c8d9b6a6808daab5"
149
149
  }
@@ -24,6 +24,7 @@ export const FileUploader = ({
24
24
  dir,
25
25
  ...props
26
26
  }: FileUploaderProps) => {
27
+ const hiddenInputRef = useRef<HTMLInputElement>(null);
27
28
  const [isFileTooBig, setIsFileTooBig] = useState(false);
28
29
  const [isLOF, setIsLOF] = useState(false);
29
30
  const [activeIndex, setActiveIndex] = useState(-1);
@@ -54,54 +55,67 @@ export const FileUploader = ({
54
55
 
55
56
  const handleKeyDown = useCallback(
56
57
  (e: React.KeyboardEvent<HTMLDivElement>) => {
57
- e.preventDefault();
58
- e.stopPropagation();
59
-
60
- if (!value) return;
61
-
62
- const moveNext = () => {
63
- const nextIndex = activeIndex + 1;
64
- setActiveIndex(nextIndex > value.length - 1 ? 0 : nextIndex);
65
- };
66
-
67
- const movePrev = () => {
68
- const nextIndex = activeIndex - 1;
69
- setActiveIndex(nextIndex < 0 ? value.length - 1 : nextIndex);
70
- };
71
-
72
- const prevKey =
73
- orientation === "horizontal"
74
- ? direction === "ltr"
75
- ? "ArrowLeft"
76
- : "ArrowRight"
77
- : "ArrowUp";
78
-
79
- const nextKey =
80
- orientation === "horizontal"
81
- ? direction === "ltr"
82
- ? "ArrowRight"
83
- : "ArrowLeft"
84
- : "ArrowDown";
85
-
86
- if (e.key === nextKey) {
87
- moveNext();
88
- } else if (e.key === prevKey) {
89
- movePrev();
90
- } else if (e.key === "Enter" || e.key === "Space") {
91
- if (activeIndex === -1) {
92
- dropzoneState.inputRef.current?.click();
93
- }
94
- } else if (e.key === "Delete" || e.key === "Backspace") {
95
- if (activeIndex !== -1) {
96
- removeFileFromSet(activeIndex);
97
- if (value.length - 1 === 0) {
98
- setActiveIndex(-1);
99
- return;
100
- }
58
+ const files = value ?? [];
59
+ const KEY_LIST = [
60
+ "ArrowLeft",
61
+ "ArrowRight",
62
+ "ArrowUp",
63
+ "ArrowDown",
64
+ "Enter",
65
+ "Space",
66
+ "Delete",
67
+ "Backspace",
68
+ "Escape",
69
+ ];
70
+
71
+ if (KEY_LIST.includes(e.key)) {
72
+ e.preventDefault();
73
+ e.stopPropagation();
74
+
75
+ const moveNext = () => {
76
+ const nextIndex = activeIndex + 1;
77
+ setActiveIndex(nextIndex > files.length - 1 ? 0 : nextIndex);
78
+ };
79
+
80
+ const movePrev = () => {
81
+ const nextIndex = activeIndex - 1;
82
+ setActiveIndex(nextIndex < 0 ? files.length - 1 : nextIndex);
83
+ };
84
+
85
+ const prevKey =
86
+ orientation === "horizontal"
87
+ ? direction === "ltr"
88
+ ? "ArrowLeft"
89
+ : "ArrowRight"
90
+ : "ArrowUp";
91
+
92
+ const nextKey =
93
+ orientation === "horizontal"
94
+ ? direction === "ltr"
95
+ ? "ArrowRight"
96
+ : "ArrowLeft"
97
+ : "ArrowDown";
98
+
99
+ if (e.key === nextKey) {
100
+ moveNext();
101
+ } else if (e.key === prevKey) {
101
102
  movePrev();
103
+ } else if (e.key === "Enter" || e.key === "Space") {
104
+ if (activeIndex === -1) {
105
+ dropzoneState.inputRef.current?.click();
106
+ }
107
+ } else if (e.key === "Delete" || e.key === "Backspace") {
108
+ if (activeIndex !== -1) {
109
+ removeFileFromSet(activeIndex);
110
+ if (files.length - 1 === 0) {
111
+ setActiveIndex(-1);
112
+ return;
113
+ }
114
+ movePrev();
115
+ }
116
+ } else if (e.key === "Escape") {
117
+ setActiveIndex(-1);
102
118
  }
103
- } else if (e.key === "Escape") {
104
- setActiveIndex(-1);
105
119
  }
106
120
  },
107
121
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -131,6 +145,16 @@ export const FileUploader = ({
131
145
 
132
146
  onValueChange(newValues);
133
147
 
148
+ if (hiddenInputRef.current) {
149
+ // Note the specific way we need to munge the file into the hidden input
150
+ // https://stackoverflow.com/a/68182158/1068446
151
+ const dataTransfer = new DataTransfer();
152
+ newValues.forEach((v) => {
153
+ dataTransfer.items.add(v);
154
+ });
155
+ hiddenInputRef.current.files = dataTransfer.files;
156
+ }
157
+
134
158
  if (rejectedFiles.length > 0) {
135
159
  for (const rejectedFile of rejectedFiles) {
136
160
  if (rejectedFile.errors[0]?.code === "file-too-large") {
@@ -182,20 +206,17 @@ export const FileUploader = ({
182
206
  setActiveIndex,
183
207
  orientation,
184
208
  direction,
209
+ hiddenInputRef,
185
210
  }}
186
211
  >
187
212
  <div
188
- tabIndex={0}
189
213
  onKeyDownCapture={handleKeyDown}
190
- className={cn(
191
- "grid w-full focus:outline-none overflow-hidden ",
192
- className,
193
- {
194
- "gap-2": value && value.length > 0,
195
- }
196
- )}
214
+ className={cn("grid w-full overflow-hidden", className, {
215
+ "gap-2": value && value.length > 0,
216
+ })}
197
217
  dir={dir}
198
218
  {...props}
219
+ tabIndex={-1}
199
220
  >
200
221
  {children}
201
222
  </div>
@@ -245,9 +266,9 @@ const FileUploaderItem = forwardRef<
245
266
  <div
246
267
  ref={ref}
247
268
  className={cn(
248
- "h-6 p-1 justify-between cursor-pointer relative",
249
- className,
250
- isSelected ? "bg-muted" : ""
269
+ "h-6 p-1 justify-between cursor-pointer relative rounded",
270
+ { "bg-base-300": isSelected },
271
+ className
251
272
  )}
252
273
  {...props}
253
274
  >
@@ -263,7 +284,12 @@ const FileUploaderItem = forwardRef<
263
284
  onClick={() => removeFileFromSet(index)}
264
285
  >
265
286
  <span className="sr-only">remove item {index}</span>
266
- <TrashIcon className="w-4 h-4 hover:stroke-destructive duration-200 ease-in-out" />
287
+ <TrashIcon
288
+ className={cn(
289
+ "w-4 h-4 shrink-0 hover:stroke-destructive duration-200 ease-in-out",
290
+ { "text-error": isSelected }
291
+ )}
292
+ />
267
293
  </button>
268
294
  </div>
269
295
  );
@@ -272,41 +298,43 @@ const FileUploaderItem = forwardRef<
272
298
  FileUploaderItem.displayName = "FileUploaderItem";
273
299
 
274
300
  const FileUploaderInput = ({
275
- className,
301
+ classes,
276
302
  children,
277
303
  ...props
278
304
  }: FileUploaderInputProps) => {
279
- const { dropzoneState, isFileTooBig, isLOF } = useFileUpload();
305
+ const { dropzoneState, isFileTooBig, isLOF, hiddenInputRef } =
306
+ useFileUpload();
280
307
  const rootProps = isLOF ? {} : dropzoneState.getRootProps();
281
308
  return (
282
- <div
283
- {...props}
284
- className={`relative w-full ${
285
- isLOF ? "opacity-50 cursor-not-allowed " : "cursor-pointer "
286
- }`}
287
- >
309
+ <div {...props} className="relative w-full">
288
310
  <div
289
311
  className={cn(
290
- `w-full rounded-lg duration-300 ease-in-out
291
- ${
292
- dropzoneState.isDragAccept
293
- ? "border-green-500"
294
- : dropzoneState.isDragReject || isFileTooBig
295
- ? "border-red-500"
296
- : "border-gray-300"
297
- }`,
298
- className
312
+ classes?.all,
313
+ "w-full rounded-lg duration-300 ease-in-out",
314
+ isLOF ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
315
+ dropzoneState.isDragAccept
316
+ ? classes?.accepted
317
+ : dropzoneState.isDragReject || isFileTooBig
318
+ ? classes?.rejected
319
+ : classes?.default
299
320
  )}
300
321
  {...rootProps}
301
322
  >
302
323
  {children}
303
324
  </div>
304
325
  <input
326
+ ref={hiddenInputRef}
305
327
  {...props}
306
- disabled={isLOF}
328
+ type="file"
329
+ tabIndex={-1}
330
+ className="hidden opacity-0"
331
+ />
332
+ <input
307
333
  ref={dropzoneState.inputRef}
334
+ disabled={isLOF}
308
335
  {...dropzoneState.getInputProps()}
309
- className={`${isLOF ? "cursor-not-allowed" : ""}`}
336
+ tabIndex={-1}
337
+ className={cn({ "cursor-not-allowed": isLOF })}
310
338
  />
311
339
  </div>
312
340
  );
@@ -1,4 +1,4 @@
1
- import type { Dispatch, SetStateAction } from "react";
1
+ import type { Dispatch, Ref, SetStateAction } from "react";
2
2
  import type { DropzoneState, DropzoneOptions } from "react-dropzone";
3
3
 
4
4
  export type FileUploaderProps = {
@@ -9,8 +9,17 @@ export type FileUploaderProps = {
9
9
  orientation?: "horizontal" | "vertical";
10
10
  } & React.HTMLAttributes<HTMLDivElement>;
11
11
 
12
- export type FileUploaderInputProps =
13
- React.InputHTMLAttributes<HTMLInputElement>;
12
+ export type FileUploaderInputProps = Omit<
13
+ React.InputHTMLAttributes<HTMLInputElement>,
14
+ "className"
15
+ > & {
16
+ classes?: Partial<{
17
+ all: string;
18
+ accepted: string;
19
+ rejected: string;
20
+ default: string;
21
+ }>;
22
+ };
14
23
 
15
24
  export type DirectionOptions = "rtl" | "ltr" | undefined;
16
25
 
@@ -24,4 +33,5 @@ export type FileUploaderContextType = {
24
33
  setActiveIndex: Dispatch<SetStateAction<number>>;
25
34
  orientation: "horizontal" | "vertical";
26
35
  direction: DirectionOptions;
36
+ hiddenInputRef: Ref<HTMLInputElement>;
27
37
  };