@sqrzro/ui 4.0.0-alpha.69 → 4.0.0-alpha.70
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/dist/forms/components/ImageInput/index.d.ts +3 -1
- package/dist/forms/components/ImageInput/index.js +3 -3
- package/dist/forms/components/ImageInputPanel/index.d.ts +2 -1
- package/dist/forms/components/ImageInputPanel/index.js +20 -3
- package/dist/forms/utility/preprocess-image.d.ts +5 -1
- package/dist/forms/utility/preprocess-image.js +16 -8
- package/package.json +1 -1
|
@@ -8,8 +8,10 @@ export interface ImageInputClassNames {
|
|
|
8
8
|
title: string;
|
|
9
9
|
}
|
|
10
10
|
export interface ImageInputComponentProps {
|
|
11
|
+
maxSize?: [number?, number?];
|
|
12
|
+
minSize?: [number?, number?];
|
|
11
13
|
onUpload?: (file: File) => FormResponse<string>;
|
|
12
14
|
}
|
|
13
15
|
export type ImageInputProps = ClassNameProps<ImageInputClassNames> & InputProps<string | null> & ImageInputComponentProps;
|
|
14
|
-
declare function ImageInput({ classNames, classNameProps, id, isDisabled, name, onChange, onUpload, value, }: Readonly<ImageInputProps>): React.ReactElement;
|
|
16
|
+
declare function ImageInput({ classNames, classNameProps, id, isDisabled, maxSize, minSize, name, onChange, onUpload, value, }: Readonly<ImageInputProps>): React.ReactElement;
|
|
15
17
|
export default ImageInput;
|
|
@@ -5,7 +5,7 @@ import preprocessImage from '../../../forms/utility/preprocess-image';
|
|
|
5
5
|
import { useClassNames } from '../../../styles/context';
|
|
6
6
|
import tw from '../../../styles/classnames/utility/tw';
|
|
7
7
|
import ImageInputPanel from '../ImageInputPanel';
|
|
8
|
-
function ImageInput({ classNames, classNameProps, id, isDisabled, name, onChange, onUpload, value, }) {
|
|
8
|
+
function ImageInput({ classNames, classNameProps, id, isDisabled, maxSize, minSize, name, onChange, onUpload, value, }) {
|
|
9
9
|
const [error, setError] = useState(null);
|
|
10
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
11
11
|
const componentClassNames = useClassNames('imageInput', {
|
|
@@ -23,7 +23,7 @@ function ImageInput({ classNames, classNameProps, id, isDisabled, name, onChange
|
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
const preprocessed = await preprocessImage(imageFiles[0]);
|
|
26
|
+
const preprocessed = await preprocessImage(imageFiles[0], { maxSize, minSize });
|
|
27
27
|
const fnReturn = await onUpload(preprocessed);
|
|
28
28
|
setIsLoading(false);
|
|
29
29
|
if (fnReturn.error) {
|
|
@@ -41,6 +41,6 @@ function ImageInput({ classNames, classNameProps, id, isDisabled, name, onChange
|
|
|
41
41
|
setError(err instanceof Error ? err.message : 'An unexpected error occurred');
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
return (_jsxs("div", { className: tw('relative flex items-center justify-center overflow-hidden text-center', componentClassNames?.root, isDisabled || isLoading ? 'pointer-events-none opacity-20' : null), children: [_jsx("input", { name: name, type: "hidden", value: value || '' }), _jsx("input", { accept: "image/*", className: "absolute left-0 top-0 h-full w-full cursor-pointer opacity-0", disabled: isDisabled || isLoading, id: id || name, name: name, onChange: handleChange, type: "file" }), _jsx(ImageInputPanel, { classNames: classNames, classNameProps: { ...classNameProps, isError: Boolean(error) }, error: error, isLoading: isLoading, value: value })] }));
|
|
44
|
+
return (_jsxs("div", { className: tw('relative flex items-center justify-center overflow-hidden text-center', componentClassNames?.root, isDisabled || isLoading ? 'pointer-events-none opacity-20' : null), children: [_jsx("input", { name: name, type: "hidden", value: value || '' }), _jsx("input", { accept: "image/*", className: "absolute left-0 top-0 h-full w-full cursor-pointer opacity-0", disabled: isDisabled || isLoading, id: id || name, name: name, onChange: handleChange, type: "file" }), _jsx(ImageInputPanel, { classNames: classNames, classNameProps: { ...classNameProps, isError: Boolean(error) }, error: error, isLoading: isLoading, minSize: minSize, value: value })] }));
|
|
45
45
|
}
|
|
46
46
|
export default ImageInput;
|
|
@@ -3,7 +3,8 @@ import type { ImageInputClassNames } from '../ImageInput';
|
|
|
3
3
|
interface ImageInputPanelProps extends ClassNameProps<ImageInputClassNames> {
|
|
4
4
|
error?: string | null;
|
|
5
5
|
isLoading?: boolean;
|
|
6
|
+
minSize?: [number?, number?];
|
|
6
7
|
value?: string | null;
|
|
7
8
|
}
|
|
8
|
-
declare function ImageInputPanel({ classNameProps, classNames, error, isLoading, value, }: ImageInputPanelProps): React.ReactElement;
|
|
9
|
+
declare function ImageInputPanel({ classNameProps, classNames, error, isLoading, minSize, value, }: ImageInputPanelProps): React.ReactElement;
|
|
9
10
|
export default ImageInputPanel;
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useClassNames, useIcon } from '../../../styles/context';
|
|
3
|
-
function
|
|
3
|
+
function renderMinSize(minSize) {
|
|
4
|
+
if (!minSize) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const [width, height] = minSize;
|
|
8
|
+
if (width && height) {
|
|
9
|
+
return `(Minimum size: ${width} x ${height}px)`;
|
|
10
|
+
}
|
|
11
|
+
if (width) {
|
|
12
|
+
return `(Minimum width: ${width}px)`;
|
|
13
|
+
}
|
|
14
|
+
if (height) {
|
|
15
|
+
return `(Minimum height: ${height}px)`;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function ImageInputPanel({ classNameProps, classNames, error, isLoading, minSize, value, }) {
|
|
4
20
|
const ErrorIcon = useIcon('imageInput.error');
|
|
5
21
|
const UploadIcon = useIcon('imageInput.upload');
|
|
6
22
|
const componentClassNames = useClassNames('imageInput', {
|
|
@@ -9,9 +25,10 @@ function ImageInputPanel({ classNameProps, classNames, error, isLoading, value,
|
|
|
9
25
|
if (value) {
|
|
10
26
|
return _jsx("img", { src: value, className: componentClassNames?.image });
|
|
11
27
|
}
|
|
28
|
+
const minSizeDescription = renderMinSize(minSize);
|
|
12
29
|
if (error) {
|
|
13
|
-
return (_jsxs("div", { children: [_jsx("i", { className: componentClassNames?.icon, children: ErrorIcon ? _jsx(ErrorIcon, {}) : null }), _jsx("p", { className: componentClassNames?.title, children: error }), _jsx("p", { className: componentClassNames?.description, children:
|
|
30
|
+
return (_jsxs("div", { children: [_jsx("i", { className: componentClassNames?.icon, children: ErrorIcon ? _jsx(ErrorIcon, {}) : null }), _jsx("p", { className: componentClassNames?.title, children: error }), _jsx("p", { className: componentClassNames?.description, children: minSizeDescription ?? 'Please try again' })] }));
|
|
14
31
|
}
|
|
15
|
-
return (_jsxs("div", { children: [_jsx("i", { className: componentClassNames?.icon, children: UploadIcon ? _jsx(UploadIcon, {}) : null }), _jsx("p", { className: componentClassNames?.title, children: "Click to Upload" }), _jsx("p", { className: componentClassNames?.description, children:
|
|
32
|
+
return (_jsxs("div", { children: [_jsx("i", { className: componentClassNames?.icon, children: UploadIcon ? _jsx(UploadIcon, {}) : null }), _jsx("p", { className: componentClassNames?.title, children: "Click to Upload" }), _jsx("p", { className: componentClassNames?.description, children: minSizeDescription ?? 'or drag an image over this box' })] }));
|
|
16
33
|
}
|
|
17
34
|
export default ImageInputPanel;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
interface PreprocessImageOptions {
|
|
2
|
+
maxSize?: [number?, number?];
|
|
3
|
+
minSize?: [number?, number?];
|
|
4
|
+
}
|
|
5
|
+
export declare function preprocessImage(file: File, options?: PreprocessImageOptions): Promise<File>;
|
|
2
6
|
export default preprocessImage;
|
|
@@ -8,7 +8,11 @@ const ALLOWED_TYPES = [
|
|
|
8
8
|
function isSupportedImage(file) {
|
|
9
9
|
return ALLOWED_TYPES.includes(file.type);
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
function fitWithinMaxSize(width, height, maxWidth, maxHeight) {
|
|
12
|
+
const scale = Math.min(maxWidth / width, maxHeight / height, 1);
|
|
13
|
+
return [Math.round(width * scale), Math.round(height * scale)];
|
|
14
|
+
}
|
|
15
|
+
export async function preprocessImage(file, options = {}) {
|
|
12
16
|
if (!isSupportedImage(file)) {
|
|
13
17
|
throw new Error('Unsupported file type');
|
|
14
18
|
}
|
|
@@ -16,20 +20,24 @@ export async function preprocessImage(file, maxWidth = 1200) {
|
|
|
16
20
|
return file;
|
|
17
21
|
}
|
|
18
22
|
const bitmap = await createImageBitmap(file);
|
|
23
|
+
const { maxSize, minSize } = options;
|
|
24
|
+
const maxWidth = maxSize?.[0] || 1000;
|
|
25
|
+
const maxHeight = maxSize?.[1] || 1000;
|
|
26
|
+
const minWidth = minSize?.[0] || 0;
|
|
27
|
+
const minHeight = minSize?.[1] || 0;
|
|
19
28
|
let { width, height } = bitmap;
|
|
20
|
-
if (width
|
|
21
|
-
|
|
22
|
-
width = maxWidth;
|
|
23
|
-
height = Math.round(height * scale);
|
|
29
|
+
if (width < minWidth || height < minHeight) {
|
|
30
|
+
throw new Error(`Image is smaller than the minimum size`);
|
|
24
31
|
}
|
|
32
|
+
const [newWidth, newHeight] = fitWithinMaxSize(width, height, maxWidth, maxHeight);
|
|
25
33
|
const canvas = document.createElement('canvas');
|
|
26
|
-
canvas.width =
|
|
27
|
-
canvas.height =
|
|
34
|
+
canvas.width = newWidth;
|
|
35
|
+
canvas.height = newHeight;
|
|
28
36
|
const ctx = canvas.getContext('2d');
|
|
29
37
|
if (!ctx) {
|
|
30
38
|
throw new Error('Could not get canvas context');
|
|
31
39
|
}
|
|
32
|
-
ctx.drawImage(bitmap, 0, 0,
|
|
40
|
+
ctx.drawImage(bitmap, 0, 0, newWidth, newHeight);
|
|
33
41
|
const blob = await new Promise((resolve, reject) => {
|
|
34
42
|
canvas.toBlob((data) => {
|
|
35
43
|
if (data) {
|