@telus-uds/components-base 2.2.0 → 2.3.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 +17 -2
- package/lib/Autocomplete/Autocomplete.js +8 -3
- package/lib/Card/CardBase.js +7 -1
- package/lib/Card/PressableCardBase.js +1 -1
- package/lib/FileUpload/FileUpload.js +57 -22
- package/lib/FileUpload/dictionary.js +6 -2
- package/lib/Listbox/ListboxOverlay.js +6 -3
- package/lib/StepTracker/StepTracker.js +10 -3
- package/package.json +3 -3
- package/src/Autocomplete/Autocomplete.jsx +9 -2
- package/src/Card/CardBase.jsx +5 -2
- package/src/Card/PressableCardBase.jsx +12 -1
- package/src/FileUpload/FileUpload.jsx +71 -28
- package/src/FileUpload/dictionary.js +6 -2
- package/src/Listbox/ListboxOverlay.jsx +5 -3
- package/src/StepTracker/StepTracker.jsx +16 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
# Change Log - @telus-uds/components-base
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This log was last generated on Thu, 19 Dec 2024 04:54:39 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## 2.3.0
|
|
8
|
+
|
|
9
|
+
Thu, 19 Dec 2024 04:54:39 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- `Autocomplete`: Enable `tokens` prop to set custom styles for the `maxHeight` and `scroll` behavior. (jaime.tuyuc@telus.com)
|
|
14
|
+
- `FileUpload`: add minFilesCount and minFileSize props (guillermo.peitzner@telus.com)
|
|
15
|
+
- Bump @telus-uds/system-theme-tokens to v3.2.0
|
|
16
|
+
|
|
17
|
+
### Patches
|
|
18
|
+
|
|
19
|
+
- `Card`: Include additional `tokenKeys` to remove warnings. (jaime.tuyuc@telus.com)
|
|
20
|
+
- `StepTracker`: elements must only use supported ARIA attributes (sergio.ramirez@telus.com)
|
|
21
|
+
|
|
7
22
|
## 2.2.0
|
|
8
23
|
|
|
9
|
-
Fri, 06 Dec 2024 02:03
|
|
24
|
+
Fri, 06 Dec 2024 02:06:03 GMT
|
|
10
25
|
|
|
11
26
|
### Minor changes
|
|
12
27
|
|
|
@@ -6,7 +6,7 @@ import View from "react-native-web/dist/exports/View";
|
|
|
6
6
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
7
7
|
import throttle from 'lodash.throttle';
|
|
8
8
|
import matchAll from 'string.prototype.matchall';
|
|
9
|
-
import { inputSupportsProps, selectSystemProps, textInputProps, textInputHandlerProps, useCopy, htmlAttrs, useOverlaidPosition, useSafeLayoutEffect } from '../utils';
|
|
9
|
+
import { inputSupportsProps, selectSystemProps, textInputProps, textInputHandlerProps, useCopy, htmlAttrs, useOverlaidPosition, useSafeLayoutEffect, getTokensPropType } from '../utils';
|
|
10
10
|
import { useThemeTokens } from '../ThemeProvider';
|
|
11
11
|
import Listbox from '../Listbox';
|
|
12
12
|
import Typography from '../Typography';
|
|
@@ -105,13 +105,16 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
105
105
|
value,
|
|
106
106
|
helpText = '',
|
|
107
107
|
loadingLabel,
|
|
108
|
+
tokens,
|
|
108
109
|
...rest
|
|
109
110
|
} = _ref2;
|
|
110
111
|
const {
|
|
111
|
-
color: resultsTextColor
|
|
112
|
-
|
|
112
|
+
color: resultsTextColor,
|
|
113
|
+
...restOfTokens
|
|
114
|
+
} = useThemeTokens('Autocomplete', tokens, {
|
|
113
115
|
focus: true
|
|
114
116
|
});
|
|
117
|
+
|
|
115
118
|
// The wrapped input is mostly responsible for controlled vs uncontrolled handling,
|
|
116
119
|
// but we also need to adjust suggestions based on the mode:
|
|
117
120
|
// - in controlled mode we rely entirely on the suggestions passed via the `items` prop,
|
|
@@ -371,6 +374,7 @@ const Autocomplete = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
371
374
|
minWidth: fullWidth ? inputWidth : MIN_LISTBOX_WIDTH,
|
|
372
375
|
maxWidth: inputWidth,
|
|
373
376
|
onLayout: handleMeasure,
|
|
377
|
+
tokens: restOfTokens,
|
|
374
378
|
ref: openOverlayRef,
|
|
375
379
|
children: isLoading ? /*#__PURE__*/_jsx(Loading, {
|
|
376
380
|
label: loadingLabel ?? getCopy('loading')
|
|
@@ -423,6 +427,7 @@ Autocomplete.propTypes = {
|
|
|
423
427
|
* </Autocomplete>
|
|
424
428
|
*/
|
|
425
429
|
children: PropTypes.func,
|
|
430
|
+
tokens: getTokensPropType('Autocomplete'),
|
|
426
431
|
/**
|
|
427
432
|
* Copy language identifier
|
|
428
433
|
*/
|
package/lib/Card/CardBase.js
CHANGED
|
@@ -25,7 +25,9 @@ const selectStyles = _ref => {
|
|
|
25
25
|
minWidth,
|
|
26
26
|
shadow,
|
|
27
27
|
backgroundGradient,
|
|
28
|
-
gradient
|
|
28
|
+
gradient,
|
|
29
|
+
maxHeight,
|
|
30
|
+
overflowY
|
|
29
31
|
} = _ref;
|
|
30
32
|
return {
|
|
31
33
|
flex,
|
|
@@ -47,6 +49,10 @@ const selectStyles = _ref => {
|
|
|
47
49
|
} : {}),
|
|
48
50
|
...(backgroundGradient && Platform.OS === 'web' ? {
|
|
49
51
|
backgroundImage: `linear-gradient(${backgroundGradient.angle}deg, ${backgroundGradient.stops[0].color}, ${backgroundGradient.stops[1].color})`
|
|
52
|
+
} : {}),
|
|
53
|
+
...(Platform.OS === 'web' ? {
|
|
54
|
+
maxHeight,
|
|
55
|
+
overflowY
|
|
50
56
|
} : {})
|
|
51
57
|
};
|
|
52
58
|
};
|
|
@@ -11,7 +11,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, foc
|
|
|
11
11
|
const tokenKeys = ['flex', 'backgroundColor', 'borderColor', 'gradient', 'borderRadius', 'borderWidth', 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'minWidth', 'shadow', 'contentAlignItems', 'contentJustifyContent', 'contentFlexGrow', 'contentFlexShrink',
|
|
12
12
|
// Outer border tokens. TODO: centralise common token sets like these as part of
|
|
13
13
|
// https://github.com/telus/universal-design-system/issues/782
|
|
14
|
-
'outerBorderColor', 'outerBorderWidth', 'outerBorderGap'];
|
|
14
|
+
'outerBorderColor', 'outerBorderWidth', 'outerBorderGap', 'icon', 'iconBackgroundColor', 'iconColor', 'iconSize', 'inputBackgroundColor', 'inputBorderColor', 'inputBorderRadius', 'inputBorderWidth', 'inputHeight', 'inputShadow', 'inputWidth'];
|
|
15
15
|
export const selectPressableCardTokens = tokens => Object.fromEntries(tokenKeys.map(key => [key, tokens[key]]));
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -73,6 +73,8 @@ const selectComponentTokens = (tokens, componentIdentifier) => {
|
|
|
73
73
|
* @param {boolean} props.allowMultipleFiles - Whether multiple files can be uploaded.
|
|
74
74
|
* @param {number} props.maxFileSize - The maximum file size in megabytes.
|
|
75
75
|
* @param {number} props.maxFilesCount - The maximum number of files that can be uploaded.
|
|
76
|
+
* @param {number} props.minFilesCount - The minimum number of files that can be uploaded.
|
|
77
|
+
* @param {number} props.minFileSize - The minimum file size in megabytes.
|
|
76
78
|
* @param {Function} props.onUpload - The callback function for file upload.
|
|
77
79
|
* @param {Function} props.onDelete - The callback function for file deletion.
|
|
78
80
|
* @param {Object} props.documentPicker - The document picker object.
|
|
@@ -88,11 +90,16 @@ const FileUpload = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
88
90
|
allowMultipleFiles = false,
|
|
89
91
|
maxFileSize,
|
|
90
92
|
maxFilesCount = 1,
|
|
93
|
+
minFilesCount = 1,
|
|
94
|
+
minFileSize = 0,
|
|
91
95
|
onUpload,
|
|
92
96
|
onDelete,
|
|
93
97
|
documentPicker,
|
|
94
98
|
...rest
|
|
95
99
|
} = _ref2;
|
|
100
|
+
if (minFilesCount <= 0) {
|
|
101
|
+
throw new Error('minFilesCount should be greater than 0');
|
|
102
|
+
}
|
|
96
103
|
const themeTokens = useThemeTokens('FileUpload', tokens, variant);
|
|
97
104
|
const getCopy = useCopy({
|
|
98
105
|
dictionary,
|
|
@@ -104,9 +111,12 @@ const FileUpload = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
104
111
|
React.useEffect(() => {
|
|
105
112
|
setRenderTrigger(prev => prev + 1);
|
|
106
113
|
}, [filesStatus]);
|
|
107
|
-
const areFileTypesExtensionsValid = files =>
|
|
108
|
-
const
|
|
109
|
-
|
|
114
|
+
const areFileTypesExtensionsValid = files => {
|
|
115
|
+
const invalidFiles = files.filter(file => {
|
|
116
|
+
const fileExtension = file.name.split('.').pop().toLowerCase();
|
|
117
|
+
return !fileTypes.includes(fileExtension);
|
|
118
|
+
});
|
|
119
|
+
if (invalidFiles.length > 0) {
|
|
110
120
|
setFilesStatus([{
|
|
111
121
|
description: getCopy('wrongFileType').replace('%{fileType}', stringifyFileTypesList(fileTypes, getCopy)),
|
|
112
122
|
style: 'error'
|
|
@@ -114,26 +124,46 @@ const FileUpload = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
114
124
|
return false;
|
|
115
125
|
}
|
|
116
126
|
return true;
|
|
117
|
-
}
|
|
118
|
-
const areFileSizesWithinLimit = files =>
|
|
119
|
-
|
|
127
|
+
};
|
|
128
|
+
const areFileSizesWithinLimit = files => {
|
|
129
|
+
const invalidFiles = files.filter(file => {
|
|
130
|
+
const fileSize = file.size;
|
|
131
|
+
return fileSize < convertFromMegaByteToByte(minFileSize) || fileSize > convertFromMegaByteToByte(maxFileSize);
|
|
132
|
+
});
|
|
133
|
+
if (invalidFiles.length > 0) {
|
|
134
|
+
const invalidFile = invalidFiles[0];
|
|
135
|
+
const fileSize = invalidFile.size;
|
|
136
|
+
if (fileSize < convertFromMegaByteToByte(minFileSize)) {
|
|
137
|
+
setFilesStatus([{
|
|
138
|
+
description: getCopy('fileTooSmall').replace('%{size}', minFileSize),
|
|
139
|
+
style: 'error'
|
|
140
|
+
}]);
|
|
141
|
+
} else {
|
|
142
|
+
setFilesStatus([{
|
|
143
|
+
description: getCopy('fileTooBig').replace('%{size}', maxFileSize),
|
|
144
|
+
style: 'error'
|
|
145
|
+
}]);
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
};
|
|
151
|
+
const areFilesCountWithinLimit = files => {
|
|
152
|
+
if (maxFilesCount && files.length > maxFilesCount) {
|
|
120
153
|
setFilesStatus([{
|
|
121
|
-
description: getCopy('
|
|
154
|
+
description: getCopy('tooManyFiles').replace('%{maxFilesCount}', maxFilesCount),
|
|
122
155
|
style: 'error'
|
|
123
156
|
}]);
|
|
124
157
|
return false;
|
|
125
158
|
}
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
const areMoreFilesUploadedThanAllowed = files => {
|
|
129
|
-
if (files.length > maxFilesCount) {
|
|
159
|
+
if (minFilesCount && files.length < minFilesCount) {
|
|
130
160
|
setFilesStatus([{
|
|
131
|
-
description: getCopy('
|
|
161
|
+
description: getCopy('fewFiles').replace('%{minFilesCount}', minFilesCount),
|
|
132
162
|
style: 'error'
|
|
133
163
|
}]);
|
|
134
|
-
return
|
|
164
|
+
return false;
|
|
135
165
|
}
|
|
136
|
-
return
|
|
166
|
+
return true;
|
|
137
167
|
};
|
|
138
168
|
const handleFilesUpload = async files => {
|
|
139
169
|
try {
|
|
@@ -169,11 +199,8 @@ const FileUpload = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
169
199
|
}
|
|
170
200
|
};
|
|
171
201
|
const handleFileDeletion = async file => {
|
|
172
|
-
const copyFile = {
|
|
173
|
-
...file
|
|
174
|
-
};
|
|
175
202
|
try {
|
|
176
|
-
const result = await onDelete(
|
|
203
|
+
const result = await onDelete(file);
|
|
177
204
|
setFilesStatus(prevStatus => {
|
|
178
205
|
const filteredFilesStatus = prevStatus.filter(f => f.file && f.file.name !== file.name);
|
|
179
206
|
return result.error ? [...filteredFilesStatus, {
|
|
@@ -212,7 +239,7 @@ const FileUpload = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
212
239
|
if (files.length === 0) return;
|
|
213
240
|
if (fileTypes && !areFileTypesExtensionsValid(files)) return;
|
|
214
241
|
if (maxFileSize && !areFileSizesWithinLimit(files)) return;
|
|
215
|
-
if (maxFilesCount &&
|
|
242
|
+
if ((maxFilesCount || minFilesCount) && !areFilesCountWithinLimit(files)) return;
|
|
216
243
|
await handleFilesUpload(files);
|
|
217
244
|
if (inputFileRef.current) inputFileRef.current.value = '';
|
|
218
245
|
};
|
|
@@ -225,8 +252,8 @@ const FileUpload = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
|
225
252
|
multiple: allowMultipleFiles
|
|
226
253
|
});
|
|
227
254
|
if (result) {
|
|
228
|
-
|
|
229
|
-
|
|
255
|
+
if (result.canceled) return;
|
|
256
|
+
const files = Array.isArray(result.assets) ? result.assets : [result.assets];
|
|
230
257
|
const event = {
|
|
231
258
|
target: {
|
|
232
259
|
files
|
|
@@ -323,6 +350,14 @@ FileUpload.propTypes = {
|
|
|
323
350
|
/**
|
|
324
351
|
* The document picker to use for mobile
|
|
325
352
|
*/
|
|
326
|
-
documentPicker: PropTypes.object
|
|
353
|
+
documentPicker: PropTypes.object,
|
|
354
|
+
/**
|
|
355
|
+
* The minimum number of files allowed for upload.
|
|
356
|
+
*/
|
|
357
|
+
minFilesCount: PropTypes.number,
|
|
358
|
+
/**
|
|
359
|
+
* The minimum file size allowed for upload in MB.
|
|
360
|
+
*/
|
|
361
|
+
minFileSize: PropTypes.number
|
|
327
362
|
};
|
|
328
363
|
export default FileUpload;
|
|
@@ -16,7 +16,9 @@ export default {
|
|
|
16
16
|
uploadSuccess: 'uploaded successfully.',
|
|
17
17
|
uploadError: 'was not uploaded due to',
|
|
18
18
|
deleteProblem: 'was not deleted due to',
|
|
19
|
-
tooManyFiles: 'You can only select up to %{maxFilesCount} files at the same time.'
|
|
19
|
+
tooManyFiles: 'You can only select up to %{maxFilesCount} files at the same time.',
|
|
20
|
+
fileTooSmall: 'The selected file must be at least %{size}MB.',
|
|
21
|
+
fewFiles: 'You must select at least %{minFilesCount} files.'
|
|
20
22
|
},
|
|
21
23
|
fr: {
|
|
22
24
|
label: 'Téléverser les fichiers"',
|
|
@@ -35,6 +37,8 @@ export default {
|
|
|
35
37
|
uploadSuccess: 'téléchargé avec succès.',
|
|
36
38
|
uploadError: `n'a pas été téléchargé en raison de`,
|
|
37
39
|
deleteProblem: `n'a pas été supprimé en raison de`,
|
|
38
|
-
tooManyFiles: `Vous ne pouvez sélectionner que jusqu'à %{maxFilesCount} fichiers en même temps
|
|
40
|
+
tooManyFiles: `Vous ne pouvez sélectionner que jusqu'à %{maxFilesCount} fichiers en même temps.`,
|
|
41
|
+
fileTooSmall: `Le fichier sélectionné doit faire au moins %{size}Mo.`,
|
|
42
|
+
fewFiles: `Vous devez sélectionner au moins %{minFilesCount} fichiers.`
|
|
39
43
|
}
|
|
40
44
|
};
|
|
@@ -29,7 +29,8 @@ const DropdownOverlay = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
29
29
|
overlaidPosition,
|
|
30
30
|
maxWidth,
|
|
31
31
|
minWidth,
|
|
32
|
-
onLayout
|
|
32
|
+
onLayout,
|
|
33
|
+
tokens
|
|
33
34
|
} = _ref;
|
|
34
35
|
const systemTokens = useThemeTokens('Listbox', {}, {});
|
|
35
36
|
return /*#__PURE__*/_jsx(View, {
|
|
@@ -45,7 +46,8 @@ const DropdownOverlay = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
45
46
|
paddingBottom: paddingVertical,
|
|
46
47
|
paddingTop: paddingVertical,
|
|
47
48
|
paddingLeft: paddingHorizontal,
|
|
48
|
-
paddingRight: paddingHorizontal
|
|
49
|
+
paddingRight: paddingHorizontal,
|
|
50
|
+
...tokens
|
|
49
51
|
},
|
|
50
52
|
children: children
|
|
51
53
|
})
|
|
@@ -72,6 +74,7 @@ DropdownOverlay.propTypes = {
|
|
|
72
74
|
}),
|
|
73
75
|
maxWidth: PropTypes.number,
|
|
74
76
|
minWidth: PropTypes.number,
|
|
75
|
-
onLayout: PropTypes.func
|
|
77
|
+
onLayout: PropTypes.func,
|
|
78
|
+
tokens: PropTypes.object
|
|
76
79
|
};
|
|
77
80
|
export default Platform.OS === 'web' ? withPortal(DropdownOverlay) : DropdownOverlay;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
+
import Platform from "react-native-web/dist/exports/Platform";
|
|
3
4
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
4
5
|
import Text from "react-native-web/dist/exports/Text";
|
|
5
6
|
import View from "react-native-web/dist/exports/View";
|
|
@@ -108,7 +109,7 @@ const StepTracker = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
|
|
|
108
109
|
dictionary,
|
|
109
110
|
copy
|
|
110
111
|
});
|
|
111
|
-
const stepTrackerLabel = showStepTrackerLabel ? (typeof copy === 'string' ? getCopy(textStepTrackerLabel ?? 1).stepTrackerLabel : getCopy('stepTrackerLabel')).replace('%{stepNumber}', current < steps.length ? current + 1 : steps.length).replace('%{stepCount}', steps.length).replace('%{stepLabel}', current < steps.length ? steps[current] : steps[steps.length - 1]) : '';
|
|
112
|
+
const stepTrackerLabel = showStepTrackerLabel ? (typeof copy === 'string' ? getCopy(textStepTrackerLabel ?? 1).stepTrackerLabel : getCopy('stepTrackerLabel')).replace('%{stepNumber}', current < steps.length ? current + 1 : steps.length).replace('%{stepCount}', steps.length).replace('%{stepLabel}', current < steps.length ? steps[current] : steps[steps.length - 1]) : getCopy('stepTrackerLabel');
|
|
112
113
|
const getStepLabel = index => {
|
|
113
114
|
if (themeTokens.showStepLabel) {
|
|
114
115
|
var _getCopy;
|
|
@@ -122,7 +123,6 @@ const StepTracker = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
|
|
|
122
123
|
if (!steps.length) return null;
|
|
123
124
|
const selectedProps = selectProps({
|
|
124
125
|
accessibilityLabel: stepTrackerLabel,
|
|
125
|
-
accessibilityLevel: 1,
|
|
126
126
|
accessibilityRole: 'progressbar',
|
|
127
127
|
accessibilityValue: {
|
|
128
128
|
min: 0,
|
|
@@ -132,6 +132,8 @@ const StepTracker = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
|
|
|
132
132
|
},
|
|
133
133
|
...rest
|
|
134
134
|
});
|
|
135
|
+
const stepsContainerAccessibilityRole = Platform.OS === 'web' ? 'list' : 'tablist';
|
|
136
|
+
const stepAccessibilityRole = Platform.OS === 'web' ? 'listitem' : 'tab';
|
|
135
137
|
return /*#__PURE__*/_jsx(View, {
|
|
136
138
|
ref: ref,
|
|
137
139
|
style: selectContainerStyles(themeTokens),
|
|
@@ -140,6 +142,7 @@ const StepTracker = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
|
|
|
140
142
|
space: 0,
|
|
141
143
|
children: [/*#__PURE__*/_jsx(View, {
|
|
142
144
|
style: staticStyles.stepsContainer,
|
|
145
|
+
accessibilityRole: stepsContainerAccessibilityRole,
|
|
143
146
|
children: steps.map((label, index) => {
|
|
144
147
|
return /*#__PURE__*/_jsx(Step, {
|
|
145
148
|
status: current,
|
|
@@ -147,13 +150,17 @@ const StepTracker = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
|
|
|
147
150
|
name: getStepLabel(index),
|
|
148
151
|
stepIndex: index,
|
|
149
152
|
stepCount: steps.length,
|
|
150
|
-
tokens: themeTokens
|
|
153
|
+
tokens: themeTokens,
|
|
154
|
+
accessibilityRole: stepAccessibilityRole,
|
|
155
|
+
accessibilityCurrent: current === index && Platform.OS === 'web' && 'step'
|
|
151
156
|
}, label);
|
|
152
157
|
})
|
|
153
158
|
}), showStepTrackerLabel && /*#__PURE__*/_jsx(View, {
|
|
154
159
|
style: [staticStyles.stepTrackerLabelContainer, selectStepTrackerLabelContainerStyles(themeTokens)],
|
|
155
160
|
children: /*#__PURE__*/_jsx(Text, {
|
|
156
161
|
style: selectStepTrackerLabelStyles(themeTokens, themeOptions),
|
|
162
|
+
accessibilityRole: "header",
|
|
163
|
+
accessibilityLevel: 2,
|
|
157
164
|
children: stepTrackerLabel
|
|
158
165
|
})
|
|
159
166
|
})]
|
package/package.json
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"@gorhom/portal": "^1.0.14",
|
|
14
14
|
"@react-native-picker/picker": "^2.9.0",
|
|
15
15
|
"@telus-uds/system-constants": "^2.0.0",
|
|
16
|
-
"@telus-uds/system-theme-tokens": "^3.
|
|
16
|
+
"@telus-uds/system-theme-tokens": "^3.2.0",
|
|
17
17
|
"airbnb-prop-types": "^2.16.0",
|
|
18
18
|
"css-mediaquery": "^0.1.2",
|
|
19
|
-
"expo-document-picker": "
|
|
19
|
+
"expo-document-picker": "^13.0.1",
|
|
20
20
|
"expo-linear-gradient": "^12.5.0",
|
|
21
21
|
"expo-modules-core": "^1.12.24",
|
|
22
22
|
"lodash.debounce": "^4.0.8",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"standard-engine": {
|
|
82
82
|
"skip": true
|
|
83
83
|
},
|
|
84
|
-
"version": "2.
|
|
84
|
+
"version": "2.3.0",
|
|
85
85
|
"types": "types/index.d.ts"
|
|
86
86
|
}
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
useCopy,
|
|
12
12
|
htmlAttrs,
|
|
13
13
|
useOverlaidPosition,
|
|
14
|
-
useSafeLayoutEffect
|
|
14
|
+
useSafeLayoutEffect,
|
|
15
|
+
getTokensPropType
|
|
15
16
|
} from '../utils'
|
|
16
17
|
import { useThemeTokens } from '../ThemeProvider'
|
|
17
18
|
import Listbox from '../Listbox'
|
|
@@ -103,11 +104,15 @@ const Autocomplete = React.forwardRef(
|
|
|
103
104
|
value,
|
|
104
105
|
helpText = '',
|
|
105
106
|
loadingLabel,
|
|
107
|
+
tokens,
|
|
106
108
|
...rest
|
|
107
109
|
},
|
|
108
110
|
ref
|
|
109
111
|
) => {
|
|
110
|
-
const { color: resultsTextColor } = useThemeTokens('
|
|
112
|
+
const { color: resultsTextColor, ...restOfTokens } = useThemeTokens('Autocomplete', tokens, {
|
|
113
|
+
focus: true
|
|
114
|
+
})
|
|
115
|
+
|
|
111
116
|
// The wrapped input is mostly responsible for controlled vs uncontrolled handling,
|
|
112
117
|
// but we also need to adjust suggestions based on the mode:
|
|
113
118
|
// - in controlled mode we rely entirely on the suggestions passed via the `items` prop,
|
|
@@ -371,6 +376,7 @@ const Autocomplete = React.forwardRef(
|
|
|
371
376
|
minWidth={fullWidth ? inputWidth : MIN_LISTBOX_WIDTH}
|
|
372
377
|
maxWidth={inputWidth}
|
|
373
378
|
onLayout={handleMeasure}
|
|
379
|
+
tokens={restOfTokens}
|
|
374
380
|
ref={openOverlayRef}
|
|
375
381
|
>
|
|
376
382
|
{isLoading ? (
|
|
@@ -433,6 +439,7 @@ Autocomplete.propTypes = {
|
|
|
433
439
|
* </Autocomplete>
|
|
434
440
|
*/
|
|
435
441
|
children: PropTypes.func,
|
|
442
|
+
tokens: getTokensPropType('Autocomplete'),
|
|
436
443
|
/**
|
|
437
444
|
* Copy language identifier
|
|
438
445
|
*/
|
package/src/Card/CardBase.jsx
CHANGED
|
@@ -22,7 +22,9 @@ const selectStyles = ({
|
|
|
22
22
|
minWidth,
|
|
23
23
|
shadow,
|
|
24
24
|
backgroundGradient,
|
|
25
|
-
gradient
|
|
25
|
+
gradient,
|
|
26
|
+
maxHeight,
|
|
27
|
+
overflowY
|
|
26
28
|
}) => {
|
|
27
29
|
return {
|
|
28
30
|
flex,
|
|
@@ -48,7 +50,8 @@ const selectStyles = ({
|
|
|
48
50
|
? {
|
|
49
51
|
backgroundImage: `linear-gradient(${backgroundGradient.angle}deg, ${backgroundGradient.stops[0].color}, ${backgroundGradient.stops[1].color})`
|
|
50
52
|
}
|
|
51
|
-
: {})
|
|
53
|
+
: {}),
|
|
54
|
+
...(Platform.OS === 'web' ? { maxHeight, overflowY } : {})
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -48,7 +48,18 @@ const tokenKeys = [
|
|
|
48
48
|
// https://github.com/telus/universal-design-system/issues/782
|
|
49
49
|
'outerBorderColor',
|
|
50
50
|
'outerBorderWidth',
|
|
51
|
-
'outerBorderGap'
|
|
51
|
+
'outerBorderGap',
|
|
52
|
+
'icon',
|
|
53
|
+
'iconBackgroundColor',
|
|
54
|
+
'iconColor',
|
|
55
|
+
'iconSize',
|
|
56
|
+
'inputBackgroundColor',
|
|
57
|
+
'inputBorderColor',
|
|
58
|
+
'inputBorderRadius',
|
|
59
|
+
'inputBorderWidth',
|
|
60
|
+
'inputHeight',
|
|
61
|
+
'inputShadow',
|
|
62
|
+
'inputWidth'
|
|
52
63
|
]
|
|
53
64
|
export const selectPressableCardTokens = (tokens) =>
|
|
54
65
|
Object.fromEntries(tokenKeys.map((key) => [key, tokens[key]]))
|
|
@@ -90,6 +90,8 @@ const selectComponentTokens = (tokens, componentIdentifier) => {
|
|
|
90
90
|
* @param {boolean} props.allowMultipleFiles - Whether multiple files can be uploaded.
|
|
91
91
|
* @param {number} props.maxFileSize - The maximum file size in megabytes.
|
|
92
92
|
* @param {number} props.maxFilesCount - The maximum number of files that can be uploaded.
|
|
93
|
+
* @param {number} props.minFilesCount - The minimum number of files that can be uploaded.
|
|
94
|
+
* @param {number} props.minFileSize - The minimum file size in megabytes.
|
|
93
95
|
* @param {Function} props.onUpload - The callback function for file upload.
|
|
94
96
|
* @param {Function} props.onDelete - The callback function for file deletion.
|
|
95
97
|
* @param {Object} props.documentPicker - The document picker object.
|
|
@@ -106,6 +108,8 @@ const FileUpload = React.forwardRef(
|
|
|
106
108
|
allowMultipleFiles = false,
|
|
107
109
|
maxFileSize,
|
|
108
110
|
maxFilesCount = 1,
|
|
111
|
+
minFilesCount = 1,
|
|
112
|
+
minFileSize = 0,
|
|
109
113
|
onUpload,
|
|
110
114
|
onDelete,
|
|
111
115
|
documentPicker,
|
|
@@ -113,6 +117,10 @@ const FileUpload = React.forwardRef(
|
|
|
113
117
|
},
|
|
114
118
|
ref
|
|
115
119
|
) => {
|
|
120
|
+
if (minFilesCount <= 0) {
|
|
121
|
+
throw new Error('minFilesCount should be greater than 0')
|
|
122
|
+
}
|
|
123
|
+
|
|
116
124
|
const themeTokens = useThemeTokens('FileUpload', tokens, variant)
|
|
117
125
|
const getCopy = useCopy({ dictionary, copy })
|
|
118
126
|
|
|
@@ -125,50 +133,78 @@ const FileUpload = React.forwardRef(
|
|
|
125
133
|
setRenderTrigger((prev) => prev + 1)
|
|
126
134
|
}, [filesStatus])
|
|
127
135
|
|
|
128
|
-
const areFileTypesExtensionsValid = (files) =>
|
|
129
|
-
files.
|
|
136
|
+
const areFileTypesExtensionsValid = (files) => {
|
|
137
|
+
const invalidFiles = files.filter((file) => {
|
|
130
138
|
const fileExtension = file.name.split('.').pop().toLowerCase()
|
|
139
|
+
return !fileTypes.includes(fileExtension)
|
|
140
|
+
})
|
|
141
|
+
if (invalidFiles.length > 0) {
|
|
142
|
+
setFilesStatus([
|
|
143
|
+
{
|
|
144
|
+
description: getCopy('wrongFileType').replace(
|
|
145
|
+
'%{fileType}',
|
|
146
|
+
stringifyFileTypesList(fileTypes, getCopy)
|
|
147
|
+
),
|
|
148
|
+
style: 'error'
|
|
149
|
+
}
|
|
150
|
+
])
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
const areFileSizesWithinLimit = (files) => {
|
|
157
|
+
const invalidFiles = files.filter((file) => {
|
|
158
|
+
const fileSize = file.size
|
|
159
|
+
return (
|
|
160
|
+
fileSize < convertFromMegaByteToByte(minFileSize) ||
|
|
161
|
+
fileSize > convertFromMegaByteToByte(maxFileSize)
|
|
162
|
+
)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
if (invalidFiles.length > 0) {
|
|
166
|
+
const invalidFile = invalidFiles[0]
|
|
167
|
+
const fileSize = invalidFile.size
|
|
168
|
+
if (fileSize < convertFromMegaByteToByte(minFileSize)) {
|
|
133
169
|
setFilesStatus([
|
|
134
170
|
{
|
|
135
|
-
description: getCopy('
|
|
136
|
-
'%{fileType}',
|
|
137
|
-
stringifyFileTypesList(fileTypes, getCopy)
|
|
138
|
-
),
|
|
171
|
+
description: getCopy('fileTooSmall').replace('%{size}', minFileSize),
|
|
139
172
|
style: 'error'
|
|
140
173
|
}
|
|
141
174
|
])
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
return true
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
const areFileSizesWithinLimit = (files) =>
|
|
148
|
-
files.some((file) => {
|
|
149
|
-
if (file.size > convertFromMegaByteToByte(maxFileSize)) {
|
|
175
|
+
} else {
|
|
150
176
|
setFilesStatus([
|
|
151
177
|
{
|
|
152
178
|
description: getCopy('fileTooBig').replace('%{size}', maxFileSize),
|
|
153
179
|
style: 'error'
|
|
154
180
|
}
|
|
155
181
|
])
|
|
156
|
-
return false
|
|
157
182
|
}
|
|
158
|
-
return
|
|
159
|
-
}
|
|
183
|
+
return false
|
|
184
|
+
}
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
160
187
|
|
|
161
|
-
const
|
|
162
|
-
if (files.length > maxFilesCount) {
|
|
188
|
+
const areFilesCountWithinLimit = (files) => {
|
|
189
|
+
if (maxFilesCount && files.length > maxFilesCount) {
|
|
163
190
|
setFilesStatus([
|
|
164
191
|
{
|
|
165
192
|
description: getCopy('tooManyFiles').replace('%{maxFilesCount}', maxFilesCount),
|
|
166
193
|
style: 'error'
|
|
167
194
|
}
|
|
168
195
|
])
|
|
169
|
-
return
|
|
196
|
+
return false
|
|
170
197
|
}
|
|
171
|
-
|
|
198
|
+
if (minFilesCount && files.length < minFilesCount) {
|
|
199
|
+
setFilesStatus([
|
|
200
|
+
{
|
|
201
|
+
description: getCopy('fewFiles').replace('%{minFilesCount}', minFilesCount),
|
|
202
|
+
style: 'error'
|
|
203
|
+
}
|
|
204
|
+
])
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
return true
|
|
172
208
|
}
|
|
173
209
|
|
|
174
210
|
const handleFilesUpload = async (files) => {
|
|
@@ -212,9 +248,8 @@ const FileUpload = React.forwardRef(
|
|
|
212
248
|
}
|
|
213
249
|
|
|
214
250
|
const handleFileDeletion = async (file) => {
|
|
215
|
-
const copyFile = { ...file }
|
|
216
251
|
try {
|
|
217
|
-
const result = await onDelete(
|
|
252
|
+
const result = await onDelete(file)
|
|
218
253
|
setFilesStatus((prevStatus) => {
|
|
219
254
|
const filteredFilesStatus = prevStatus.filter((f) => f.file && f.file.name !== file.name)
|
|
220
255
|
return result.error
|
|
@@ -269,7 +304,7 @@ const FileUpload = React.forwardRef(
|
|
|
269
304
|
|
|
270
305
|
if (maxFileSize && !areFileSizesWithinLimit(files)) return
|
|
271
306
|
|
|
272
|
-
if (maxFilesCount &&
|
|
307
|
+
if ((maxFilesCount || minFilesCount) && !areFilesCountWithinLimit(files)) return
|
|
273
308
|
|
|
274
309
|
await handleFilesUpload(files)
|
|
275
310
|
|
|
@@ -285,8 +320,8 @@ const FileUpload = React.forwardRef(
|
|
|
285
320
|
multiple: allowMultipleFiles
|
|
286
321
|
})
|
|
287
322
|
if (result) {
|
|
288
|
-
|
|
289
|
-
|
|
323
|
+
if (result.canceled) return
|
|
324
|
+
const files = Array.isArray(result.assets) ? result.assets : [result.assets]
|
|
290
325
|
const event = { target: { files } }
|
|
291
326
|
await handleOnChange(event)
|
|
292
327
|
}
|
|
@@ -390,7 +425,15 @@ FileUpload.propTypes = {
|
|
|
390
425
|
/**
|
|
391
426
|
* The document picker to use for mobile
|
|
392
427
|
*/
|
|
393
|
-
documentPicker: PropTypes.object
|
|
428
|
+
documentPicker: PropTypes.object,
|
|
429
|
+
/**
|
|
430
|
+
* The minimum number of files allowed for upload.
|
|
431
|
+
*/
|
|
432
|
+
minFilesCount: PropTypes.number,
|
|
433
|
+
/**
|
|
434
|
+
* The minimum file size allowed for upload in MB.
|
|
435
|
+
*/
|
|
436
|
+
minFileSize: PropTypes.number
|
|
394
437
|
}
|
|
395
438
|
|
|
396
439
|
export default FileUpload
|
|
@@ -16,7 +16,9 @@ export default {
|
|
|
16
16
|
uploadSuccess: 'uploaded successfully.',
|
|
17
17
|
uploadError: 'was not uploaded due to',
|
|
18
18
|
deleteProblem: 'was not deleted due to',
|
|
19
|
-
tooManyFiles: 'You can only select up to %{maxFilesCount} files at the same time.'
|
|
19
|
+
tooManyFiles: 'You can only select up to %{maxFilesCount} files at the same time.',
|
|
20
|
+
fileTooSmall: 'The selected file must be at least %{size}MB.',
|
|
21
|
+
fewFiles: 'You must select at least %{minFilesCount} files.'
|
|
20
22
|
},
|
|
21
23
|
fr: {
|
|
22
24
|
label: 'Téléverser les fichiers"',
|
|
@@ -35,6 +37,8 @@ export default {
|
|
|
35
37
|
uploadSuccess: 'téléchargé avec succès.',
|
|
36
38
|
uploadError: `n'a pas été téléchargé en raison de`,
|
|
37
39
|
deleteProblem: `n'a pas été supprimé en raison de`,
|
|
38
|
-
tooManyFiles: `Vous ne pouvez sélectionner que jusqu'à %{maxFilesCount} fichiers en même temps
|
|
40
|
+
tooManyFiles: `Vous ne pouvez sélectionner que jusqu'à %{maxFilesCount} fichiers en même temps.`,
|
|
41
|
+
fileTooSmall: `Le fichier sélectionné doit faire au moins %{size}Mo.`,
|
|
42
|
+
fewFiles: `Vous devez sélectionner au moins %{minFilesCount} fichiers.`
|
|
39
43
|
}
|
|
40
44
|
}
|
|
@@ -22,7 +22,7 @@ const paddingVertical = 0
|
|
|
22
22
|
const paddingHorizontal = 0
|
|
23
23
|
|
|
24
24
|
const DropdownOverlay = React.forwardRef(
|
|
25
|
-
({ children, isReady = false, overlaidPosition, maxWidth, minWidth, onLayout }, ref) => {
|
|
25
|
+
({ children, isReady = false, overlaidPosition, maxWidth, minWidth, onLayout, tokens }, ref) => {
|
|
26
26
|
const systemTokens = useThemeTokens('Listbox', {}, {})
|
|
27
27
|
|
|
28
28
|
return (
|
|
@@ -42,7 +42,8 @@ const DropdownOverlay = React.forwardRef(
|
|
|
42
42
|
paddingBottom: paddingVertical,
|
|
43
43
|
paddingTop: paddingVertical,
|
|
44
44
|
paddingLeft: paddingHorizontal,
|
|
45
|
-
paddingRight: paddingHorizontal
|
|
45
|
+
paddingRight: paddingHorizontal,
|
|
46
|
+
...tokens
|
|
46
47
|
}}
|
|
47
48
|
>
|
|
48
49
|
{children}
|
|
@@ -75,7 +76,8 @@ DropdownOverlay.propTypes = {
|
|
|
75
76
|
}),
|
|
76
77
|
maxWidth: PropTypes.number,
|
|
77
78
|
minWidth: PropTypes.number,
|
|
78
|
-
onLayout: PropTypes.func
|
|
79
|
+
onLayout: PropTypes.func,
|
|
80
|
+
tokens: PropTypes.object
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
export default Platform.OS === 'web' ? withPortal(DropdownOverlay) : DropdownOverlay
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { StyleSheet, Text, View } from 'react-native'
|
|
3
|
+
import { Platform, StyleSheet, Text, View } from 'react-native'
|
|
4
4
|
import StackView from '../StackView'
|
|
5
5
|
import { applyTextStyles, useTheme, useThemeTokens } from '../ThemeProvider'
|
|
6
6
|
import { a11yProps, getTokensPropType, selectSystemProps, variantProp, viewProps } from '../utils'
|
|
@@ -105,7 +105,7 @@ const StepTracker = React.forwardRef(
|
|
|
105
105
|
'%{stepLabel}',
|
|
106
106
|
current < steps.length ? steps[current] : steps[steps.length - 1]
|
|
107
107
|
)
|
|
108
|
-
: ''
|
|
108
|
+
: getCopy('stepTrackerLabel')
|
|
109
109
|
|
|
110
110
|
const getStepLabel = (index) => {
|
|
111
111
|
if (themeTokens.showStepLabel) {
|
|
@@ -118,7 +118,6 @@ const StepTracker = React.forwardRef(
|
|
|
118
118
|
if (!steps.length) return null
|
|
119
119
|
const selectedProps = selectProps({
|
|
120
120
|
accessibilityLabel: stepTrackerLabel,
|
|
121
|
-
accessibilityLevel: 1,
|
|
122
121
|
accessibilityRole: 'progressbar',
|
|
123
122
|
accessibilityValue: {
|
|
124
123
|
min: 0,
|
|
@@ -129,10 +128,16 @@ const StepTracker = React.forwardRef(
|
|
|
129
128
|
...rest
|
|
130
129
|
})
|
|
131
130
|
|
|
131
|
+
const stepsContainerAccessibilityRole = Platform.OS === 'web' ? 'list' : 'tablist'
|
|
132
|
+
const stepAccessibilityRole = Platform.OS === 'web' ? 'listitem' : 'tab'
|
|
133
|
+
|
|
132
134
|
return (
|
|
133
135
|
<View ref={ref} style={selectContainerStyles(themeTokens)} {...selectedProps}>
|
|
134
136
|
<StackView space={0}>
|
|
135
|
-
<View
|
|
137
|
+
<View
|
|
138
|
+
style={staticStyles.stepsContainer}
|
|
139
|
+
accessibilityRole={stepsContainerAccessibilityRole}
|
|
140
|
+
>
|
|
136
141
|
{steps.map((label, index) => {
|
|
137
142
|
return (
|
|
138
143
|
<Step
|
|
@@ -143,6 +148,8 @@ const StepTracker = React.forwardRef(
|
|
|
143
148
|
stepIndex={index}
|
|
144
149
|
stepCount={steps.length}
|
|
145
150
|
tokens={themeTokens}
|
|
151
|
+
accessibilityRole={stepAccessibilityRole}
|
|
152
|
+
accessibilityCurrent={current === index && Platform.OS === 'web' && 'step'}
|
|
146
153
|
/>
|
|
147
154
|
)
|
|
148
155
|
})}
|
|
@@ -154,7 +161,11 @@ const StepTracker = React.forwardRef(
|
|
|
154
161
|
selectStepTrackerLabelContainerStyles(themeTokens)
|
|
155
162
|
]}
|
|
156
163
|
>
|
|
157
|
-
<Text
|
|
164
|
+
<Text
|
|
165
|
+
style={selectStepTrackerLabelStyles(themeTokens, themeOptions)}
|
|
166
|
+
accessibilityRole="header"
|
|
167
|
+
accessibilityLevel={2}
|
|
168
|
+
>
|
|
158
169
|
{stepTrackerLabel}
|
|
159
170
|
</Text>
|
|
160
171
|
</View>
|