@widergy/mobile-ui 1.6.0 → 1.8.0
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 +39 -0
- package/lib/components/MultipleFilePicker/components/Input/README.md +77 -0
- package/lib/components/MultipleFilePicker/components/Input/components/ShowPassword/constants.js +2 -0
- package/lib/components/MultipleFilePicker/components/Input/components/ShowPassword/index.js +27 -0
- package/lib/components/MultipleFilePicker/components/Input/components/ShowPassword/propTypes.js +8 -0
- package/lib/components/MultipleFilePicker/components/Input/components/ShowPassword/styles.js +15 -0
- package/lib/components/MultipleFilePicker/components/Input/components/Title/index.js +78 -0
- package/lib/components/MultipleFilePicker/components/Input/components/Title/propTypes.js +14 -0
- package/lib/components/MultipleFilePicker/components/Input/components/Title/styles.js +42 -0
- package/lib/components/MultipleFilePicker/components/Input/components/Underline/index.js +80 -0
- package/lib/components/MultipleFilePicker/components/Input/components/Underline/styles.js +39 -0
- package/lib/components/MultipleFilePicker/components/Input/constants.js +2 -0
- package/lib/components/MultipleFilePicker/components/Input/index.js +299 -0
- package/lib/components/MultipleFilePicker/components/Input/propTypes.js +43 -0
- package/lib/components/MultipleFilePicker/components/Input/styles.js +47 -0
- package/lib/components/MultipleFilePicker/components/Picker/index.js +95 -0
- package/lib/components/MultipleFilePicker/components/Picker/styles.js +47 -0
- package/lib/components/MultipleFilePicker/components/UploadedFiles/index.js +140 -0
- package/lib/components/MultipleFilePicker/components/UploadedFiles/styles.js +65 -0
- package/lib/components/MultipleFilePicker/components/UploadedFiles/utils.js +6 -0
- package/lib/components/MultipleFilePicker/constants.js +18 -0
- package/lib/components/MultipleFilePicker/index.js +162 -0
- package/lib/components/MultipleFilePicker/propTypes.js +17 -0
- package/lib/components/MultipleFilePicker/utils.js +41 -0
- package/lib/components/UTTracker/README.md +24 -0
- package/lib/components/UTTracker/components/Connectors/index.js +26 -0
- package/lib/components/UTTracker/components/Connectors/styles.js +20 -0
- package/lib/components/UTTracker/components/Step/index.js +114 -0
- package/lib/components/UTTracker/components/Step/styles.js +80 -0
- package/lib/components/UTTracker/components/SubStep/index.js +28 -0
- package/lib/components/UTTracker/components/SubStep/styles.js +10 -0
- package/lib/components/UTTracker/constants.js +4 -0
- package/lib/components/UTTracker/index.js +120 -0
- package/lib/components/UTTracker/propTypes.js +19 -0
- package/lib/components/UTTracker/styles.js +57 -0
- package/lib/index.js +2 -0
- package/package.json +3 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { portraitHorizontalScale, portraitVerticalScale } from '../../../../utils/portraitScalingUtils';
|
|
4
|
+
|
|
5
|
+
export default StyleSheet.create({
|
|
6
|
+
container: {
|
|
7
|
+
flexDirection: 'row'
|
|
8
|
+
},
|
|
9
|
+
containerInput: {
|
|
10
|
+
height: '100%',
|
|
11
|
+
width: '100%'
|
|
12
|
+
},
|
|
13
|
+
containerStyle: {
|
|
14
|
+
flex: 1,
|
|
15
|
+
shadowColor: '#E4E6EA',
|
|
16
|
+
elevation: 4
|
|
17
|
+
},
|
|
18
|
+
containerButtonPadding: {
|
|
19
|
+
padding: portraitHorizontalScale(65)
|
|
20
|
+
},
|
|
21
|
+
containerPadding: {
|
|
22
|
+
padding: portraitHorizontalScale(18)
|
|
23
|
+
},
|
|
24
|
+
fieldInput: {
|
|
25
|
+
borderRadius: 5,
|
|
26
|
+
borderStyle: 'dashed',
|
|
27
|
+
borderWidth: 1,
|
|
28
|
+
flex: 1,
|
|
29
|
+
height: 'auto',
|
|
30
|
+
paddingBottom: portraitVerticalScale(24),
|
|
31
|
+
paddingTop: portraitVerticalScale(16),
|
|
32
|
+
width: '100%',
|
|
33
|
+
marginVertical: 10
|
|
34
|
+
},
|
|
35
|
+
helpText: {
|
|
36
|
+
textAlign: 'center',
|
|
37
|
+
marginBottom: 10
|
|
38
|
+
},
|
|
39
|
+
pickerText: {
|
|
40
|
+
textAlign: 'center'
|
|
41
|
+
},
|
|
42
|
+
textStyles: {
|
|
43
|
+
marginLeft: 5,
|
|
44
|
+
marginTop: 16
|
|
45
|
+
},
|
|
46
|
+
textButtonStyles: {
|
|
47
|
+
flex: 1,
|
|
48
|
+
padding: 0,
|
|
49
|
+
textAlignVertical: 'center',
|
|
50
|
+
margin: 15
|
|
51
|
+
},
|
|
52
|
+
touchable: {
|
|
53
|
+
borderRadius: 4,
|
|
54
|
+
paddingHorizontal: portraitHorizontalScale(12),
|
|
55
|
+
height: 80,
|
|
56
|
+
width: '100%'
|
|
57
|
+
},
|
|
58
|
+
uploadTouchable: {
|
|
59
|
+
borderRadius: 4,
|
|
60
|
+
marginHorizontal: 16,
|
|
61
|
+
marginVertical: 10,
|
|
62
|
+
height: 38,
|
|
63
|
+
width: '90%'
|
|
64
|
+
}
|
|
65
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// eslint-disable-next-line import/no-unresolved
|
|
2
|
+
import DocumentPicker from 'react-native-document-picker';
|
|
3
|
+
|
|
4
|
+
import { MEGABYTE } from '../../utils/fileUtils.js';
|
|
5
|
+
|
|
6
|
+
export const FILE_UPLOAD_ICON = 'file-upload';
|
|
7
|
+
|
|
8
|
+
export const UPLOAD_ICON = 'upload';
|
|
9
|
+
|
|
10
|
+
export const CHECK_ICON = 'check';
|
|
11
|
+
|
|
12
|
+
export const TRASH_ICON = 'trash-2';
|
|
13
|
+
|
|
14
|
+
export const TYPE_ICON = 'feather';
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_ALLOWED_TYPES = [DocumentPicker.types.allFiles];
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_MAX_SIZE = 10 * MEGABYTE;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
// eslint-disable-next-line import/no-unresolved
|
|
4
|
+
import DocumentPicker from 'react-native-document-picker';
|
|
5
|
+
import { isEmpty } from '@widergy/web-utils/lib/array';
|
|
6
|
+
|
|
7
|
+
import { retrieveFile, isImageByUri, blobToFile } from '../../utils/fileUtils.js';
|
|
8
|
+
|
|
9
|
+
import Picker from './components/Picker';
|
|
10
|
+
import UploadedFiles from './components/UploadedFiles';
|
|
11
|
+
import { UPLOAD_ICON, DEFAULT_MAX_SIZE } from './constants';
|
|
12
|
+
import filePickerPropTypes from './propTypes';
|
|
13
|
+
import { onlyPDFAllowed, pdfAspectRatioValidation } from './utils';
|
|
14
|
+
|
|
15
|
+
class MultipleFilePicker extends Component {
|
|
16
|
+
constructor(props) {
|
|
17
|
+
super(props);
|
|
18
|
+
this.state = {
|
|
19
|
+
uploadedFiles: []
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
handleShowPicker = async () => {
|
|
24
|
+
const {
|
|
25
|
+
allowedTypes,
|
|
26
|
+
onMaxSizeError,
|
|
27
|
+
onError,
|
|
28
|
+
onChange,
|
|
29
|
+
maxFileByteSize,
|
|
30
|
+
maxFiles,
|
|
31
|
+
minFiles,
|
|
32
|
+
avoidRetrieveFile,
|
|
33
|
+
fileTypeError,
|
|
34
|
+
allowedPDFUploadSizes,
|
|
35
|
+
pdfFormatError
|
|
36
|
+
} = this.props;
|
|
37
|
+
try {
|
|
38
|
+
const documents = await DocumentPicker.pick({
|
|
39
|
+
allowMultiSelection: true,
|
|
40
|
+
type: allowedTypes && !onlyPDFAllowed(allowedTypes) ? allowedTypes : DocumentPicker.types.allFiles
|
|
41
|
+
});
|
|
42
|
+
const { uploadedFiles } = this.state;
|
|
43
|
+
|
|
44
|
+
if (uploadedFiles.length + documents.length > maxFiles) {
|
|
45
|
+
throw new Error(fileTypeError || 'La cantidad de archivos supera al máximo permitido');
|
|
46
|
+
}
|
|
47
|
+
if (uploadedFiles.length + documents.length < minFiles) {
|
|
48
|
+
throw new Error(fileTypeError || 'La cantidad de archivos es menor al mínimo permitido');
|
|
49
|
+
}
|
|
50
|
+
documents.map(async document => {
|
|
51
|
+
const isPDF = document.type.includes('pdf');
|
|
52
|
+
const isImage = document.type.includes('image');
|
|
53
|
+
if (onlyPDFAllowed(allowedTypes) && !isPDF) {
|
|
54
|
+
throw new Error(fileTypeError || 'El tipo de archivo debe ser PDF.');
|
|
55
|
+
}
|
|
56
|
+
if (allowedTypes && !isImage && !isImageByUri(document.uri) && !isPDF) {
|
|
57
|
+
throw new Error(fileTypeError || 'Tipo de archivo inválido.');
|
|
58
|
+
}
|
|
59
|
+
if (maxFileByteSize && document.size > maxFileByteSize) {
|
|
60
|
+
onMaxSizeError(document.size, maxFileByteSize);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const file = !avoidRetrieveFile && (await retrieveFile(document.uri, document.type));
|
|
65
|
+
|
|
66
|
+
if (file && isPDF && !isEmpty(allowedPDFUploadSizes)) {
|
|
67
|
+
const isWrongFormat = await pdfAspectRatioValidation(file, allowedPDFUploadSizes);
|
|
68
|
+
if (isWrongFormat) {
|
|
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
|
+
);
|
|
75
|
+
}
|
|
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
|
+
});
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (!DocumentPicker.isCancel(err)) {
|
|
87
|
+
onError(err.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
handleDeleteFile = index => {
|
|
93
|
+
const { onChange } = this.props;
|
|
94
|
+
if (onChange) {
|
|
95
|
+
onChange(null);
|
|
96
|
+
}
|
|
97
|
+
this.setState(prevState => ({
|
|
98
|
+
uploadedFiles: prevState.uploadedFiles.filter((a, i) => i !== index)
|
|
99
|
+
}));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
render() {
|
|
103
|
+
const {
|
|
104
|
+
error,
|
|
105
|
+
title,
|
|
106
|
+
filePlaceholder,
|
|
107
|
+
style,
|
|
108
|
+
withMarkdownTitle,
|
|
109
|
+
markdownStyles,
|
|
110
|
+
disabled,
|
|
111
|
+
helpText,
|
|
112
|
+
pickerText,
|
|
113
|
+
UploadIcon
|
|
114
|
+
} = this.props;
|
|
115
|
+
const { uploadedFiles } = this.state;
|
|
116
|
+
|
|
117
|
+
return uploadedFiles.length !== 0 ? (
|
|
118
|
+
<View style={style}>
|
|
119
|
+
<UploadedFiles
|
|
120
|
+
icon={UPLOAD_ICON}
|
|
121
|
+
error={error}
|
|
122
|
+
onAdd={this.handleShowPicker}
|
|
123
|
+
onDelete={this.handleDeleteFile}
|
|
124
|
+
uploadedFiles={uploadedFiles}
|
|
125
|
+
filePlaceholder={filePlaceholder}
|
|
126
|
+
title={title}
|
|
127
|
+
withMarkdownTitle={withMarkdownTitle}
|
|
128
|
+
markdownStyles={markdownStyles}
|
|
129
|
+
disabled={disabled}
|
|
130
|
+
pickerText={pickerText}
|
|
131
|
+
helpText={helpText}
|
|
132
|
+
/>
|
|
133
|
+
</View>
|
|
134
|
+
) : (
|
|
135
|
+
<View style={style}>
|
|
136
|
+
<Picker
|
|
137
|
+
icon={UPLOAD_ICON}
|
|
138
|
+
error={error}
|
|
139
|
+
onAdd={this.handleShowPicker}
|
|
140
|
+
onDelete={this.handleDeleteFile}
|
|
141
|
+
uploadedFiles={uploadedFiles}
|
|
142
|
+
filePlaceholder={filePlaceholder}
|
|
143
|
+
title={title}
|
|
144
|
+
withMarkdownTitle={withMarkdownTitle}
|
|
145
|
+
markdownStyles={markdownStyles}
|
|
146
|
+
disabled={disabled}
|
|
147
|
+
pickerText={pickerText}
|
|
148
|
+
helpText={helpText}
|
|
149
|
+
UploadIcon={UploadIcon}
|
|
150
|
+
/>
|
|
151
|
+
</View>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
MultipleFilePicker.defaultProps = {
|
|
157
|
+
maxFileByteSize: DEFAULT_MAX_SIZE
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
MultipleFilePicker.propTypes = filePickerPropTypes;
|
|
161
|
+
|
|
162
|
+
export default MultipleFilePicker;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { oneOfType, string, bool, func, number, arrayOf, shape } from 'prop-types';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
error: oneOfType([string, bool]),
|
|
5
|
+
onChange: func,
|
|
6
|
+
allowedTypes: arrayOf(string),
|
|
7
|
+
filePlaceholder: string,
|
|
8
|
+
title: string,
|
|
9
|
+
onMaxSizeError: func,
|
|
10
|
+
maxFileByteSize: number,
|
|
11
|
+
onError: func,
|
|
12
|
+
fileTypeError: string,
|
|
13
|
+
allowedPDFUploadSizes: arrayOf(
|
|
14
|
+
shape({ name: string, heightInPt: number, widthInPt: number, tolerance: number })
|
|
15
|
+
),
|
|
16
|
+
disabled: bool
|
|
17
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { PDFDocument } from 'pdf-lib';
|
|
2
|
+
|
|
3
|
+
export const onlyPDFAllowed = allowedTypes => allowedTypes.length === 1 && allowedTypes[0].includes('pdf');
|
|
4
|
+
|
|
5
|
+
const lengthMatches = (length1, length2, toleranceInPercentage) => {
|
|
6
|
+
const delta = length1 * (toleranceInPercentage / 100);
|
|
7
|
+
return Math.abs(length1 - length2) < delta;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const pageMatches = (pageSize, allowedPDFUploadSizes) =>
|
|
11
|
+
allowedPDFUploadSizes.some(
|
|
12
|
+
size =>
|
|
13
|
+
(lengthMatches(size.heightInPt, pageSize.height, size.toleranceInPercentage) &&
|
|
14
|
+
lengthMatches(size.widthInPt, pageSize.width, size.toleranceInPercentage)) ||
|
|
15
|
+
(lengthMatches(size.heightInPt, pageSize.width, size.toleranceInPercentage) &&
|
|
16
|
+
lengthMatches(size.widthInPt, pageSize.height, size.toleranceInPercentage))
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const blobToBase64 = blob =>
|
|
20
|
+
new Promise((resolve, reject) => {
|
|
21
|
+
const reader = new FileReader();
|
|
22
|
+
reader.onload = function () {
|
|
23
|
+
const result = reader.result.replace(/^data:.+;base64,/, '');
|
|
24
|
+
resolve(result);
|
|
25
|
+
};
|
|
26
|
+
reader.onerror = function () {
|
|
27
|
+
reject(new Error('No es posible leer el archivo.'));
|
|
28
|
+
};
|
|
29
|
+
reader.readAsDataURL(blob);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const pdfAspectRatioValidation = async (file, allowedPDFUploadSizes) => {
|
|
33
|
+
const base64 = await blobToBase64(file);
|
|
34
|
+
const pdf = await PDFDocument.load(base64);
|
|
35
|
+
const pages = pdf.getPages();
|
|
36
|
+
|
|
37
|
+
return pages.some(page => {
|
|
38
|
+
const { width, height } = page.getSize();
|
|
39
|
+
return !pageMatches({ width, height }, allowedPDFUploadSizes);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# UTTracker
|
|
2
|
+
|
|
3
|
+
### Description
|
|
4
|
+
|
|
5
|
+
This component displays a vertical stepper intended to track progress
|
|
6
|
+
|
|
7
|
+
## Props
|
|
8
|
+
|
|
9
|
+
| Name | Type | Default | Description |
|
|
10
|
+
| ----------- | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| title | string | | Displays the title above the tracker. |
|
|
12
|
+
| mode | card \| flat | card | Defines whether the component should display as a card or as the bare components without any background. |
|
|
13
|
+
| variant | standard \| error | standard | Defines the color scheme of the tracker steps and the icon on the active step. |
|
|
14
|
+
| steps | stepsType | | Displays a step with a rounded icon on the left for each object received. |
|
|
15
|
+
| currentStep | number | | Defines which step will be active, previous ones will be marked as completed while following ones will not. Use 1 for the first step |
|
|
16
|
+
| detailsTab | detailsType | | Defines whether the detailsTab is enabled or not, if it is, it will show the subSteps of each step. The title property shows a message along the tab (card mode only) |
|
|
17
|
+
|
|
18
|
+
### Custom Types
|
|
19
|
+
|
|
20
|
+
| Type | Shape |
|
|
21
|
+
| ------------ | ------------------------------------------------------------------------------ |
|
|
22
|
+
| stepsType | `{ id: number, title: string, subtitle: string, subSteps: [...subStepsType] }` |
|
|
23
|
+
| subStepsType | `{ id: number, title: string, subtitle: string }` |
|
|
24
|
+
| detailsType | `{ enabled: bool, title: string }` |
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { number } from 'prop-types';
|
|
4
|
+
import merge from 'lodash/merge';
|
|
5
|
+
import { ViewPropTypes } from 'deprecated-react-native-prop-types';
|
|
6
|
+
|
|
7
|
+
import ownStyles from './styles';
|
|
8
|
+
|
|
9
|
+
const Connectors = ({ firstStepPosition, lastStepPosition, stepperHeight, style }) => {
|
|
10
|
+
const themedStyles = merge({}, ownStyles, style);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<View style={themedStyles.wrapper(firstStepPosition, lastStepPosition, stepperHeight)}>
|
|
14
|
+
<View style={themedStyles.connectors} />
|
|
15
|
+
</View>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
Connectors.propTypes = {
|
|
20
|
+
firstStepPosition: number,
|
|
21
|
+
lastStepPosition: number,
|
|
22
|
+
stepperHeight: number,
|
|
23
|
+
style: ViewPropTypes.style
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Connectors;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { OVAL_SIZE } from '../../styles';
|
|
4
|
+
|
|
5
|
+
export default StyleSheet.create({
|
|
6
|
+
connectors: {
|
|
7
|
+
borderColor: 'gray',
|
|
8
|
+
borderLeftWidth: 2,
|
|
9
|
+
borderStyle: 'dashed',
|
|
10
|
+
flexGrow: 1
|
|
11
|
+
},
|
|
12
|
+
wrapper: (firstStepPosition, lastStepHeight, stepperHeight) => ({
|
|
13
|
+
height: '100%',
|
|
14
|
+
paddingBottom: stepperHeight - lastStepHeight,
|
|
15
|
+
paddingLeft: OVAL_SIZE / 2 - 1,
|
|
16
|
+
paddingTop: OVAL_SIZE + 4 + firstStepPosition,
|
|
17
|
+
position: 'absolute',
|
|
18
|
+
width: 2
|
|
19
|
+
})
|
|
20
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { bool, func } from 'prop-types';
|
|
4
|
+
import merge from 'lodash/merge';
|
|
5
|
+
|
|
6
|
+
import Label from '../../../Label';
|
|
7
|
+
import { StepPropTypes, VariantPropTypes } from '../../propTypes';
|
|
8
|
+
import Icon from '../../../Icon';
|
|
9
|
+
import { ERROR } from '../../constants';
|
|
10
|
+
|
|
11
|
+
import ownStyles, { ownVariantStyles } from './styles';
|
|
12
|
+
|
|
13
|
+
const ERROR_ICON_SIZE = 16;
|
|
14
|
+
|
|
15
|
+
const Step = ({
|
|
16
|
+
first,
|
|
17
|
+
isActive,
|
|
18
|
+
isCompleted,
|
|
19
|
+
last,
|
|
20
|
+
setFirstStepPosition,
|
|
21
|
+
setLastStepPosition,
|
|
22
|
+
step,
|
|
23
|
+
style = {},
|
|
24
|
+
variant
|
|
25
|
+
}) => {
|
|
26
|
+
const stepCompleted = isCompleted(step.id);
|
|
27
|
+
const stepActive = isActive(step.id);
|
|
28
|
+
|
|
29
|
+
const themedStyles = merge({}, ownStyles, ownVariantStyles[variant], style[variant]);
|
|
30
|
+
|
|
31
|
+
const [stepIconOffset, setStepIconOffset] = useState(0);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View
|
|
35
|
+
onLayout={e => {
|
|
36
|
+
if (first) setFirstStepPosition(e.nativeEvent.layout.y + stepIconOffset);
|
|
37
|
+
if (last) setLastStepPosition(e.nativeEvent.layout.y + stepIconOffset);
|
|
38
|
+
}}
|
|
39
|
+
style={[
|
|
40
|
+
themedStyles.outerContainer,
|
|
41
|
+
stepCompleted && themedStyles.completedOuterContainer,
|
|
42
|
+
stepActive && themedStyles.activeOuterContainer,
|
|
43
|
+
!first && ownStyles.stepMargin
|
|
44
|
+
]}
|
|
45
|
+
>
|
|
46
|
+
<View
|
|
47
|
+
onLayout={e => (first || last) && setStepIconOffset(e.nativeEvent.layout.y)}
|
|
48
|
+
style={[
|
|
49
|
+
themedStyles.container,
|
|
50
|
+
stepCompleted && themedStyles.completedContainer,
|
|
51
|
+
stepActive && themedStyles.activeContainer
|
|
52
|
+
]}
|
|
53
|
+
>
|
|
54
|
+
<View
|
|
55
|
+
style={[
|
|
56
|
+
themedStyles.innerContainer,
|
|
57
|
+
stepCompleted && themedStyles.completedInnerContainer,
|
|
58
|
+
stepActive && themedStyles.activeInnerContainer
|
|
59
|
+
]}
|
|
60
|
+
>
|
|
61
|
+
{stepActive && variant === ERROR && (
|
|
62
|
+
<Icon
|
|
63
|
+
color="white"
|
|
64
|
+
height={ERROR_ICON_SIZE}
|
|
65
|
+
name="close"
|
|
66
|
+
size={ERROR_ICON_SIZE}
|
|
67
|
+
style={themedStyles.icon}
|
|
68
|
+
type="antdesign"
|
|
69
|
+
width={ERROR_ICON_SIZE}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
</View>
|
|
73
|
+
</View>
|
|
74
|
+
<View style={themedStyles.textContainer}>
|
|
75
|
+
{step.title && (
|
|
76
|
+
<Label
|
|
77
|
+
color={
|
|
78
|
+
stepCompleted
|
|
79
|
+
? themedStyles.completedTitleColor
|
|
80
|
+
: stepActive
|
|
81
|
+
? themedStyles.activeTitleColor
|
|
82
|
+
: themedStyles.titleColor
|
|
83
|
+
}
|
|
84
|
+
>
|
|
85
|
+
{step.title}
|
|
86
|
+
</Label>
|
|
87
|
+
)}
|
|
88
|
+
{step.subtitle && (
|
|
89
|
+
<Label color={themedStyles.subtitleColor} style={themedStyles.subtitle}>
|
|
90
|
+
{step.subtitle}
|
|
91
|
+
</Label>
|
|
92
|
+
)}
|
|
93
|
+
{step.description && (
|
|
94
|
+
<Label color={themedStyles.descriptionColor} style={themedStyles.description}>
|
|
95
|
+
{step.description}
|
|
96
|
+
</Label>
|
|
97
|
+
)}
|
|
98
|
+
</View>
|
|
99
|
+
</View>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
Step.propTypes = {
|
|
104
|
+
first: bool,
|
|
105
|
+
isActive: func,
|
|
106
|
+
isCompleted: func,
|
|
107
|
+
last: bool,
|
|
108
|
+
setFirstStepPosition: func,
|
|
109
|
+
setLastStepPosition: func,
|
|
110
|
+
step: StepPropTypes,
|
|
111
|
+
variant: VariantPropTypes
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export default Step;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { OVAL_SIZE } from '../../styles';
|
|
4
|
+
import { ERROR, STANDARD } from '../../constants';
|
|
5
|
+
|
|
6
|
+
export const ownVariantStyles = {
|
|
7
|
+
[ERROR]: {
|
|
8
|
+
activeContainer: {
|
|
9
|
+
backgroundColor: 'red',
|
|
10
|
+
borderColor: 'red'
|
|
11
|
+
},
|
|
12
|
+
activeInnerContainer: {
|
|
13
|
+
backgroundColor: 'red'
|
|
14
|
+
},
|
|
15
|
+
activeTitleColor: 'red',
|
|
16
|
+
completedContainer: {
|
|
17
|
+
borderColor: '#F38595'
|
|
18
|
+
},
|
|
19
|
+
completedInnerContainer: {
|
|
20
|
+
backgroundColor: '#F38595'
|
|
21
|
+
},
|
|
22
|
+
completedTitleColor: '#F38595'
|
|
23
|
+
},
|
|
24
|
+
[STANDARD]: {
|
|
25
|
+
activeContainer: {
|
|
26
|
+
borderColor: 'blue'
|
|
27
|
+
},
|
|
28
|
+
activeInnerContainer: {
|
|
29
|
+
backgroundColor: 'blue'
|
|
30
|
+
},
|
|
31
|
+
activeTitleColor: 'blue',
|
|
32
|
+
completedContainer: {
|
|
33
|
+
borderColor: '#93ACFF'
|
|
34
|
+
},
|
|
35
|
+
completedInnerContainer: {
|
|
36
|
+
backgroundColor: '#93ACFF'
|
|
37
|
+
},
|
|
38
|
+
completedTitleColor: '#93ACFF'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default StyleSheet.create({
|
|
43
|
+
container: {
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
backgroundColor: 'white',
|
|
46
|
+
borderColor: 'gray',
|
|
47
|
+
borderRadius: OVAL_SIZE / 2,
|
|
48
|
+
borderWidth: OVAL_SIZE / 10,
|
|
49
|
+
height: OVAL_SIZE,
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
marginRight: 16,
|
|
52
|
+
width: OVAL_SIZE
|
|
53
|
+
},
|
|
54
|
+
description: {
|
|
55
|
+
marginTop: 4
|
|
56
|
+
},
|
|
57
|
+
descriptionColor: 'gray',
|
|
58
|
+
icon: {
|
|
59
|
+
position: 'absolute',
|
|
60
|
+
right: -3,
|
|
61
|
+
top: -3
|
|
62
|
+
},
|
|
63
|
+
innerContainer: {
|
|
64
|
+
backgroundColor: 'white',
|
|
65
|
+
borderRadius: OVAL_SIZE / 4,
|
|
66
|
+
height: OVAL_SIZE / 2,
|
|
67
|
+
width: OVAL_SIZE / 2
|
|
68
|
+
},
|
|
69
|
+
outerContainer: {
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
flexDirection: 'row'
|
|
72
|
+
},
|
|
73
|
+
stepMargin: { marginTop: 24 },
|
|
74
|
+
subtitle: {
|
|
75
|
+
marginTop: 4
|
|
76
|
+
},
|
|
77
|
+
textContainer: {
|
|
78
|
+
width: '90%'
|
|
79
|
+
}
|
|
80
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { string } from 'prop-types';
|
|
4
|
+
import merge from 'lodash/merge';
|
|
5
|
+
import { ViewPropTypes } from 'deprecated-react-native-prop-types';
|
|
6
|
+
|
|
7
|
+
import Label from '../../../Label';
|
|
8
|
+
|
|
9
|
+
import ownStyles from './styles';
|
|
10
|
+
|
|
11
|
+
const SubStep = ({ style, subtitle, title }) => {
|
|
12
|
+
const themedStyles = merge({}, ownStyles, style);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<View style={themedStyles.container}>
|
|
16
|
+
<Label color={themedStyles.titleColor}>{title}</Label>
|
|
17
|
+
<Label color={themedStyles.subtitleColor}>{subtitle}</Label>
|
|
18
|
+
</View>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
SubStep.propTypes = {
|
|
23
|
+
style: ViewPropTypes.style,
|
|
24
|
+
subtitle: string,
|
|
25
|
+
title: string
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default SubStep;
|