@widergy/mobile-ui 1.25.1 → 1.26.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.
- package/CHANGELOG.md +14 -0
- package/lib/components/MultipleFilePicker/index.js +54 -46
- package/lib/components/MultipleFilePicker/utils.js +67 -0
- package/lib/components/UTIcon/README.md +12 -9
- package/lib/components/UTIcon/index.js +32 -5
- package/lib/components/UTIcon/styles.js +34 -0
- package/lib/components/UTIcon/utils.js +29 -5
- package/lib/components/UTTextInput/versions/V0/flavors/OutlinedInput/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.26.1](https://github.com/widergy/mobile-ui/compare/v1.26.0...v1.26.1) (2024-09-26)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* preview and validations attachment list ([#361](https://github.com/widergy/mobile-ui/issues/361)) ([7fa7c2b](https://github.com/widergy/mobile-ui/commit/7fa7c2b7ff95bd916d7f44804c4afc8055936a50))
|
|
7
|
+
|
|
8
|
+
# [1.26.0](https://github.com/widergy/mobile-ui/compare/v1.25.1...v1.26.0) (2024-09-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* ut icon updates ([#358](https://github.com/widergy/mobile-ui/issues/358)) ([30af561](https://github.com/widergy/mobile-ui/commit/30af5617d47e62ccbba7bf175a3a5127e0f5db7e))
|
|
14
|
+
|
|
1
15
|
## [1.25.1](https://github.com/widergy/mobile-ui/compare/v1.25.0...v1.25.1) (2024-09-23)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -2,30 +2,50 @@ import React, { Component } from 'react';
|
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
// eslint-disable-next-line import/no-unresolved
|
|
4
4
|
import DocumentPicker from 'react-native-document-picker';
|
|
5
|
-
import { isEmpty } from '
|
|
5
|
+
import { isArray, isEmpty } from 'lodash';
|
|
6
6
|
|
|
7
|
-
import { retrieveFile,
|
|
7
|
+
import { retrieveFile, blobToFile } from '../../utils/fileUtils.js';
|
|
8
8
|
|
|
9
9
|
import Picker from './components/Picker';
|
|
10
10
|
import UploadedFiles from './components/UploadedFiles';
|
|
11
11
|
import { UPLOAD_ICON, DEFAULT_MAX_SIZE } from './constants';
|
|
12
12
|
import filePickerPropTypes from './propTypes';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getInitialValuesFrom,
|
|
15
|
+
isInvalidFileSize,
|
|
16
|
+
onlyPDFAllowed,
|
|
17
|
+
validateFileFormat,
|
|
18
|
+
validateFileQuantity,
|
|
19
|
+
validateFileType,
|
|
20
|
+
validateOnlyPDFAllowed
|
|
21
|
+
} from './utils';
|
|
14
22
|
|
|
15
23
|
class MultipleFilePicker extends Component {
|
|
16
24
|
constructor(props) {
|
|
17
25
|
super(props);
|
|
26
|
+
const { files } = props?.value || [];
|
|
27
|
+
|
|
18
28
|
this.state = {
|
|
19
|
-
uploadedFiles: []
|
|
29
|
+
uploadedFiles: isArray(files) ? getInitialValuesFrom(files) : [],
|
|
30
|
+
rawFiles: isArray(files) ? files : []
|
|
20
31
|
};
|
|
21
32
|
}
|
|
22
33
|
|
|
34
|
+
componentDidUpdate(_, prevState) {
|
|
35
|
+
const { rawFiles } = this.state;
|
|
36
|
+
const { onChange } = this.props;
|
|
37
|
+
|
|
38
|
+
if (prevState.rawFiles !== rawFiles && onChange) {
|
|
39
|
+
const payloadOnChange = isEmpty(rawFiles) ? null : { files: rawFiles };
|
|
40
|
+
onChange(payloadOnChange);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
23
44
|
handleShowPicker = async () => {
|
|
24
45
|
const {
|
|
25
46
|
allowedTypes,
|
|
26
47
|
onMaxSizeError,
|
|
27
48
|
onError,
|
|
28
|
-
onChange,
|
|
29
49
|
maxFileByteSize,
|
|
30
50
|
maxFiles,
|
|
31
51
|
minFiles,
|
|
@@ -41,47 +61,34 @@ class MultipleFilePicker extends Component {
|
|
|
41
61
|
});
|
|
42
62
|
const { uploadedFiles } = this.state;
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
throw new Error(
|
|
70
|
-
pdfFormatError ||
|
|
71
|
-
`El formato de archivo es inválido. (Válidos: ${allowedPDFUploadSizes
|
|
72
|
-
?.map(aspectRatio => aspectRatio.name)
|
|
73
|
-
.join(' - ')})`
|
|
74
|
-
);
|
|
64
|
+
const filesSelectedQuantity = uploadedFiles.length + documents.length;
|
|
65
|
+
validateFileQuantity(filesSelectedQuantity, { maxFiles, minFiles }, fileTypeError);
|
|
66
|
+
|
|
67
|
+
await Promise.all(
|
|
68
|
+
documents.map(async document => {
|
|
69
|
+
const hasFileBeenUploaded = uploadedFiles.some(fileAdded => fileAdded.name === document.name);
|
|
70
|
+
|
|
71
|
+
if (!hasFileBeenUploaded) {
|
|
72
|
+
validateOnlyPDFAllowed(document, allowedTypes, fileTypeError);
|
|
73
|
+
validateFileType(document, allowedTypes, fileTypeError);
|
|
74
|
+
|
|
75
|
+
const isValid = isInvalidFileSize(document, maxFileByteSize, onMaxSizeError);
|
|
76
|
+
if (isValid) return;
|
|
77
|
+
|
|
78
|
+
const file =
|
|
79
|
+
!avoidRetrieveFile && (await retrieveFile(document.uri, document.type, document.name));
|
|
80
|
+
|
|
81
|
+
await validateFileFormat(file, document, allowedPDFUploadSizes, pdfFormatError);
|
|
82
|
+
|
|
83
|
+
const fileToUpload = avoidRetrieveFile ? { document } : { file: blobToFile(file, document.type) };
|
|
84
|
+
|
|
85
|
+
this.setState(prevState => ({
|
|
86
|
+
uploadedFiles: [...prevState.uploadedFiles, { name: document.name, size: document.size }],
|
|
87
|
+
rawFiles: [...prevState.rawFiles, fileToUpload]
|
|
88
|
+
}));
|
|
75
89
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (onChange) {
|
|
79
|
-
onChange(avoidRetrieveFile ? { document } : { file: blobToFile(file, document.type) });
|
|
80
|
-
}
|
|
81
|
-
this.setState(prevState => ({
|
|
82
|
-
uploadedFiles: [...prevState.uploadedFiles, { name: document.name, size: document.size }]
|
|
83
|
-
}));
|
|
84
|
-
});
|
|
90
|
+
})
|
|
91
|
+
);
|
|
85
92
|
} catch (err) {
|
|
86
93
|
if (!DocumentPicker.isCancel(err)) {
|
|
87
94
|
onError(err.message);
|
|
@@ -95,7 +102,8 @@ class MultipleFilePicker extends Component {
|
|
|
95
102
|
onChange(null);
|
|
96
103
|
}
|
|
97
104
|
this.setState(prevState => ({
|
|
98
|
-
uploadedFiles: prevState.uploadedFiles.filter((a, i) => i !== index)
|
|
105
|
+
uploadedFiles: prevState.uploadedFiles.filter((a, i) => i !== index),
|
|
106
|
+
rawFiles: prevState.rawFiles.filter((a, i) => i !== index)
|
|
99
107
|
}));
|
|
100
108
|
};
|
|
101
109
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { PDFDocument } from 'pdf-lib';
|
|
2
|
+
import { isEmpty } from 'lodash';
|
|
3
|
+
|
|
4
|
+
import { isImageByUri } from '../../utils/fileUtils.js';
|
|
2
5
|
|
|
3
6
|
export const onlyPDFAllowed = allowedTypes => allowedTypes.length === 1 && allowedTypes[0].includes('pdf');
|
|
4
7
|
|
|
@@ -39,3 +42,67 @@ export const pdfAspectRatioValidation = async (file, allowedPDFUploadSizes) => {
|
|
|
39
42
|
return !pageMatches({ width, height }, allowedPDFUploadSizes);
|
|
40
43
|
});
|
|
41
44
|
};
|
|
45
|
+
|
|
46
|
+
export const validateFileQuantity = (fieldQuantity, { maxFiles, minFiles }, fileTypeError) => {
|
|
47
|
+
if (fieldQuantity > maxFiles) {
|
|
48
|
+
throw new Error(fileTypeError || 'La cantidad de archivos supera al máximo permitido');
|
|
49
|
+
}
|
|
50
|
+
if (fieldQuantity < minFiles) {
|
|
51
|
+
throw new Error(fileTypeError || 'La cantidad de archivos es menor al mínimo permitido');
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const isPDF = document => document.type.includes('pdf');
|
|
56
|
+
const isImage = document => document.type.includes('image');
|
|
57
|
+
|
|
58
|
+
export const validateOnlyPDFAllowed = (document, allowedTypes, fileTypeError) => {
|
|
59
|
+
const condition = onlyPDFAllowed(allowedTypes) && !isPDF(document);
|
|
60
|
+
if (condition) {
|
|
61
|
+
throw new Error(fileTypeError || 'El tipo de archivo debe ser PDF.');
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const validateFileType = (document, allowedTypes, fileTypeError) => {
|
|
66
|
+
const isInvalidImage = !isImage(document) && !isImageByUri(document.uri);
|
|
67
|
+
const condition = allowedTypes && isInvalidImage && !isPDF(document);
|
|
68
|
+
if (condition) {
|
|
69
|
+
throw new Error(fileTypeError || 'Tipo de archivo inválido.');
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const isInvalidFileSize = (document, maxFileByteSize, onMaxSizeError) => {
|
|
74
|
+
const isInvalid = maxFileByteSize && document.size > maxFileByteSize;
|
|
75
|
+
if (isInvalid) {
|
|
76
|
+
if (onMaxSizeError) {
|
|
77
|
+
onMaxSizeError(document.size, maxFileByteSize);
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error(`El archivo debe ser menor a ${maxFileByteSize}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return isInvalid;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const validateFileFormat = async (file, document, allowedPDFUploadSizes, pdfFormatError) => {
|
|
86
|
+
const isInvalid = file && isPDF(document) && !isEmpty(allowedPDFUploadSizes);
|
|
87
|
+
if (isInvalid) {
|
|
88
|
+
const isWrongFormat = await pdfAspectRatioValidation(file, allowedPDFUploadSizes);
|
|
89
|
+
if (isWrongFormat) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
pdfFormatError ||
|
|
92
|
+
`El formato de archivo es inválido. (Válidos: ${allowedPDFUploadSizes
|
|
93
|
+
?.map(aspectRatio => aspectRatio.name)
|
|
94
|
+
.join(' - ')})`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const getInitialValuesFrom = files =>
|
|
101
|
+
files
|
|
102
|
+
?.map(fileBlob => {
|
|
103
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
104
|
+
const fileData = fileBlob?.file?._data || {};
|
|
105
|
+
|
|
106
|
+
return fileData ? { name: fileData?.name, size: fileData?.size } : null;
|
|
107
|
+
})
|
|
108
|
+
.filter(file => file !== null) || [];
|
|
@@ -13,14 +13,17 @@ For a comprehensive list of available icons and their design specifications, ref
|
|
|
13
13
|
|
|
14
14
|
## Props
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
16
|
+
|
|
17
|
+
| Name | Type | Default | Description |
|
|
18
|
+
| :----------- | --------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| area | boolean | false | Padding around the icon is added when it is true. |
|
|
20
|
+
| colorTheme | string | 'dark' | The icon color, must be from the theme palette. |
|
|
21
|
+
| fillTheme | string | | The internal fill color must be from the theme palette. |
|
|
22
|
+
| name | string | | The name of the icon from the Tabler Icons library. |
|
|
23
|
+
| shade | string | | The shade of the color theme to use. |
|
|
24
|
+
| size | number | | The size of the icon. |
|
|
25
|
+
| style | object | | Custom styles to apply to the icon. |
|
|
26
|
+
| variant | string | 'default' | It depends on the `area` property and defines whether it should be displayed filled (`default`) or just with a border (`outlined`) around the icon. |
|
|
24
27
|
|
|
25
28
|
## Icon Names
|
|
26
29
|
|
|
@@ -97,7 +100,7 @@ Clarifications:
|
|
|
97
100
|
|
|
98
101
|
- Note that not all icons look good with an internal fill color.
|
|
99
102
|
|
|
100
|
-
## Fill Shade
|
|
103
|
+
## Fill Shade (optional)
|
|
101
104
|
|
|
102
105
|
The `fillShade` prop must be one of these:
|
|
103
106
|
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { number, object, oneOfType, string } from 'prop-types';
|
|
2
|
+
import { bool, number, object, oneOfType, string } from 'prop-types';
|
|
3
|
+
import { View } from 'react-native';
|
|
3
4
|
import * as TablerIcons from '@tabler/icons-react-native';
|
|
4
5
|
|
|
5
6
|
import { useTheme } from '../../theming';
|
|
6
7
|
|
|
7
8
|
import { ENERGY_ICONS } from './constants';
|
|
8
|
-
import { getIconProps } from './utils';
|
|
9
|
+
import { getIconProps, getVariantStyle } from './utils';
|
|
10
|
+
import ownStyles from './styles';
|
|
9
11
|
|
|
10
|
-
const UTIcon = ({
|
|
12
|
+
const UTIcon = ({
|
|
13
|
+
area,
|
|
14
|
+
color,
|
|
15
|
+
colorTheme = 'dark',
|
|
16
|
+
fillShade,
|
|
17
|
+
fillTheme,
|
|
18
|
+
name,
|
|
19
|
+
shade,
|
|
20
|
+
size,
|
|
21
|
+
style,
|
|
22
|
+
variant = 'default'
|
|
23
|
+
}) => {
|
|
11
24
|
const theme = useTheme();
|
|
12
25
|
|
|
13
26
|
const IconComponent = ENERGY_ICONS[name] || TablerIcons[name];
|
|
@@ -25,10 +38,23 @@ const UTIcon = ({ color, colorTheme = 'dark', fillShade, fillTheme, name, shade,
|
|
|
25
38
|
theme
|
|
26
39
|
});
|
|
27
40
|
|
|
28
|
-
return
|
|
41
|
+
return (
|
|
42
|
+
<View
|
|
43
|
+
style={
|
|
44
|
+
area && [
|
|
45
|
+
ownStyles.withArea,
|
|
46
|
+
ownStyles[`size${size}`],
|
|
47
|
+
getVariantStyle({ color: colorTheme || color, shade, theme, variant })
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
>
|
|
51
|
+
<IconComponent {...iconProps} />
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
29
54
|
};
|
|
30
55
|
|
|
31
56
|
UTIcon.propTypes = {
|
|
57
|
+
area: bool,
|
|
32
58
|
/**
|
|
33
59
|
* @deprecated The "color" prop is deprecated and will be removed in a future release. Please use "colorTheme" instead.
|
|
34
60
|
*/
|
|
@@ -39,7 +65,8 @@ UTIcon.propTypes = {
|
|
|
39
65
|
name: string,
|
|
40
66
|
shade: string,
|
|
41
67
|
size: oneOfType([number, string]),
|
|
42
|
-
style: object
|
|
68
|
+
style: object,
|
|
69
|
+
variant: string
|
|
43
70
|
};
|
|
44
71
|
|
|
45
72
|
export default UTIcon;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const paddingSmall = 2;
|
|
4
|
+
const paddingMedium = 4;
|
|
5
|
+
const paddingLarge = 10;
|
|
6
|
+
const paddingXlarge = 12;
|
|
7
|
+
const paddingXxlarge = 21;
|
|
8
|
+
|
|
9
|
+
const generateSizeStyles = () => {
|
|
10
|
+
const sizes = [
|
|
11
|
+
{ range: [0, 23], padding: paddingSmall },
|
|
12
|
+
{ range: [24, 27], padding: paddingMedium },
|
|
13
|
+
{ range: [28, 31], padding: paddingLarge },
|
|
14
|
+
{ range: [32, 55], padding: paddingXlarge },
|
|
15
|
+
{ range: [56, 100], padding: paddingXxlarge }
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
return sizes.reduce((styles, { range, padding }) => {
|
|
19
|
+
const [start, end] = range;
|
|
20
|
+
const sizeStyles = Array.from({ length: end - start + 1 }, (_, i) => i + start).reduce(
|
|
21
|
+
(acc, size) => ({ ...acc, [`size${size}`]: { padding } }),
|
|
22
|
+
{}
|
|
23
|
+
);
|
|
24
|
+
return { ...styles, ...sizeStyles };
|
|
25
|
+
}, {});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default StyleSheet.create({
|
|
29
|
+
withArea: {
|
|
30
|
+
backgroundColor: 'transparent',
|
|
31
|
+
borderRadius: 100
|
|
32
|
+
},
|
|
33
|
+
...generateSizeStyles()
|
|
34
|
+
});
|
|
@@ -4,15 +4,15 @@ import { retrieveColor } from './theme';
|
|
|
4
4
|
export const isUTIcon = icon => typeof icon === 'string';
|
|
5
5
|
|
|
6
6
|
export const getIconProps = ({
|
|
7
|
-
name,
|
|
8
|
-
theme,
|
|
9
7
|
color,
|
|
10
8
|
colorTheme,
|
|
11
|
-
shade,
|
|
12
|
-
fillTheme,
|
|
13
9
|
fillShade,
|
|
10
|
+
fillTheme,
|
|
11
|
+
name,
|
|
12
|
+
shade,
|
|
14
13
|
size,
|
|
15
|
-
style
|
|
14
|
+
style,
|
|
15
|
+
theme
|
|
16
16
|
}) => {
|
|
17
17
|
const themeColor = retrieveColor({ colorTheme: color || colorTheme, theme, shade });
|
|
18
18
|
const filled = name.endsWith('Filled');
|
|
@@ -34,3 +34,27 @@ export const getIconProps = ({
|
|
|
34
34
|
...(isEnergyIcon || isBrandIcon ? customIconProps : defaultIconProps)
|
|
35
35
|
};
|
|
36
36
|
};
|
|
37
|
+
|
|
38
|
+
export const getVariantStyle = ({ color, shade, theme, variant }) => {
|
|
39
|
+
const borderColor = retrieveColor({ colorTheme: color, theme, shade });
|
|
40
|
+
|
|
41
|
+
const defaultBackgrounds = {
|
|
42
|
+
accent: theme.Palette.actions?.accent['01'] || theme.Palette.accent['01'],
|
|
43
|
+
dark: theme.Palette.light['03'],
|
|
44
|
+
error: theme.Palette.error['01'],
|
|
45
|
+
gray: theme.Palette.light['03'],
|
|
46
|
+
information: theme.Palette.information['01'],
|
|
47
|
+
light: theme.Palette.light['03'],
|
|
48
|
+
negative: theme.Palette.actions?.negative['01'] || theme.Palette.negative['01'],
|
|
49
|
+
neutral: theme.Palette.actions?.neutral['01'] || theme.Palette.neutral['01'],
|
|
50
|
+
success: theme.Palette.success['01'],
|
|
51
|
+
warning: theme.Palette.warning['01']
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const variantStyles = {
|
|
55
|
+
default: { backgroundColor: defaultBackgrounds[color] },
|
|
56
|
+
outlined: { borderColor, borderWidth: 2 }
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return variantStyles[variant];
|
|
60
|
+
};
|
package/package.json
CHANGED