From 741d3bf33067164ae7b5bb86de48a5a9d0912ece Mon Sep 17 00:00:00 2001 From: Enchu Date: Sun, 2 Nov 2025 10:22:38 +0300 Subject: [PATCH 1/5] fix(50): adding an error --- packages/pkg.fileuploader/FileUploader.tsx | 8 ++++++++ packages/pkg.fileuploader/types.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/packages/pkg.fileuploader/FileUploader.tsx b/packages/pkg.fileuploader/FileUploader.tsx index 51f275b2..71765b27 100644 --- a/packages/pkg.fileuploader/FileUploader.tsx +++ b/packages/pkg.fileuploader/FileUploader.tsx @@ -64,6 +64,7 @@ export const FileUploader = ({ disabled, isWarning, onChange, + isError, limit = 3, bytesSizeLimit = DEFAULT_SIZE_LIMIT, children, @@ -91,10 +92,17 @@ export const FileUploader = ({ const handleFilesChange = (files?: FileList | null) => { setError(''); + isError = undefined; if (!files || files.length == 0) return; const fileList = [...files]; if (fileList.length > limit || !validateSize(fileList, bytesSizeLimit)) { + if (isError) { + isError = `Можно отправить не более ${limit} ${plural( + pluralFiles, + limit, + )} общим объёмом до ${formatedSizeLimit}`; + } return setError( `Можно отправить не более ${limit} ${plural( pluralFiles, diff --git a/packages/pkg.fileuploader/types.ts b/packages/pkg.fileuploader/types.ts index 61ac448c..245ed25c 100644 --- a/packages/pkg.fileuploader/types.ts +++ b/packages/pkg.fileuploader/types.ts @@ -11,6 +11,7 @@ export type FileUploaderProps = { withLargeError?: boolean; size?: SizeType; limit?: number; + isError?: string; isWarning?: boolean; descriptionText?: string; onChange: (files: File[]) => void; From a1d5d0e38e889f60a0464e5694296d86bcc1652f Mon Sep 17 00:00:00 2001 From: Enchu Date: Sun, 2 Nov 2025 10:27:25 +0300 Subject: [PATCH 2/5] feat(50): version increase --- packages/pkg.fileuploader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pkg.fileuploader/package.json b/packages/pkg.fileuploader/package.json index 16a1c167..51014659 100644 --- a/packages/pkg.fileuploader/package.json +++ b/packages/pkg.fileuploader/package.json @@ -1,6 +1,6 @@ { "name": "@xipkg/fileuploader", - "version": "2.0.12", + "version": "2.0.13", "main": "./dist/index.mjs", "module": "./dist/index.mjs", "types": "./dist/index.d.mts", From ee8f2803b8f15d272aa4577badba10312c50d61a Mon Sep 17 00:00:00 2001 From: Enchu Date: Wed, 5 Nov 2025 19:19:28 +0300 Subject: [PATCH 3/5] fex(50): adding error handling --- packages/pkg.fileuploader/FileUploader.tsx | 79 ++++++++++++++++++---- packages/pkg.fileuploader/types.ts | 2 +- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/packages/pkg.fileuploader/FileUploader.tsx b/packages/pkg.fileuploader/FileUploader.tsx index 71765b27..1034c0ee 100644 --- a/packages/pkg.fileuploader/FileUploader.tsx +++ b/packages/pkg.fileuploader/FileUploader.tsx @@ -56,6 +56,13 @@ const DEFAULT_SIZE_LIMIT = 6 * 1024 * 1024; // 6 MB const pluralFiles = ['файла', 'файлов', 'файлов']; +const readFile = (file: File) => + new Promise((resolve) => { + const reader = new FileReader(); + reader.addEventListener('load', () => resolve(reader.result), false); + reader.readAsDataURL(file); + }); + export const FileUploader = ({ withError = true, withLargeError = true, @@ -64,7 +71,7 @@ export const FileUploader = ({ disabled, isWarning, onChange, - isError, + isError = () => {}, limit = 3, bytesSizeLimit = DEFAULT_SIZE_LIMIT, children, @@ -88,27 +95,67 @@ export const FileUploader = ({ stopDefaultEvents(e); setIsDragOver(false); handleFilesChange(e.dataTransfer.files); + handleFileChange(e.dataTransfer.files[0]); + }; + + const handleFileChange = async (file: File) => { + setError(''); + isError(undefined); + + if (!file) return; + + if (file.size === 0) { + const errorMessage = 'Файл пустой. Пожалуйста, выберите другой файл'; + setError(errorMessage); + isError(errorMessage); + return; + } + + if (file.size > bytesSizeLimit) { + const errorMessage = `Фото слишком большое. Выберите фото до 1 Мб`; + setError(errorMessage); + isError(errorMessage); + return; + } + + if (!file.type.startsWith('image/')) { + const errorMessage = 'Пожалуйста, загрузите изображение'; + setError(errorMessage); + isError(errorMessage); + return; + } + + const allowedTypes = ['image/png', 'image/jpeg']; + if (!allowedTypes.includes(file.type)) { + const errorMessage = 'Этот формат не поддерживается. Загрузите фото в PNG или JPEG'; + setError(errorMessage); + isError(errorMessage); + return; + } + + if (validateBeforeUpload) { + const validationError = validateBeforeUpload([file]); + if (validationError) { + setError(validationError); + isError(validationError); + return; + } + } + + return await readFile(file); }; const handleFilesChange = (files?: FileList | null) => { setError(''); - isError = undefined; if (!files || files.length == 0) return; const fileList = [...files]; if (fileList.length > limit || !validateSize(fileList, bytesSizeLimit)) { - if (isError) { - isError = `Можно отправить не более ${limit} ${plural( - pluralFiles, - limit, - )} общим объёмом до ${formatedSizeLimit}`; - } - return setError( - `Можно отправить не более ${limit} ${plural( - pluralFiles, - limit, - )} общим объёмом до ${formatedSizeLimit}`, - ); + const errorMessage = `Можно отправить не более ${limit} ${plural( + pluralFiles, + limit, + )} общим объёмом до ${formatedSizeLimit}`; + return setError(errorMessage); } if (validateBeforeUpload && validateBeforeUpload(fileList)) { @@ -120,6 +167,10 @@ export const FileUploader = ({ }; const handleChange = (e: ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + handleFileChange(e.target.files[0]); + } + handleFilesChange(e.target.files); }; diff --git a/packages/pkg.fileuploader/types.ts b/packages/pkg.fileuploader/types.ts index 245ed25c..82c55086 100644 --- a/packages/pkg.fileuploader/types.ts +++ b/packages/pkg.fileuploader/types.ts @@ -11,7 +11,7 @@ export type FileUploaderProps = { withLargeError?: boolean; size?: SizeType; limit?: number; - isError?: string; + isError?: (error: string | undefined) => void; isWarning?: boolean; descriptionText?: string; onChange: (files: File[]) => void; From faf4f3da4b90f29c087472013cf5a7b251aa0508 Mon Sep 17 00:00:00 2001 From: Enchu Date: Tue, 11 Nov 2025 10:20:36 +0300 Subject: [PATCH 4/5] fix(50): change types, change errors --- packages/pkg.fileuploader/FileUploader.tsx | 149 ++++++++++----------- packages/pkg.fileuploader/types.ts | 6 +- 2 files changed, 74 insertions(+), 81 deletions(-) diff --git a/packages/pkg.fileuploader/FileUploader.tsx b/packages/pkg.fileuploader/FileUploader.tsx index 1034c0ee..505c610e 100644 --- a/packages/pkg.fileuploader/FileUploader.tsx +++ b/packages/pkg.fileuploader/FileUploader.tsx @@ -20,9 +20,6 @@ const containerStyles = cva( isDragOver: { true: 'shadow-[0px_0px_0px_4px_var(--xi-brand-80)] outline-offset-4 outline-4 outline-brand-20 outline border-transparent dark:shadow-[0px_0px_0px_4px_var(--xi-brand-60)] dark:outline-brand-40', }, - isError: { - true: 'ring-2 ring-red-60 dark:ring-red-40 !border-transparent', - }, isWarning: { true: 'ring-2 ring-orange-80 dark:ring-orange-40 !border-transparent', }, @@ -56,32 +53,23 @@ const DEFAULT_SIZE_LIMIT = 6 * 1024 * 1024; // 6 MB const pluralFiles = ['файла', 'файлов', 'файлов']; -const readFile = (file: File) => - new Promise((resolve) => { - const reader = new FileReader(); - reader.addEventListener('load', () => resolve(reader.result), false); - reader.readAsDataURL(file); - }); - export const FileUploader = ({ - withError = true, - withLargeError = true, size = 'medium', descriptionText, disabled, isWarning, onChange, - isError = () => {}, + onError, + enableErrorHandling = true, limit = 3, bytesSizeLimit = DEFAULT_SIZE_LIMIT, children, - multiple, validateBeforeUpload, fileTypesHint, + acceptedFileTypes, ...inputProps }: FileUploaderProps & DefaultInputPropsT) => { const [isDragOver, setIsDragOver] = useState(false); - const [error, setError] = useState(''); const inputRef = useRef(null); const dragDeph = useRef(0); @@ -91,75 +79,88 @@ export const FileUploader = ({ const isLarge = size === 'large'; const formatedSizeLimit = formatBytesSize(bytesSizeLimit); - const handleDrop = (e: DragEvent) => { - stopDefaultEvents(e); - setIsDragOver(false); - handleFilesChange(e.dataTransfer.files); - handleFileChange(e.dataTransfer.files[0]); - }; - - const handleFileChange = async (file: File) => { - setError(''); - isError(undefined); - - if (!file) return; - - if (file.size === 0) { - const errorMessage = 'Файл пустой. Пожалуйста, выберите другой файл'; - setError(errorMessage); - isError(errorMessage); - return; - } - - if (file.size > bytesSizeLimit) { - const errorMessage = `Фото слишком большое. Выберите фото до 1 Мб`; - setError(errorMessage); - isError(errorMessage); - return; + const handleError = (fileList: File[]): boolean => { + if (!enableErrorHandling || !onError) { + return false; } - if (!file.type.startsWith('image/')) { - const errorMessage = 'Пожалуйста, загрузите изображение'; - setError(errorMessage); - isError(errorMessage); - return; + if (fileList.length > limit) { + onError(`Можно отправить не более ${limit} ${plural( + pluralFiles, + limit, + )} общим объёмом до ${formatedSizeLimit}`); + return true; } - const allowedTypes = ['image/png', 'image/jpeg']; - if (!allowedTypes.includes(file.type)) { - const errorMessage = 'Этот формат не поддерживается. Загрузите фото в PNG или JPEG'; - setError(errorMessage); - isError(errorMessage); - return; + if (acceptedFileTypes && acceptedFileTypes.length > 0) { + const invalidTypeFile = fileList.find((file) => { + const fileType = file.type.toLowerCase(); + const fileName = file.name.toLowerCase(); + + return !acceptedFileTypes.some((acceptedType) => { + const normalizedAcceptedType = acceptedType.toLowerCase().trim(); + + if (fileType === normalizedAcceptedType) { + return true; + } + + if (normalizedAcceptedType.endsWith('/') && fileType.startsWith(normalizedAcceptedType)) { + return true; + } + + if (normalizedAcceptedType.startsWith('.')) { + return fileName.endsWith(normalizedAcceptedType); + } + + if (fileName.endsWith(`.${normalizedAcceptedType}`)) { + return true; + } + + return false; + }); + }); + + if (invalidTypeFile) { + const displayTypes = acceptedFileTypes + .map((type) => { + if (type.startsWith('.')) { + return type.toUpperCase(); + } + if (type.includes('/')) { + return type.split('/')[1]?.toUpperCase() || type.toUpperCase(); + } + return type.toUpperCase(); + }) + .join(', '); + + onError(`Неподдерживаемый формат файла. Разрешены: ${displayTypes}`); + return true; + } } if (validateBeforeUpload) { - const validationError = validateBeforeUpload([file]); + const validationError = validateBeforeUpload(fileList); if (validationError) { - setError(validationError); - isError(validationError); - return; + onError(validationError); + return true; } } - return await readFile(file); + return false; + }; + + const handleDrop = (e: DragEvent) => { + stopDefaultEvents(e); + setIsDragOver(false); + handleFilesChange(e.dataTransfer.files); }; const handleFilesChange = (files?: FileList | null) => { - setError(''); if (!files || files.length == 0) return; const fileList = [...files]; - if (fileList.length > limit || !validateSize(fileList, bytesSizeLimit)) { - const errorMessage = `Можно отправить не более ${limit} ${plural( - pluralFiles, - limit, - )} общим объёмом до ${formatedSizeLimit}`; - return setError(errorMessage); - } - - if (validateBeforeUpload && validateBeforeUpload(fileList)) { - return setError(validateBeforeUpload(fileList)); + if (handleError(fileList)) { + return; } onChange(fileList); @@ -167,19 +168,15 @@ export const FileUploader = ({ }; const handleChange = (e: ChangeEvent) => { - if (e.target.files && e.target.files[0]) { - handleFileChange(e.target.files[0]); - } - handleFilesChange(e.target.files); }; - const handleDragEnter = (e: React.DragEvent) => { + const handleDragEnter = (_e: React.DragEvent) => { dragDeph.current++; setIsDragOver(true); }; - const handleDragLeave = (e: React.DragEvent) => { + const handleDragLeave = (_e: React.DragEvent) => { dragDeph.current--; if (dragDeph.current === 0) { setIsDragOver(false); @@ -211,7 +208,6 @@ export const FileUploader = ({ className={containerStyles({ isDisabled: disabled, isDragOver, - isError: !!error, isWarning, size, })} @@ -234,7 +230,7 @@ export const FileUploader = ({ )}

- {isLarge && fileTypesHint && withLargeError && ( + {isLarge && fileTypesHint && (

{descriptionText || `${fileTypesHint.map((el) => el.toUpperCase()).join(', ')} до ${formatedSizeLimit}`} @@ -245,9 +241,6 @@ export const FileUploader = ({ )} - {error && withError && ( -

{error}

- )} ); }; diff --git a/packages/pkg.fileuploader/types.ts b/packages/pkg.fileuploader/types.ts index 82c55086..b801c7cb 100644 --- a/packages/pkg.fileuploader/types.ts +++ b/packages/pkg.fileuploader/types.ts @@ -7,11 +7,10 @@ export type DefaultInputPropsT = Omit< >; export type FileUploaderProps = { - withError?: boolean; - withLargeError?: boolean; size?: SizeType; limit?: number; - isError?: (error: string | undefined) => void; + onError?: (error: string) => void; + enableErrorHandling?: boolean; isWarning?: boolean; descriptionText?: string; onChange: (files: File[]) => void; @@ -19,6 +18,7 @@ export type FileUploaderProps = { bytesSizeLimit?: number; children?: React.ReactNode; fileTypesHint?: string[]; + acceptedFileTypes?: string[]; }; export type FileProps = { From 3c2f3e2720d1484108f331f09ee5b083d62c826f Mon Sep 17 00:00:00 2001 From: Enchu Date: Sun, 16 Nov 2025 10:23:57 +0300 Subject: [PATCH 5/5] feat(50): update package --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 82dbeee1..02163d10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22994,7 +22994,7 @@ }, "packages/pkg.fileuploader": { "name": "@xipkg/fileuploader", - "version": "2.0.12", + "version": "2.0.13", "license": "MIT", "dependencies": { "@xipkg/icons": "^2.5.2",