@telus-uds/components-base 2.2.0 → 2.4.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 +36 -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/InputLabel/InputLabel.js +36 -2
- package/lib/InputSupports/InputSupports.js +31 -8
- package/lib/InputSupports/dictionary.js +12 -0
- package/lib/InputSupports/useInputSupports.js +12 -3
- package/lib/Link/LinkBase.js +17 -15
- package/lib/Listbox/ListboxOverlay.js +6 -3
- package/lib/Modal/Modal.js +2 -2
- package/lib/MultiSelectFilter/MultiSelectFilter.js +54 -41
- package/lib/Notification/Notification.js +7 -3
- package/lib/Search/Search.js +39 -8
- package/lib/Select/Picker.native.js +8 -4
- package/lib/Select/constants.js +4 -2
- package/lib/StepTracker/StepTracker.js +10 -3
- package/lib/TextInput/TextArea.js +7 -6
- package/lib/TextInput/TextInput.js +7 -6
- package/lib/TextInput/TextInputBase.js +48 -14
- package/lib/utils/props/inputSupportsProps.js +15 -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/InputLabel/InputLabel.jsx +39 -2
- package/src/InputSupports/InputSupports.jsx +33 -7
- package/src/InputSupports/dictionary.js +12 -0
- package/src/InputSupports/useInputSupports.js +24 -3
- package/src/Link/LinkBase.jsx +17 -15
- package/src/Listbox/ListboxOverlay.jsx +5 -3
- package/src/Modal/Modal.jsx +1 -1
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +55 -27
- package/src/Notification/Notification.jsx +9 -3
- package/src/Search/Search.jsx +52 -24
- package/src/Select/Picker.native.jsx +10 -4
- package/src/Select/constants.js +4 -1
- package/src/StepTracker/StepTracker.jsx +16 -5
- package/src/TextInput/TextArea.jsx +12 -5
- package/src/TextInput/TextInput.jsx +13 -6
- package/src/TextInput/TextInputBase.jsx +57 -10
- package/src/utils/props/inputSupportsProps.js +15 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
# Change Log - @telus-uds/components-base
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This log was last generated on Fri, 10 Jan 2025 21:41:25 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## 2.4.0
|
|
8
|
+
|
|
9
|
+
Fri, 10 Jan 2025 21:41:25 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- `Search`: `showLeftIcon` prop added to display the update icon in the left side for the component. (35577399+JoshHC@users.noreply.github.com)
|
|
14
|
+
- `MultiSelectFilter`: add label and button icon tokens (kristina.kirpichnikova@telus.com)
|
|
15
|
+
- Bump @telus-uds/system-theme-tokens to v3.3.0
|
|
16
|
+
|
|
17
|
+
### Patches
|
|
18
|
+
|
|
19
|
+
- `Link`: add missing outer border tokens for mobile devices (guillermo.peitzner@telus.com)
|
|
20
|
+
- `Modal`: add fallback to avoid error when pressing Tab (Mauricio.BatresMontejo@telus.com)
|
|
21
|
+
- `Select`: fix Option not appearing in component when selected on iOS largest text sizes (sergio.ramirez@telus.com)
|
|
22
|
+
- `InputLabel`: add character count to support InputText and TextArea (Mauricio.BatresMontejo@telus.com)
|
|
23
|
+
- `Notification`: add system style variant retrocompatibility (guillermo.peitzner@telus.com)
|
|
24
|
+
- `InputLabel`: static styles of the label modified to fix hint and tooltip misalignment (35577399+JoshHC@users.noreply.github.com)
|
|
25
|
+
|
|
26
|
+
## 2.3.0
|
|
27
|
+
|
|
28
|
+
Thu, 19 Dec 2024 05:02:20 GMT
|
|
29
|
+
|
|
30
|
+
### Minor changes
|
|
31
|
+
|
|
32
|
+
- `Autocomplete`: Enable `tokens` prop to set custom styles for the `maxHeight` and `scroll` behavior. (jaime.tuyuc@telus.com)
|
|
33
|
+
- `FileUpload`: add minFilesCount and minFileSize props (guillermo.peitzner@telus.com)
|
|
34
|
+
- Bump @telus-uds/system-theme-tokens to v3.2.0
|
|
35
|
+
|
|
36
|
+
### Patches
|
|
37
|
+
|
|
38
|
+
- `Card`: Include additional `tokenKeys` to remove warnings. (jaime.tuyuc@telus.com)
|
|
39
|
+
- `StepTracker`: elements must only use supported ARIA attributes (sergio.ramirez@telus.com)
|
|
40
|
+
|
|
7
41
|
## 2.2.0
|
|
8
42
|
|
|
9
|
-
Fri, 06 Dec 2024 02:03
|
|
43
|
+
Fri, 06 Dec 2024 02:06:03 GMT
|
|
10
44
|
|
|
11
45
|
### Minor changes
|
|
12
46
|
|
|
@@ -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
|
};
|
|
@@ -49,6 +49,8 @@ const InputLabel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
49
49
|
tooltip,
|
|
50
50
|
tokens,
|
|
51
51
|
variant,
|
|
52
|
+
characterCount,
|
|
53
|
+
maxCharacterAllowed,
|
|
52
54
|
...rest
|
|
53
55
|
} = _ref3;
|
|
54
56
|
const themeTokens = useThemeTokens('InputLabel', tokens, variant);
|
|
@@ -81,11 +83,26 @@ const InputLabel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
81
83
|
content: tooltip,
|
|
82
84
|
copy: copy
|
|
83
85
|
})
|
|
86
|
+
}), maxCharacterAllowed && isHintInline && /*#__PURE__*/_jsxs(Text, {
|
|
87
|
+
style: [selectHintStyles({
|
|
88
|
+
...themeTokens
|
|
89
|
+
}), staticStyles.characterCountlabel],
|
|
90
|
+
children: [characterCount, "/", maxCharacterAllowed]
|
|
84
91
|
})]
|
|
85
|
-
}), hint && !isHintInline && /*#__PURE__*/_jsx(Text, {
|
|
92
|
+
}), hint && !maxCharacterAllowed && !isHintInline && /*#__PURE__*/_jsx(Text, {
|
|
86
93
|
style: [selectHintStyles(themeTokens), staticStyles.hintBelow],
|
|
87
94
|
nativeID: hintId,
|
|
88
95
|
children: hint
|
|
96
|
+
}), hint && maxCharacterAllowed && !isHintInline && /*#__PURE__*/_jsxs(View, {
|
|
97
|
+
style: staticStyles.container,
|
|
98
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
99
|
+
style: [selectHintStyles(themeTokens), staticStyles.flexHintBelow],
|
|
100
|
+
nativeID: hintId,
|
|
101
|
+
children: hint
|
|
102
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
103
|
+
style: [selectHintStyles(themeTokens), staticStyles.characterCountlabel],
|
|
104
|
+
children: [characterCount, "/", maxCharacterAllowed]
|
|
105
|
+
})]
|
|
89
106
|
})]
|
|
90
107
|
});
|
|
91
108
|
});
|
|
@@ -120,6 +137,14 @@ InputLabel.propTypes = {
|
|
|
120
137
|
* Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
|
|
121
138
|
*/
|
|
122
139
|
tooltip: PropTypes.string,
|
|
140
|
+
/**
|
|
141
|
+
* Current number of characterts of an input text.
|
|
142
|
+
*/
|
|
143
|
+
characterCount: PropTypes.number,
|
|
144
|
+
/**
|
|
145
|
+
* Max number of characters that allows an input text.
|
|
146
|
+
*/
|
|
147
|
+
maxCharacterAllowed: PropTypes.number,
|
|
123
148
|
tokens: getTokensPropType('InputLabel'),
|
|
124
149
|
variant: variantProp.propType
|
|
125
150
|
};
|
|
@@ -129,13 +154,22 @@ const staticStyles = StyleSheet.create({
|
|
|
129
154
|
flexShrink: 1,
|
|
130
155
|
flexDirection: 'row'
|
|
131
156
|
},
|
|
157
|
+
characterCountlabel: {
|
|
158
|
+
marginLeft: 'auto',
|
|
159
|
+
marginTop: 'auto'
|
|
160
|
+
},
|
|
132
161
|
label: {
|
|
133
|
-
flexShrink: 1
|
|
162
|
+
flexShrink: 1,
|
|
163
|
+
alignSelf: 'center'
|
|
134
164
|
},
|
|
135
165
|
hintBelow: {
|
|
136
166
|
flexBasis: '100%',
|
|
137
167
|
flexShrink: 0
|
|
138
168
|
},
|
|
169
|
+
flexHintBelow: {
|
|
170
|
+
flexBasis: '100%',
|
|
171
|
+
flexShrink: 1
|
|
172
|
+
},
|
|
139
173
|
tooltipAlign: {
|
|
140
174
|
alignSelf: 'center',
|
|
141
175
|
justifyContent: 'center'
|
|
@@ -5,7 +5,8 @@ import Feedback from '../Feedback';
|
|
|
5
5
|
import StackView from '../StackView';
|
|
6
6
|
import { useThemeTokens } from '../ThemeProvider';
|
|
7
7
|
import useInputSupports from './useInputSupports';
|
|
8
|
-
import { getTokensPropType } from '../utils';
|
|
8
|
+
import { getTokensPropType, useCopy } from '../utils';
|
|
9
|
+
import dictionary from './dictionary';
|
|
9
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
11
|
const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
11
12
|
let {
|
|
@@ -19,11 +20,19 @@ const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
19
20
|
feedbackProps = {},
|
|
20
21
|
tooltip,
|
|
21
22
|
validation,
|
|
23
|
+
maxCharacterAllowed,
|
|
24
|
+
inputValue,
|
|
22
25
|
nativeID
|
|
23
26
|
} = _ref;
|
|
24
27
|
const {
|
|
25
28
|
space
|
|
26
29
|
} = useThemeTokens('InputSupports');
|
|
30
|
+
const getCopy = useCopy({
|
|
31
|
+
dictionary,
|
|
32
|
+
copy
|
|
33
|
+
});
|
|
34
|
+
const maxCharsReachedErrorMessage = (inputValue === null || inputValue === void 0 ? void 0 : inputValue.length) > maxCharacterAllowed ? getCopy('maxCharsMessage').replace(/%\{charCount\}/g, maxCharacterAllowed) : '';
|
|
35
|
+
const feedbackValidation = (inputValue === null || inputValue === void 0 ? void 0 : inputValue.length) > maxCharacterAllowed ? 'error' : validation;
|
|
27
36
|
const {
|
|
28
37
|
inputId,
|
|
29
38
|
hintId,
|
|
@@ -34,7 +43,10 @@ const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
34
43
|
hint,
|
|
35
44
|
label,
|
|
36
45
|
validation,
|
|
37
|
-
nativeID
|
|
46
|
+
nativeID,
|
|
47
|
+
copy,
|
|
48
|
+
maxCharacterAllowed,
|
|
49
|
+
charactersCount: maxCharacterAllowed - (inputValue === null || inputValue === void 0 ? void 0 : inputValue.length)
|
|
38
50
|
});
|
|
39
51
|
return /*#__PURE__*/_jsxs(StackView, {
|
|
40
52
|
space: space,
|
|
@@ -46,14 +58,17 @@ const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
46
58
|
hintPosition: hintPosition,
|
|
47
59
|
hintId: hintId,
|
|
48
60
|
tooltip: tooltip,
|
|
49
|
-
forId: inputId
|
|
61
|
+
forId: inputId,
|
|
62
|
+
characterCount: inputValue === null || inputValue === void 0 ? void 0 : inputValue.length,
|
|
63
|
+
maxCharacterAllowed: maxCharacterAllowed
|
|
50
64
|
}), typeof children === 'function' ? children({
|
|
51
65
|
inputId,
|
|
52
|
-
...a11yProps
|
|
53
|
-
|
|
66
|
+
...a11yProps,
|
|
67
|
+
validation: feedbackValidation
|
|
68
|
+
}) : children, feedback || maxCharsReachedErrorMessage ? /*#__PURE__*/_jsx(Feedback, {
|
|
54
69
|
id: feedbackId,
|
|
55
|
-
title: feedback,
|
|
56
|
-
validation:
|
|
70
|
+
title: feedback || maxCharsReachedErrorMessage,
|
|
71
|
+
validation: feedbackValidation,
|
|
57
72
|
tokens: feedbackTokens,
|
|
58
73
|
variant: {
|
|
59
74
|
icon: feedbackProps.showFeedbackIcon
|
|
@@ -105,6 +120,14 @@ InputSupports.propTypes = {
|
|
|
105
120
|
/**
|
|
106
121
|
* ID for DOM element on web
|
|
107
122
|
*/
|
|
108
|
-
nativeID: PropTypes.string
|
|
123
|
+
nativeID: PropTypes.string,
|
|
124
|
+
/**
|
|
125
|
+
* The text value of a TextArea or TextInput
|
|
126
|
+
*/
|
|
127
|
+
inputValue: PropTypes.string,
|
|
128
|
+
/**
|
|
129
|
+
* Max number of characters that allows an input text.
|
|
130
|
+
*/
|
|
131
|
+
maxCharacterAllowed: PropTypes.number
|
|
109
132
|
};
|
|
110
133
|
export default InputSupports;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
en: {
|
|
3
|
+
maxCharacters: 'Maximum %{charCount} characters',
|
|
4
|
+
charactersRemaining: '%{charCount} characters remaining',
|
|
5
|
+
maxCharsMessage: 'Must not exceed %{charCount} characters'
|
|
6
|
+
},
|
|
7
|
+
fr: {
|
|
8
|
+
maxCharacters: '%{charCount} caractères maximum',
|
|
9
|
+
charactersRemaining: '%{charCount} caractères restants',
|
|
10
|
+
maxCharsMessage: 'Ne doit pas dépasser %{charCount} caractères'
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import useUniqueId from '../utils/useUniqueId';
|
|
2
|
+
import { useCopy } from '../utils';
|
|
3
|
+
import dictionary from './dictionary';
|
|
2
4
|
const joinDefined = array => array.filter(item => item !== undefined).join(' ');
|
|
3
5
|
const useInputSupports = _ref => {
|
|
4
6
|
let {
|
|
@@ -6,19 +8,26 @@ const useInputSupports = _ref => {
|
|
|
6
8
|
feedback,
|
|
7
9
|
validation,
|
|
8
10
|
hint,
|
|
9
|
-
nativeID
|
|
11
|
+
nativeID,
|
|
12
|
+
copy,
|
|
13
|
+
maxCharacterAllowed,
|
|
14
|
+
charactersCount
|
|
10
15
|
} = _ref;
|
|
16
|
+
const getCopy = useCopy({
|
|
17
|
+
dictionary,
|
|
18
|
+
copy
|
|
19
|
+
});
|
|
11
20
|
const hasValidationError = validation === 'error';
|
|
12
21
|
const inputId = useUniqueId('input');
|
|
13
22
|
const hintId = useUniqueId('input-hint');
|
|
14
23
|
const feedbackId = useUniqueId('input-feedback');
|
|
15
24
|
const a11yProps = {
|
|
16
25
|
accessibilityLabel: label,
|
|
17
|
-
accessibilityHint: joinDefined([!hasValidationError && feedback, hint]),
|
|
26
|
+
accessibilityHint: joinDefined([!hasValidationError && feedback, hint, maxCharacterAllowed ? getCopy('maxCharacters').replace(/%\{charCount\}/g, maxCharacterAllowed) : undefined]),
|
|
18
27
|
// native only -> replaced with describedBy on web
|
|
19
28
|
accessibilityDescribedBy: joinDefined([!hasValidationError && feedback && feedbackId,
|
|
20
29
|
// feedback receives a11yRole=alert on error, so there's no need to include it here
|
|
21
|
-
hint && hintId]),
|
|
30
|
+
hint && hintId, charactersCount ? getCopy('charactersRemaining').replace(/%\{charCount\}/g, charactersCount) : undefined]),
|
|
22
31
|
accessibilityInvalid: hasValidationError
|
|
23
32
|
};
|
|
24
33
|
return {
|
package/lib/Link/LinkBase.js
CHANGED
|
@@ -19,20 +19,15 @@ const selectOuterBorderStyles = _ref => {
|
|
|
19
19
|
borderRadius,
|
|
20
20
|
outerBorderOutline
|
|
21
21
|
} = _ref;
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
outerBorderGap
|
|
32
|
-
}),
|
|
33
|
-
borderRadius
|
|
34
|
-
} : {}
|
|
35
|
-
);
|
|
22
|
+
return {
|
|
23
|
+
outline: outerBorderOutline,
|
|
24
|
+
...applyOuterBorder({
|
|
25
|
+
outerBorderColor,
|
|
26
|
+
outerBorderWidth,
|
|
27
|
+
outerBorderGap
|
|
28
|
+
}),
|
|
29
|
+
borderRadius
|
|
30
|
+
};
|
|
36
31
|
};
|
|
37
32
|
const selectTextStyles = _ref2 => {
|
|
38
33
|
let {
|
|
@@ -182,7 +177,7 @@ const LinkBase = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
|
|
|
182
177
|
const themeTokens = resolveLinkTokens(linkState);
|
|
183
178
|
const outerBorderStyles = selectOuterBorderStyles(themeTokens);
|
|
184
179
|
const decorationStyles = selectDecorationStyles(themeTokens);
|
|
185
|
-
return [outerBorderStyles, blockLeftStyle, decorationStyles, hasIcon && staticStyles.rowContainer];
|
|
180
|
+
return [outerBorderStyles, staticStyles.outerBorderStyles, blockLeftStyle, decorationStyles, hasIcon && staticStyles.rowContainer];
|
|
186
181
|
},
|
|
187
182
|
children: linkState => {
|
|
188
183
|
const themeTokens = resolveLinkTokens(linkState);
|
|
@@ -264,6 +259,13 @@ const staticStyles = StyleSheet.create({
|
|
|
264
259
|
pointerEvents: 'none'
|
|
265
260
|
}
|
|
266
261
|
})
|
|
262
|
+
},
|
|
263
|
+
outerBorderStyles: {
|
|
264
|
+
...(Platform.OS !== 'web' && {
|
|
265
|
+
margin: 0,
|
|
266
|
+
marginHorizontal: 2,
|
|
267
|
+
padding: 0
|
|
268
|
+
})
|
|
267
269
|
}
|
|
268
270
|
});
|
|
269
271
|
export default withLinkRouter(LinkBase);
|
|
@@ -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;
|
package/lib/Modal/Modal.js
CHANGED
|
@@ -148,11 +148,11 @@ const Modal = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
|
|
|
148
148
|
const manageFocus = React.useCallback(event => {
|
|
149
149
|
if (event.key === 'Tab') {
|
|
150
150
|
var _modalBodyRef$current;
|
|
151
|
-
const focusableElements = Array.from(modalBodyRef === null || modalBodyRef === void 0 || (_modalBodyRef$current = modalBodyRef.current) === null || _modalBodyRef$current === void 0 ? void 0 : _modalBodyRef$current.querySelectorAll(`
|
|
151
|
+
const focusableElements = Array.from((modalBodyRef === null || modalBodyRef === void 0 || (_modalBodyRef$current = modalBodyRef.current) === null || _modalBodyRef$current === void 0 ? void 0 : _modalBodyRef$current.querySelectorAll(`
|
|
152
152
|
a[href], button, textarea, input, select,
|
|
153
153
|
[tabindex]:not([tabindex="-1"]),
|
|
154
154
|
[contenteditable="true"]
|
|
155
|
-
`));
|
|
155
|
+
`)) || []);
|
|
156
156
|
const firstElement = focusableElements[0];
|
|
157
157
|
const lastElement = focusableElements[focusableElements.length - 1];
|
|
158
158
|
if (event.shiftKey && document.activeElement === firstElement) {
|