formik-form-components 2.0.3 → 2.0.4
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/README.md +1 -21
- package/dist/Form/AppAutoCompleter.d.ts +1 -2
- package/dist/Form/AppAutoCompleter.d.ts.map +1 -1
- package/dist/Form/AppCheckBox.d.ts +1 -1
- package/dist/Form/AppCheckBox.d.ts.map +1 -1
- package/dist/Form/AppDateAndTimePicker.d.ts +1 -1
- package/dist/Form/AppDateAndTimePicker.d.ts.map +1 -1
- package/dist/Form/AppFormErrorMessage.d.ts +1 -1
- package/dist/Form/AppFormErrorMessage.d.ts.map +1 -1
- package/dist/Form/AppInputField.d.ts +1 -1
- package/dist/Form/AppInputField.d.ts.map +1 -1
- package/dist/Form/AppMultiSelector.d.ts +1 -1
- package/dist/Form/AppMultiSelector.d.ts.map +1 -1
- package/dist/Form/AppPhoneNoInput.d.ts +1 -1
- package/dist/Form/AppPhoneNoInput.d.ts.map +1 -1
- package/dist/Form/AppRadioGroup.d.ts +1 -1
- package/dist/Form/AppRadioGroup.d.ts.map +1 -1
- package/dist/Form/AppRating.d.ts +1 -1
- package/dist/Form/AppRating.d.ts.map +1 -1
- package/dist/Form/AppSimpleUploadFile.d.ts +1 -1
- package/dist/Form/AppSimpleUploadFile.d.ts.map +1 -1
- package/dist/Form/AppSwitch.d.ts +1 -2
- package/dist/Form/AppSwitch.d.ts.map +1 -1
- package/dist/Form/AppTagsCreator.d.ts +1 -2
- package/dist/Form/AppTagsCreator.d.ts.map +1 -1
- package/dist/Form/AppTextArea.d.ts +1 -1
- package/dist/Form/AppTextArea.d.ts.map +1 -1
- package/dist/Form/AppUploadFile.d.ts +1 -1
- package/dist/Form/AppUploadFile.d.ts.map +1 -1
- package/dist/Form/SubmitButton.d.ts +1 -1
- package/dist/Form/SubmitButton.d.ts.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.esm.js +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/index.d.ts +17 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/package.json +16 -22
- package/src/App.css +0 -38
- package/src/App.test.tsx +0 -9
- package/src/App.tsx +0 -166
- package/src/Form/AppAutoCompleter.tsx +0 -252
- package/src/Form/AppCheckBox.tsx +0 -101
- package/src/Form/AppDateAndTimePicker.tsx +0 -94
- package/src/Form/AppDatePicker.tsx +0 -69
- package/src/Form/AppFormErrorMessage.tsx +0 -34
- package/src/Form/AppInputField.tsx +0 -80
- package/src/Form/AppMultiSelector.tsx +0 -163
- package/src/Form/AppPhoneNoInput.tsx +0 -106
- package/src/Form/AppRadioGroup.tsx +0 -92
- package/src/Form/AppRating.tsx +0 -98
- package/src/Form/AppSelectInput.tsx +0 -249
- package/src/Form/AppSimpleUploadFile.tsx +0 -154
- package/src/Form/AppSwitch.tsx +0 -84
- package/src/Form/AppTagsCreator.tsx +0 -252
- package/src/Form/AppTextArea.tsx +0 -90
- package/src/Form/AppUploadFile.tsx +0 -167
- package/src/Form/SubmitButton.tsx +0 -122
- package/src/Form/index.tsx +0 -27
- package/src/assets/illustrations/BackgroundIllustration.tsx +0 -42
- package/src/assets/illustrations/UploadIllustration.tsx +0 -659
- package/src/assets/illustrations/index.ts +0 -1
- package/src/file-thumbnail/types.ts +0 -7
- package/src/file-thumbnail/utils.ts +0 -162
- package/src/index.css +0 -9
- package/src/index.tsx +0 -19
- package/src/lib/index.ts +0 -47
- package/src/logo.svg +0 -1
- package/src/react-app-env.d.ts +0 -1
- package/src/reportWebVitals.ts +0 -15
- package/src/setupTests.ts +0 -5
- package/src/styles/PhoneInputCustom.css +0 -238
- package/src/styles/compiled-tailwind.css +0 -1
- package/src/upload/Upload.tsx +0 -162
- package/src/upload/errors/RejectionFiles.tsx +0 -49
- package/src/upload/index.ts +0 -5
- package/src/upload/preview/MultiFilePreview.tsx +0 -297
- package/src/upload/preview/SingleFilePreview.tsx +0 -81
- package/src/upload/types.ts +0 -51
package/src/upload/Upload.tsx
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { useDropzone } from "react-dropzone";
|
|
5
|
-
import { XMarkIcon } from "@heroicons/react/20/solid";
|
|
6
|
-
import { UploadIllustration } from "../assets/illustrations";
|
|
7
|
-
import RejectionFiles from "./errors/RejectionFiles";
|
|
8
|
-
import MultiFilePreview from "./preview/MultiFilePreview";
|
|
9
|
-
import SingleFilePreview from "./preview/SingleFilePreview";
|
|
10
|
-
import type { UploadProps } from "./types";
|
|
11
|
-
|
|
12
|
-
interface StyledDropZoneProps {
|
|
13
|
-
isDragActive: boolean;
|
|
14
|
-
isError: boolean;
|
|
15
|
-
disabled?: boolean;
|
|
16
|
-
hasFile: boolean;
|
|
17
|
-
children: React.ReactNode;
|
|
18
|
-
[key: string]: any;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const StyledDropZone: React.FC<StyledDropZoneProps> = ({
|
|
22
|
-
isDragActive,
|
|
23
|
-
isError,
|
|
24
|
-
disabled,
|
|
25
|
-
hasFile,
|
|
26
|
-
children,
|
|
27
|
-
...props
|
|
28
|
-
}) => (
|
|
29
|
-
<div
|
|
30
|
-
className={`
|
|
31
|
-
outline-none cursor-pointer overflow-hidden relative rounded-lg transition-all
|
|
32
|
-
${isDragActive ? "opacity-80" : ""}
|
|
33
|
-
${
|
|
34
|
-
isError
|
|
35
|
-
? "border-2 border-red-500 bg-red-50"
|
|
36
|
-
: "border border-dashed border-gray-300"
|
|
37
|
-
}
|
|
38
|
-
${disabled ? "opacity-50 pointer-events-none" : ""}
|
|
39
|
-
${hasFile ? "p-12" : "p-8"}
|
|
40
|
-
hover:opacity-80
|
|
41
|
-
`}
|
|
42
|
-
{...props}
|
|
43
|
-
>
|
|
44
|
-
{children}
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const Placeholder = () => (
|
|
49
|
-
<div className="grid place-items-center min-h-[200px] w-full">
|
|
50
|
-
<div className="flex flex-col md:flex-row items-center gap-8 text-center md:text-left">
|
|
51
|
-
<div className="w-48">
|
|
52
|
-
<UploadIllustration />
|
|
53
|
-
</div>
|
|
54
|
-
<div>
|
|
55
|
-
<h5 className="text-lg font-medium mb-2">Drop or Select file</h5>
|
|
56
|
-
<p className="text-gray-500">
|
|
57
|
-
Drop files here or click{" "}
|
|
58
|
-
<span className="underline mx-0.5">browse</span> through your machine
|
|
59
|
-
</p>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
const Upload: React.FC<UploadProps> = ({
|
|
66
|
-
disabled,
|
|
67
|
-
multiple = false,
|
|
68
|
-
error,
|
|
69
|
-
helperText,
|
|
70
|
-
file,
|
|
71
|
-
onDelete,
|
|
72
|
-
files,
|
|
73
|
-
thumbnail,
|
|
74
|
-
onUpload,
|
|
75
|
-
onRemove,
|
|
76
|
-
onRemoveAll,
|
|
77
|
-
className = "",
|
|
78
|
-
...other
|
|
79
|
-
}) => {
|
|
80
|
-
const {
|
|
81
|
-
getRootProps,
|
|
82
|
-
getInputProps,
|
|
83
|
-
isDragActive,
|
|
84
|
-
isDragReject,
|
|
85
|
-
fileRejections,
|
|
86
|
-
} = useDropzone({
|
|
87
|
-
multiple,
|
|
88
|
-
disabled,
|
|
89
|
-
...other,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const hasFile = !!file && !multiple;
|
|
93
|
-
const hasFiles = files && multiple && files.length > 0;
|
|
94
|
-
const isError = isDragReject || !!error;
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<div className={`w-full relative ${className}`}>
|
|
98
|
-
<StyledDropZone
|
|
99
|
-
{...getRootProps()}
|
|
100
|
-
isDragActive={isDragActive}
|
|
101
|
-
isError={isError}
|
|
102
|
-
disabled={disabled}
|
|
103
|
-
hasFile={hasFile}
|
|
104
|
-
>
|
|
105
|
-
<input {...getInputProps()} />
|
|
106
|
-
|
|
107
|
-
{!hasFile && <Placeholder />}
|
|
108
|
-
{hasFile && <SingleFilePreview file={file} />}
|
|
109
|
-
</StyledDropZone>
|
|
110
|
-
|
|
111
|
-
{helperText && <p className="mt-2 text-sm text-gray-500">{helperText}</p>}
|
|
112
|
-
|
|
113
|
-
<RejectionFiles fileRejections={fileRejections} />
|
|
114
|
-
|
|
115
|
-
{hasFile && onDelete && (
|
|
116
|
-
<button
|
|
117
|
-
type="button"
|
|
118
|
-
onClick={onDelete}
|
|
119
|
-
className="absolute top-4 right-4 z-10 p-1.5 rounded-full bg-black/70 text-white hover:bg-black/50 transition-colors"
|
|
120
|
-
>
|
|
121
|
-
<XMarkIcon className="w-4 h-4" />
|
|
122
|
-
</button>
|
|
123
|
-
)}
|
|
124
|
-
|
|
125
|
-
{hasFiles && (
|
|
126
|
-
<>
|
|
127
|
-
<div className="my-6">
|
|
128
|
-
<MultiFilePreview
|
|
129
|
-
files={files}
|
|
130
|
-
thumbnail={thumbnail}
|
|
131
|
-
onRemove={onRemove}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
<div className="flex justify-end gap-3">
|
|
136
|
-
{onRemoveAll && (
|
|
137
|
-
<button
|
|
138
|
-
type="button"
|
|
139
|
-
onClick={onRemoveAll}
|
|
140
|
-
className="px-4 py-1.5 text-sm border rounded-md hover:bg-gray-50"
|
|
141
|
-
>
|
|
142
|
-
Remove all
|
|
143
|
-
</button>
|
|
144
|
-
)}
|
|
145
|
-
|
|
146
|
-
{onUpload && (
|
|
147
|
-
<button
|
|
148
|
-
type="button"
|
|
149
|
-
onClick={onUpload}
|
|
150
|
-
className="px-4 py-1.5 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700"
|
|
151
|
-
>
|
|
152
|
-
Upload files
|
|
153
|
-
</button>
|
|
154
|
-
)}
|
|
155
|
-
</div>
|
|
156
|
-
</>
|
|
157
|
-
)}
|
|
158
|
-
</div>
|
|
159
|
-
);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
export default Upload;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import type { FileRejection } from "react-dropzone";
|
|
4
|
-
import { fileData } from "../../file-thumbnail/utils";
|
|
5
|
-
|
|
6
|
-
// ----------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
type Props = {
|
|
9
|
-
fileRejections: readonly FileRejection[];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default function RejectionFiles({
|
|
13
|
-
fileRejections,
|
|
14
|
-
}: Props): React.JSX.Element | null {
|
|
15
|
-
if (!fileRejections.length) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function formatBytes(bytes?: number): string {
|
|
20
|
-
const b = bytes ?? 0;
|
|
21
|
-
if (b === 0) return "0 Bytes";
|
|
22
|
-
const k = 1024;
|
|
23
|
-
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
24
|
-
const i = Math.floor(Math.log(b) / Math.log(k));
|
|
25
|
-
return `${parseFloat((b / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div className="mt-3 rounded-lg border border-red-200 bg-red-50 p-2">
|
|
30
|
-
{fileRejections.map(({ file, errors }) => {
|
|
31
|
-
const { path, size } = fileData(file);
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<div key={path} className="my-1">
|
|
35
|
-
<p className="truncate text-sm font-medium">
|
|
36
|
-
{path} - {size != null ? formatBytes(size) : ""}
|
|
37
|
-
</p>
|
|
38
|
-
|
|
39
|
-
{errors.map((error) => (
|
|
40
|
-
<span key={error.code} className="block text-xs text-red-600">
|
|
41
|
-
- {error.message}
|
|
42
|
-
</span>
|
|
43
|
-
))}
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
})}
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
}
|
package/src/upload/index.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export * from "./types";
|
|
2
|
-
export { default as RejectionFiles } from "./errors/RejectionFiles";
|
|
3
|
-
export { default as MultiFilePreview } from "./preview/MultiFilePreview";
|
|
4
|
-
export { default as SingleFilePreview } from "./preview/SingleFilePreview";
|
|
5
|
-
export { default as Upload } from "./Upload";
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState, useRef, Key } from "react";
|
|
4
|
-
import { AnimatePresence, motion } from "framer-motion";
|
|
5
|
-
import type { UploadProps } from "../types";
|
|
6
|
-
import { formatFileSize } from "../../file-thumbnail/utils";
|
|
7
|
-
|
|
8
|
-
export interface FileItem extends File {
|
|
9
|
-
id?: number | string;
|
|
10
|
-
preview?: string;
|
|
11
|
-
url?: string;
|
|
12
|
-
is_private?: boolean;
|
|
13
|
-
[key: string]: unknown;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default function MultiFilePreview({
|
|
17
|
-
thumbnail,
|
|
18
|
-
files,
|
|
19
|
-
onRemove,
|
|
20
|
-
className = "",
|
|
21
|
-
isClickable,
|
|
22
|
-
isEditable,
|
|
23
|
-
onDeleteButtonClick,
|
|
24
|
-
onPrivacyUpdateClick,
|
|
25
|
-
}: UploadProps): React.JSX.Element | null {
|
|
26
|
-
const [menuAnchor, setMenuAnchor] = useState<null | HTMLElement>(null);
|
|
27
|
-
const [selectedFile, setSelectedFile] = useState<FileItem | null>(null);
|
|
28
|
-
const menuRef = useRef<HTMLDivElement>(null);
|
|
29
|
-
const canViewPrivate = true;
|
|
30
|
-
|
|
31
|
-
if (files?.length == null) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const handleMenuClose = () => {
|
|
36
|
-
setMenuAnchor(null);
|
|
37
|
-
setSelectedFile(null);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const getFileThumbnail = (file: FileItem) => {
|
|
41
|
-
const typedFile = file as FileItem;
|
|
42
|
-
const imgUrl = typedFile.preview;
|
|
43
|
-
|
|
44
|
-
// Check if it's an image (following SingleFilePreview logic)
|
|
45
|
-
const isImage =
|
|
46
|
-
typedFile.type?.startsWith("image/") ||
|
|
47
|
-
/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(typedFile.name || "");
|
|
48
|
-
|
|
49
|
-
if (imgUrl && isImage) {
|
|
50
|
-
if (typedFile.is_private && !canViewPrivate) {
|
|
51
|
-
return (
|
|
52
|
-
<svg
|
|
53
|
-
className="w-full h-full text-gray-400"
|
|
54
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
55
|
-
viewBox="0 0 20 20"
|
|
56
|
-
fill="currentColor"
|
|
57
|
-
>
|
|
58
|
-
<path
|
|
59
|
-
fillRule="evenodd"
|
|
60
|
-
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
|
|
61
|
-
clipRule="evenodd"
|
|
62
|
-
/>
|
|
63
|
-
</svg>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
return (
|
|
67
|
-
<img
|
|
68
|
-
src={imgUrl}
|
|
69
|
-
alt={typedFile.name || ""}
|
|
70
|
-
className="w-full h-full object-cover"
|
|
71
|
-
/>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Non-image files - use SingleFilePreview style icons
|
|
76
|
-
return (
|
|
77
|
-
<div className="w-full h-full flex flex-col items-center justify-center bg-gray-50 p-2 text-center">
|
|
78
|
-
<svg
|
|
79
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
80
|
-
className="h-8 w-8 text-gray-400 mb-1 flex-shrink-0"
|
|
81
|
-
fill="none"
|
|
82
|
-
viewBox="0 0 24 24"
|
|
83
|
-
stroke="currentColor"
|
|
84
|
-
strokeWidth={2}
|
|
85
|
-
>
|
|
86
|
-
<path
|
|
87
|
-
strokeLinecap="round"
|
|
88
|
-
strokeLinejoin="round"
|
|
89
|
-
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
90
|
-
/>
|
|
91
|
-
</svg>
|
|
92
|
-
<p className="text-xs font-medium text-gray-900 truncate max-w-[80px]">
|
|
93
|
-
{typedFile.name}
|
|
94
|
-
</p>
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div className={`space-y-2 ${className}`}>
|
|
101
|
-
<AnimatePresence>
|
|
102
|
-
{files.map((file) => {
|
|
103
|
-
const typedFile = file as FileItem;
|
|
104
|
-
const { name = "", size = 0 } = typedFile;
|
|
105
|
-
const isStringFile = typeof file === "string";
|
|
106
|
-
const itemKey = (typedFile.id ||
|
|
107
|
-
typedFile.name ||
|
|
108
|
-
Math.random().toString(36).substr(2, 9)) as Key;
|
|
109
|
-
|
|
110
|
-
if (thumbnail) {
|
|
111
|
-
return (
|
|
112
|
-
<motion.div
|
|
113
|
-
key={itemKey}
|
|
114
|
-
initial={{ opacity: 0, y: 20 }}
|
|
115
|
-
animate={{ opacity: 1, y: 0 }}
|
|
116
|
-
exit={{ opacity: 0, scale: 0.95 }}
|
|
117
|
-
className="relative inline-block m-1 w-20 h-20 rounded-lg overflow-hidden border border-gray-200 bg-gray-50"
|
|
118
|
-
>
|
|
119
|
-
<div className="w-full h-full flex items-center justify-center">
|
|
120
|
-
{getFileThumbnail(typedFile)}
|
|
121
|
-
</div>
|
|
122
|
-
{onRemove && (
|
|
123
|
-
<button
|
|
124
|
-
type="button"
|
|
125
|
-
onClick={(e) => {
|
|
126
|
-
e.stopPropagation();
|
|
127
|
-
onRemove(typedFile);
|
|
128
|
-
}}
|
|
129
|
-
className="absolute top-1 right-1 p-1 rounded-full bg-black/50 text-white hover:bg-black/70 transition-colors"
|
|
130
|
-
>
|
|
131
|
-
<svg
|
|
132
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
133
|
-
className="h-3 w-3"
|
|
134
|
-
viewBox="0 0 20 20"
|
|
135
|
-
fill="currentColor"
|
|
136
|
-
>
|
|
137
|
-
<path
|
|
138
|
-
fillRule="evenodd"
|
|
139
|
-
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
|
140
|
-
clipRule="evenodd"
|
|
141
|
-
/>
|
|
142
|
-
</svg>
|
|
143
|
-
</button>
|
|
144
|
-
)}
|
|
145
|
-
</motion.div>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<motion.div
|
|
151
|
-
key={itemKey}
|
|
152
|
-
initial={{ opacity: 0, y: 20 }}
|
|
153
|
-
animate={{ opacity: 1, y: 0 }}
|
|
154
|
-
exit={{ opacity: 0, scale: 0.95 }}
|
|
155
|
-
className="flex items-center p-3 rounded-lg border border-gray-200 hover:bg-gray-50 transition-colors"
|
|
156
|
-
>
|
|
157
|
-
{/* List view - SingleFilePreview inspired thumbnail */}
|
|
158
|
-
<div className="w-14 h-14 flex-shrink-0 mr-3 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 flex items-center justify-center">
|
|
159
|
-
{getFileThumbnail(typedFile)}
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<div
|
|
163
|
-
className="flex-grow min-w-0"
|
|
164
|
-
onClick={() => {
|
|
165
|
-
if (isClickable && typedFile.url) {
|
|
166
|
-
window.open(typedFile.url, "_blank");
|
|
167
|
-
}
|
|
168
|
-
}}
|
|
169
|
-
>
|
|
170
|
-
<p className="text-sm font-medium text-gray-900 truncate">
|
|
171
|
-
{isStringFile ? file : name}
|
|
172
|
-
</p>
|
|
173
|
-
{!isStringFile && (
|
|
174
|
-
<p className="text-xs text-gray-500">
|
|
175
|
-
{formatFileSize(size)}
|
|
176
|
-
</p>
|
|
177
|
-
)}
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
<div className="flex items-center space-x-1">
|
|
181
|
-
{onRemove && (
|
|
182
|
-
<button
|
|
183
|
-
type="button"
|
|
184
|
-
onClick={(e) => {
|
|
185
|
-
e.stopPropagation();
|
|
186
|
-
onRemove(typedFile);
|
|
187
|
-
}}
|
|
188
|
-
className="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded transition-all"
|
|
189
|
-
>
|
|
190
|
-
<svg
|
|
191
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
192
|
-
className="h-4 w-4"
|
|
193
|
-
viewBox="0 0 20 20"
|
|
194
|
-
fill="currentColor"
|
|
195
|
-
>
|
|
196
|
-
<path
|
|
197
|
-
fillRule="evenodd"
|
|
198
|
-
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
|
199
|
-
clipRule="evenodd"
|
|
200
|
-
/>
|
|
201
|
-
</svg>
|
|
202
|
-
</button>
|
|
203
|
-
)}
|
|
204
|
-
|
|
205
|
-
{isEditable && (
|
|
206
|
-
<div className="relative" ref={menuRef}>
|
|
207
|
-
<button
|
|
208
|
-
type="button"
|
|
209
|
-
onClick={(e) => {
|
|
210
|
-
e.stopPropagation();
|
|
211
|
-
setMenuAnchor(menuRef.current);
|
|
212
|
-
setSelectedFile(typedFile);
|
|
213
|
-
}}
|
|
214
|
-
className="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded transition-all"
|
|
215
|
-
>
|
|
216
|
-
<svg
|
|
217
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
218
|
-
className="h-4 w-4"
|
|
219
|
-
viewBox="0 0 20 20"
|
|
220
|
-
fill="currentColor"
|
|
221
|
-
>
|
|
222
|
-
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
|
|
223
|
-
</svg>
|
|
224
|
-
</button>
|
|
225
|
-
|
|
226
|
-
{menuAnchor && selectedFile === typedFile && (
|
|
227
|
-
<div className="absolute right-0 z-20 mt-2 w-48 bg-white rounded-lg shadow-xl border ring-1 ring-black ring-opacity-5 py-1 origin-top-right">
|
|
228
|
-
<button
|
|
229
|
-
onClick={(e) => {
|
|
230
|
-
e.stopPropagation();
|
|
231
|
-
if (onDeleteButtonClick) {
|
|
232
|
-
onDeleteButtonClick(typedFile);
|
|
233
|
-
}
|
|
234
|
-
handleMenuClose();
|
|
235
|
-
}}
|
|
236
|
-
className="flex w-full items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
|
|
237
|
-
>
|
|
238
|
-
<svg
|
|
239
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
240
|
-
className="mr-3 h-4 w-4"
|
|
241
|
-
fill="none"
|
|
242
|
-
viewBox="0 0 24 24"
|
|
243
|
-
stroke="currentColor"
|
|
244
|
-
>
|
|
245
|
-
<path
|
|
246
|
-
strokeLinecap="round"
|
|
247
|
-
strokeLinejoin="round"
|
|
248
|
-
strokeWidth={2}
|
|
249
|
-
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
250
|
-
/>
|
|
251
|
-
</svg>
|
|
252
|
-
Delete
|
|
253
|
-
</button>
|
|
254
|
-
<button
|
|
255
|
-
onClick={(e) => {
|
|
256
|
-
e.stopPropagation();
|
|
257
|
-
if (onPrivacyUpdateClick) {
|
|
258
|
-
onPrivacyUpdateClick(typedFile);
|
|
259
|
-
}
|
|
260
|
-
handleMenuClose();
|
|
261
|
-
}}
|
|
262
|
-
className="flex w-full items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
|
|
263
|
-
>
|
|
264
|
-
<svg
|
|
265
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
266
|
-
className="mr-3 h-4 w-4"
|
|
267
|
-
fill="none"
|
|
268
|
-
viewBox="0 0 24 24"
|
|
269
|
-
stroke="currentColor"
|
|
270
|
-
>
|
|
271
|
-
<path
|
|
272
|
-
strokeLinecap="round"
|
|
273
|
-
strokeLinejoin="round"
|
|
274
|
-
strokeWidth={2}
|
|
275
|
-
d={
|
|
276
|
-
typedFile?.is_private
|
|
277
|
-
? "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
278
|
-
: "M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"
|
|
279
|
-
}
|
|
280
|
-
/>
|
|
281
|
-
</svg>
|
|
282
|
-
{typedFile?.is_private
|
|
283
|
-
? "Make Public"
|
|
284
|
-
: "Make Private"}
|
|
285
|
-
</button>
|
|
286
|
-
</div>
|
|
287
|
-
)}
|
|
288
|
-
</div>
|
|
289
|
-
)}
|
|
290
|
-
</div>
|
|
291
|
-
</motion.div>
|
|
292
|
-
);
|
|
293
|
-
})}
|
|
294
|
-
</AnimatePresence>
|
|
295
|
-
</div>
|
|
296
|
-
);
|
|
297
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { formatFileSize } from "../../file-thumbnail/utils";
|
|
4
|
-
import type { CustomFile } from "../types";
|
|
5
|
-
|
|
6
|
-
// ----------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
type Props = {
|
|
9
|
-
file: CustomFile | string | null;
|
|
10
|
-
className?: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export default function SingleFilePreview({
|
|
14
|
-
file,
|
|
15
|
-
className = "",
|
|
16
|
-
}: Props): React.JSX.Element | null {
|
|
17
|
-
if (!file) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const imgUrl = typeof file === "string" ? file : file.preview;
|
|
22
|
-
const isImage =
|
|
23
|
-
typeof file === "string"
|
|
24
|
-
? /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file)
|
|
25
|
-
: file.type?.startsWith("image/");
|
|
26
|
-
|
|
27
|
-
const fileName =
|
|
28
|
-
typeof file === "string" ? file.split("/").pop() : file.name || "file";
|
|
29
|
-
|
|
30
|
-
const fileSize =
|
|
31
|
-
typeof file === "string"
|
|
32
|
-
? null
|
|
33
|
-
: file.size
|
|
34
|
-
? formatFileSize(file.size)
|
|
35
|
-
: null;
|
|
36
|
-
|
|
37
|
-
if (isImage && imgUrl) {
|
|
38
|
-
return (
|
|
39
|
-
<div
|
|
40
|
-
className={`relative w-full h-full flex items-center justify-center ${className}`}
|
|
41
|
-
>
|
|
42
|
-
<div className="relative max-w-md w-full p-2">
|
|
43
|
-
<div className="aspect-square w-full flex items-center justify-center bg-gray-50 rounded-lg border border-gray-200 overflow-hidden">
|
|
44
|
-
<img
|
|
45
|
-
src={imgUrl}
|
|
46
|
-
alt={fileName}
|
|
47
|
-
className="max-w-full max-h-[200px] w-auto h-auto object-contain"
|
|
48
|
-
style={{
|
|
49
|
-
backgroundColor: "var(--color-bg-paper)",
|
|
50
|
-
}}
|
|
51
|
-
/>
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// For non-image files, show a file icon with name
|
|
59
|
-
return (
|
|
60
|
-
<div
|
|
61
|
-
className={`w-full h-full p-4 flex flex-col items-center justify-center text-center ${className}`}
|
|
62
|
-
>
|
|
63
|
-
<svg
|
|
64
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
-
className="h-10 w-10 text-gray-400 mb-2"
|
|
66
|
-
fill="none"
|
|
67
|
-
viewBox="0 0 24 24"
|
|
68
|
-
stroke="currentColor"
|
|
69
|
-
>
|
|
70
|
-
<path
|
|
71
|
-
strokeLinecap="round"
|
|
72
|
-
strokeLinejoin="round"
|
|
73
|
-
strokeWidth={2}
|
|
74
|
-
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
75
|
-
/>
|
|
76
|
-
</svg>
|
|
77
|
-
<p className="text-sm font-medium truncate w-full">{fileName}</p>
|
|
78
|
-
{fileSize && <p className="text-xs text-gray-500">{fileSize}</p>}
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
}
|
package/src/upload/types.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { DropzoneOptions, FileRejection, DropEvent } from "react-dropzone";
|
|
2
|
-
import type { FileItem } from "./preview/MultiFilePreview";
|
|
3
|
-
|
|
4
|
-
// ----------------------------------------------------------------------
|
|
5
|
-
|
|
6
|
-
export interface CustomFile extends File {
|
|
7
|
-
path?: string;
|
|
8
|
-
preview?: string;
|
|
9
|
-
lastModifiedDate?: Date;
|
|
10
|
-
url?: string;
|
|
11
|
-
name: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface UploadProps extends Omit<DropzoneOptions, "onDrop"> {
|
|
15
|
-
error?: boolean;
|
|
16
|
-
className?: string;
|
|
17
|
-
style?: React.CSSProperties;
|
|
18
|
-
thumbnail?: boolean;
|
|
19
|
-
isClickable?: boolean;
|
|
20
|
-
placeholder?: React.ReactNode;
|
|
21
|
-
helperText?: React.ReactNode;
|
|
22
|
-
disableMultiple?: boolean;
|
|
23
|
-
isEditable?: boolean;
|
|
24
|
-
onDeleteButtonClick?: (file: FileItem) => void;
|
|
25
|
-
onPrivacyUpdateClick?: (file: FileItem) => void;
|
|
26
|
-
// File handling
|
|
27
|
-
file?: CustomFile | string | File | null;
|
|
28
|
-
onDelete?: () => void;
|
|
29
|
-
// Multiple files
|
|
30
|
-
files?: (File | string | { name: string; url: string; preview?: string })[];
|
|
31
|
-
onUpload?: () => void;
|
|
32
|
-
onRemove?: (file: CustomFile | string) => void;
|
|
33
|
-
onRemoveAll?: () => void;
|
|
34
|
-
// Drop handler - using FileRejection from react-dropzone
|
|
35
|
-
onDrop?: <T extends File>(
|
|
36
|
-
acceptedFiles: T[],
|
|
37
|
-
fileRejections: FileRejection[],
|
|
38
|
-
event: DropEvent
|
|
39
|
-
) => void;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Helper type for file with preview
|
|
43
|
-
export interface FileWithPreview extends File {
|
|
44
|
-
preview?: string;
|
|
45
|
-
url?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Type guard for file with preview
|
|
49
|
-
export const isFileWithPreview = (file: unknown): file is FileWithPreview => {
|
|
50
|
-
return file instanceof File && "preview" in file;
|
|
51
|
-
};
|