@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/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.3.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.4.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
|
}
|
|
@@ -47,6 +47,8 @@ const InputLabel = React.forwardRef(
|
|
|
47
47
|
tooltip,
|
|
48
48
|
tokens,
|
|
49
49
|
variant,
|
|
50
|
+
characterCount,
|
|
51
|
+
maxCharacterAllowed,
|
|
50
52
|
...rest
|
|
51
53
|
},
|
|
52
54
|
ref
|
|
@@ -95,12 +97,30 @@ const InputLabel = React.forwardRef(
|
|
|
95
97
|
<Tooltip content={tooltip} copy={copy} />
|
|
96
98
|
</View>
|
|
97
99
|
)}
|
|
100
|
+
{maxCharacterAllowed && isHintInline && (
|
|
101
|
+
<Text style={[selectHintStyles({ ...themeTokens }), staticStyles.characterCountlabel]}>
|
|
102
|
+
{characterCount}/{maxCharacterAllowed}
|
|
103
|
+
</Text>
|
|
104
|
+
)}
|
|
98
105
|
</View>
|
|
99
|
-
{hint && !isHintInline && (
|
|
106
|
+
{hint && !maxCharacterAllowed && !isHintInline && (
|
|
100
107
|
<Text style={[selectHintStyles(themeTokens), staticStyles.hintBelow]} nativeID={hintId}>
|
|
101
108
|
{hint}
|
|
102
109
|
</Text>
|
|
103
110
|
)}
|
|
111
|
+
{hint && maxCharacterAllowed && !isHintInline && (
|
|
112
|
+
<View style={staticStyles.container}>
|
|
113
|
+
<Text
|
|
114
|
+
style={[selectHintStyles(themeTokens), staticStyles.flexHintBelow]}
|
|
115
|
+
nativeID={hintId}
|
|
116
|
+
>
|
|
117
|
+
{hint}
|
|
118
|
+
</Text>
|
|
119
|
+
<Text style={[selectHintStyles(themeTokens), staticStyles.characterCountlabel]}>
|
|
120
|
+
{characterCount}/{maxCharacterAllowed}
|
|
121
|
+
</Text>
|
|
122
|
+
</View>
|
|
123
|
+
)}
|
|
104
124
|
</>
|
|
105
125
|
)
|
|
106
126
|
}
|
|
@@ -137,6 +157,14 @@ InputLabel.propTypes = {
|
|
|
137
157
|
* Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
|
|
138
158
|
*/
|
|
139
159
|
tooltip: PropTypes.string,
|
|
160
|
+
/**
|
|
161
|
+
* Current number of characterts of an input text.
|
|
162
|
+
*/
|
|
163
|
+
characterCount: PropTypes.number,
|
|
164
|
+
/**
|
|
165
|
+
* Max number of characters that allows an input text.
|
|
166
|
+
*/
|
|
167
|
+
maxCharacterAllowed: PropTypes.number,
|
|
140
168
|
tokens: getTokensPropType('InputLabel'),
|
|
141
169
|
variant: variantProp.propType
|
|
142
170
|
}
|
|
@@ -148,13 +176,22 @@ const staticStyles = StyleSheet.create({
|
|
|
148
176
|
flexShrink: 1,
|
|
149
177
|
flexDirection: 'row'
|
|
150
178
|
},
|
|
179
|
+
characterCountlabel: {
|
|
180
|
+
marginLeft: 'auto',
|
|
181
|
+
marginTop: 'auto'
|
|
182
|
+
},
|
|
151
183
|
label: {
|
|
152
|
-
flexShrink: 1
|
|
184
|
+
flexShrink: 1,
|
|
185
|
+
alignSelf: 'center'
|
|
153
186
|
},
|
|
154
187
|
hintBelow: {
|
|
155
188
|
flexBasis: '100%',
|
|
156
189
|
flexShrink: 0
|
|
157
190
|
},
|
|
191
|
+
flexHintBelow: {
|
|
192
|
+
flexBasis: '100%',
|
|
193
|
+
flexShrink: 1
|
|
194
|
+
},
|
|
158
195
|
tooltipAlign: {
|
|
159
196
|
alignSelf: 'center',
|
|
160
197
|
justifyContent: 'center'
|
|
@@ -6,7 +6,8 @@ import Feedback from '../Feedback'
|
|
|
6
6
|
import StackView from '../StackView'
|
|
7
7
|
import { useThemeTokens } from '../ThemeProvider'
|
|
8
8
|
import useInputSupports from './useInputSupports'
|
|
9
|
-
import { getTokensPropType } from '../utils'
|
|
9
|
+
import { getTokensPropType, useCopy } from '../utils'
|
|
10
|
+
import dictionary from './dictionary'
|
|
10
11
|
|
|
11
12
|
const InputSupports = React.forwardRef(
|
|
12
13
|
(
|
|
@@ -21,18 +22,31 @@ const InputSupports = React.forwardRef(
|
|
|
21
22
|
feedbackProps = {},
|
|
22
23
|
tooltip,
|
|
23
24
|
validation,
|
|
25
|
+
maxCharacterAllowed,
|
|
26
|
+
inputValue,
|
|
24
27
|
nativeID
|
|
25
28
|
},
|
|
26
29
|
ref
|
|
27
30
|
) => {
|
|
28
31
|
const { space } = useThemeTokens('InputSupports')
|
|
32
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
33
|
+
|
|
34
|
+
const maxCharsReachedErrorMessage =
|
|
35
|
+
inputValue?.length > maxCharacterAllowed
|
|
36
|
+
? getCopy('maxCharsMessage').replace(/%\{charCount\}/g, maxCharacterAllowed)
|
|
37
|
+
: ''
|
|
38
|
+
|
|
39
|
+
const feedbackValidation = inputValue?.length > maxCharacterAllowed ? 'error' : validation
|
|
29
40
|
|
|
30
41
|
const { inputId, hintId, feedbackId, a11yProps } = useInputSupports({
|
|
31
42
|
feedback,
|
|
32
43
|
hint,
|
|
33
44
|
label,
|
|
34
45
|
validation,
|
|
35
|
-
nativeID
|
|
46
|
+
nativeID,
|
|
47
|
+
copy,
|
|
48
|
+
maxCharacterAllowed,
|
|
49
|
+
charactersCount: maxCharacterAllowed - inputValue?.length
|
|
36
50
|
})
|
|
37
51
|
|
|
38
52
|
return (
|
|
@@ -46,14 +60,18 @@ const InputSupports = React.forwardRef(
|
|
|
46
60
|
hintId={hintId}
|
|
47
61
|
tooltip={tooltip}
|
|
48
62
|
forId={inputId}
|
|
63
|
+
characterCount={inputValue?.length}
|
|
64
|
+
maxCharacterAllowed={maxCharacterAllowed}
|
|
49
65
|
/>
|
|
50
66
|
)}
|
|
51
|
-
{typeof children === 'function'
|
|
52
|
-
|
|
67
|
+
{typeof children === 'function'
|
|
68
|
+
? children({ inputId, ...a11yProps, validation: feedbackValidation })
|
|
69
|
+
: children}
|
|
70
|
+
{feedback || maxCharsReachedErrorMessage ? (
|
|
53
71
|
<Feedback
|
|
54
72
|
id={feedbackId}
|
|
55
|
-
title={feedback}
|
|
56
|
-
validation={
|
|
73
|
+
title={feedback || maxCharsReachedErrorMessage}
|
|
74
|
+
validation={feedbackValidation}
|
|
57
75
|
tokens={feedbackTokens}
|
|
58
76
|
variant={{ icon: feedbackProps.showFeedbackIcon }}
|
|
59
77
|
{...feedbackProps}
|
|
@@ -107,7 +125,15 @@ InputSupports.propTypes = {
|
|
|
107
125
|
/**
|
|
108
126
|
* ID for DOM element on web
|
|
109
127
|
*/
|
|
110
|
-
nativeID: PropTypes.string
|
|
128
|
+
nativeID: PropTypes.string,
|
|
129
|
+
/**
|
|
130
|
+
* The text value of a TextArea or TextInput
|
|
131
|
+
*/
|
|
132
|
+
inputValue: PropTypes.string,
|
|
133
|
+
/**
|
|
134
|
+
* Max number of characters that allows an input text.
|
|
135
|
+
*/
|
|
136
|
+
maxCharacterAllowed: PropTypes.number
|
|
111
137
|
}
|
|
112
138
|
|
|
113
139
|
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,8 +1,20 @@
|
|
|
1
1
|
import useUniqueId from '../utils/useUniqueId'
|
|
2
|
+
import { useCopy } from '../utils'
|
|
3
|
+
import dictionary from './dictionary'
|
|
2
4
|
|
|
3
5
|
const joinDefined = (array) => array.filter((item) => item !== undefined).join(' ')
|
|
4
6
|
|
|
5
|
-
const useInputSupports = ({
|
|
7
|
+
const useInputSupports = ({
|
|
8
|
+
label,
|
|
9
|
+
feedback,
|
|
10
|
+
validation,
|
|
11
|
+
hint,
|
|
12
|
+
nativeID,
|
|
13
|
+
copy,
|
|
14
|
+
maxCharacterAllowed,
|
|
15
|
+
charactersCount
|
|
16
|
+
}) => {
|
|
17
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
6
18
|
const hasValidationError = validation === 'error'
|
|
7
19
|
|
|
8
20
|
const inputId = useUniqueId('input')
|
|
@@ -11,10 +23,19 @@ const useInputSupports = ({ label, feedback, validation, hint, nativeID }) => {
|
|
|
11
23
|
|
|
12
24
|
const a11yProps = {
|
|
13
25
|
accessibilityLabel: label,
|
|
14
|
-
accessibilityHint: joinDefined([
|
|
26
|
+
accessibilityHint: joinDefined([
|
|
27
|
+
!hasValidationError && feedback,
|
|
28
|
+
hint,
|
|
29
|
+
maxCharacterAllowed
|
|
30
|
+
? getCopy('maxCharacters').replace(/%\{charCount\}/g, maxCharacterAllowed)
|
|
31
|
+
: undefined
|
|
32
|
+
]), // native only -> replaced with describedBy on web
|
|
15
33
|
accessibilityDescribedBy: joinDefined([
|
|
16
34
|
!hasValidationError && feedback && feedbackId, // feedback receives a11yRole=alert on error, so there's no need to include it here
|
|
17
|
-
hint && hintId
|
|
35
|
+
hint && hintId,
|
|
36
|
+
charactersCount
|
|
37
|
+
? getCopy('charactersRemaining').replace(/%\{charCount\}/g, charactersCount)
|
|
38
|
+
: undefined
|
|
18
39
|
]),
|
|
19
40
|
accessibilityInvalid: hasValidationError
|
|
20
41
|
}
|
package/src/Link/LinkBase.jsx
CHANGED
|
@@ -32,21 +32,15 @@ const selectOuterBorderStyles = ({
|
|
|
32
32
|
outerBorderGap,
|
|
33
33
|
borderRadius,
|
|
34
34
|
outerBorderOutline
|
|
35
|
-
}) =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
outerBorderWidth,
|
|
45
|
-
outerBorderGap
|
|
46
|
-
}),
|
|
47
|
-
borderRadius
|
|
48
|
-
}
|
|
49
|
-
: {}
|
|
35
|
+
}) => ({
|
|
36
|
+
outline: outerBorderOutline,
|
|
37
|
+
...applyOuterBorder({
|
|
38
|
+
outerBorderColor,
|
|
39
|
+
outerBorderWidth,
|
|
40
|
+
outerBorderGap
|
|
41
|
+
}),
|
|
42
|
+
borderRadius
|
|
43
|
+
})
|
|
50
44
|
|
|
51
45
|
const selectTextStyles = ({ color, blockFontSize }) => {
|
|
52
46
|
return {
|
|
@@ -174,6 +168,7 @@ const LinkBase = React.forwardRef(
|
|
|
174
168
|
const decorationStyles = selectDecorationStyles(themeTokens)
|
|
175
169
|
return [
|
|
176
170
|
outerBorderStyles,
|
|
171
|
+
staticStyles.outerBorderStyles,
|
|
177
172
|
blockLeftStyle,
|
|
178
173
|
decorationStyles,
|
|
179
174
|
hasIcon && staticStyles.rowContainer
|
|
@@ -273,6 +268,13 @@ const staticStyles = StyleSheet.create({
|
|
|
273
268
|
pointerEvents: 'none'
|
|
274
269
|
}
|
|
275
270
|
})
|
|
271
|
+
},
|
|
272
|
+
outerBorderStyles: {
|
|
273
|
+
...(Platform.OS !== 'web' && {
|
|
274
|
+
margin: 0,
|
|
275
|
+
marginHorizontal: 2,
|
|
276
|
+
padding: 0
|
|
277
|
+
})
|
|
276
278
|
}
|
|
277
279
|
})
|
|
278
280
|
|
|
@@ -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
|
package/src/Modal/Modal.jsx
CHANGED