@telus-uds/components-base 1.65.0 → 1.66.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 +12 -2
- package/lib/TextInput/TextInputBase.js +102 -19
- package/lib/TextInput/propTypes.js +5 -0
- package/lib/utils/props/textInputProps.js +1 -0
- package/lib-module/TextInput/TextInputBase.js +101 -19
- package/lib-module/TextInput/propTypes.js +5 -0
- package/lib-module/utils/props/textInputProps.js +1 -0
- package/package.json +2 -2
- package/src/TextInput/TextInputBase.jsx +67 -12
- package/src/TextInput/propTypes.js +4 -0
- package/src/utils/props/textInputProps.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# Change Log - @telus-uds/components-base
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Wed, 01 Nov 2023 00:54:31 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## 1.66.0
|
|
8
|
+
|
|
9
|
+
Wed, 01 Nov 2023 00:54:31 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- card type for textInput component (srikanthkhari@gmail.com)
|
|
14
|
+
- Add responsive `width` variant to `ButtonGroup` (shahzaibkhalidmalik@outlook.com)
|
|
15
|
+
- Bump @telus-uds/system-theme-tokens to v2.43.0
|
|
16
|
+
|
|
7
17
|
## 1.65.0
|
|
8
18
|
|
|
9
|
-
Thu, 26 Oct 2023 19:
|
|
19
|
+
Thu, 26 Oct 2023 19:35:09 GMT
|
|
10
20
|
|
|
11
21
|
### Minor changes
|
|
12
22
|
|
|
@@ -23,6 +23,8 @@ var _StackView = _interopRequireDefault(require("../StackView"));
|
|
|
23
23
|
|
|
24
24
|
var _IconButton = _interopRequireDefault(require("../IconButton"));
|
|
25
25
|
|
|
26
|
+
var _Icon = _interopRequireDefault(require("../Icon"));
|
|
27
|
+
|
|
26
28
|
var _utils = require("../utils");
|
|
27
29
|
|
|
28
30
|
var _dictionary = _interopRequireDefault(require("./dictionary"));
|
|
@@ -37,7 +39,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
|
|
|
37
39
|
|
|
38
40
|
const [selectProps, selectedSystemPropTypes] = (0, _utils.selectSystemProps)([_utils.a11yProps, _utils.textInputHandlerProps, _utils.textInputProps, _utils.viewProps]);
|
|
39
41
|
|
|
40
|
-
const selectInputStyles = (_ref, themeOptions, inactive) => {
|
|
42
|
+
const selectInputStyles = (_ref, themeOptions, inactive, type) => {
|
|
41
43
|
let {
|
|
42
44
|
backgroundColor,
|
|
43
45
|
color,
|
|
@@ -98,7 +100,7 @@ const selectInputStyles = (_ref, themeOptions, inactive) => {
|
|
|
98
100
|
borderWidth,
|
|
99
101
|
borderColor,
|
|
100
102
|
borderRadius,
|
|
101
|
-
paddingLeft: offsetBorder(paddingLeft),
|
|
103
|
+
paddingLeft: type === 'card' ? offsetBorder(paddingLeft + 34) : offsetBorder(paddingLeft),
|
|
102
104
|
paddingRight: icon ? offsetBorder(paddingWithIcon) : offsetBorder(paddingRight),
|
|
103
105
|
paddingTop: offsetBorder(paddingTop),
|
|
104
106
|
paddingBottom: offsetBorder(paddingBottom),
|
|
@@ -152,16 +154,64 @@ const selectIconContainerStyles = (_ref4, buttonCount) => {
|
|
|
152
154
|
};
|
|
153
155
|
};
|
|
154
156
|
|
|
155
|
-
const
|
|
157
|
+
const selectLeftIconContainerStyles = _ref5 => {
|
|
156
158
|
let {
|
|
157
|
-
|
|
159
|
+
leftIconPaddingBottom
|
|
158
160
|
} = _ref5;
|
|
161
|
+
return {
|
|
162
|
+
// not tokenizing paddingLeft as it remains same across brands for now
|
|
163
|
+
paddingLeft: 10,
|
|
164
|
+
paddingBottom: leftIconPaddingBottom
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const selectButtonsContainerStyle = _ref6 => {
|
|
169
|
+
let {
|
|
170
|
+
buttonsPaddingRight
|
|
171
|
+
} = _ref6;
|
|
159
172
|
return {
|
|
160
173
|
paddingRight: buttonsPaddingRight
|
|
161
174
|
};
|
|
162
175
|
};
|
|
163
176
|
|
|
164
|
-
const
|
|
177
|
+
const getIcon = function () {
|
|
178
|
+
let cardNumber = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
179
|
+
let {
|
|
180
|
+
defaultCreditIcon,
|
|
181
|
+
amexIcon,
|
|
182
|
+
visaIcon,
|
|
183
|
+
masterCardIcon
|
|
184
|
+
} = arguments.length > 1 ? arguments[1] : undefined;
|
|
185
|
+
const cardType = {
|
|
186
|
+
1: {
|
|
187
|
+
icon: visaIcon,
|
|
188
|
+
testID: 'visa'
|
|
189
|
+
},
|
|
190
|
+
2: {
|
|
191
|
+
icon: amexIcon,
|
|
192
|
+
testID: 'amex'
|
|
193
|
+
},
|
|
194
|
+
4: {
|
|
195
|
+
icon: masterCardIcon,
|
|
196
|
+
testID: 'mastercard'
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const firstDigit = cardNumber ? cardNumber[0] : '';
|
|
200
|
+
const defaultIcon = {
|
|
201
|
+
icon: defaultCreditIcon,
|
|
202
|
+
testID: 'default'
|
|
203
|
+
};
|
|
204
|
+
const selectedIcon = cardNumber.length > 4 ? cardType[firstDigit] || defaultIcon : defaultIcon;
|
|
205
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
|
|
206
|
+
icon: selectedIcon.icon,
|
|
207
|
+
variant: {
|
|
208
|
+
size: 'large'
|
|
209
|
+
},
|
|
210
|
+
testID: selectedIcon.testID
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref7, ref) => {
|
|
165
215
|
let {
|
|
166
216
|
buttons = [],
|
|
167
217
|
copy = 'en',
|
|
@@ -180,8 +230,9 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
|
|
|
180
230
|
tokens,
|
|
181
231
|
value,
|
|
182
232
|
variant = {},
|
|
233
|
+
type,
|
|
183
234
|
...rest
|
|
184
|
-
} =
|
|
235
|
+
} = _ref7;
|
|
185
236
|
const [isFocused, setIsFocused] = (0, _react.useState)(false);
|
|
186
237
|
const [showPassword, setShowPassword] = (0, _react.useState)(false);
|
|
187
238
|
|
|
@@ -222,6 +273,12 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
|
|
|
222
273
|
onChange,
|
|
223
274
|
readOnly
|
|
224
275
|
});
|
|
276
|
+
const {
|
|
277
|
+
password,
|
|
278
|
+
numeric
|
|
279
|
+
} = variant;
|
|
280
|
+
const isNumeric = numeric || type === 'card' || type === 'number';
|
|
281
|
+
const isPassword = password || type === 'password';
|
|
225
282
|
const element = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current;
|
|
226
283
|
(0, _react.useEffect)(() => {
|
|
227
284
|
if (_Platform.default.OS === 'web' && pattern && element) {
|
|
@@ -234,11 +291,16 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
|
|
|
234
291
|
const handleChangeText = event => {
|
|
235
292
|
var _event$nativeEvent, _event$target;
|
|
236
293
|
|
|
237
|
-
const {
|
|
238
|
-
numeric
|
|
239
|
-
} = variant;
|
|
240
294
|
const text = ((_event$nativeEvent = event.nativeEvent) === null || _event$nativeEvent === void 0 ? void 0 : _event$nativeEvent.text) || ((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.value);
|
|
241
|
-
|
|
295
|
+
let filteredText = isNumeric ? text === null || text === void 0 ? void 0 : text.replace(/[^\d]/g, '') : text;
|
|
296
|
+
|
|
297
|
+
if (type === 'card' && filteredText) {
|
|
298
|
+
const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '');
|
|
299
|
+
const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g'); // Add a space every 4 digits starting from the 5th position
|
|
300
|
+
|
|
301
|
+
filteredText = formattedValue.replace(regex, '$1 ').trim();
|
|
302
|
+
}
|
|
303
|
+
|
|
242
304
|
setValue(filteredText, event);
|
|
243
305
|
if (typeof onChangeText === 'function') onChangeText(filteredText, event);
|
|
244
306
|
};
|
|
@@ -267,7 +329,11 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
|
|
|
267
329
|
clearButtonIcon: ClearButtonIcon,
|
|
268
330
|
icon: IconComponent,
|
|
269
331
|
passwordShowButtonIcon,
|
|
270
|
-
passwordHideButtonIcon
|
|
332
|
+
passwordHideButtonIcon,
|
|
333
|
+
defaultCreditIcon,
|
|
334
|
+
amexIcon,
|
|
335
|
+
visaIcon,
|
|
336
|
+
masterCardIcon
|
|
271
337
|
} = themeTokens;
|
|
272
338
|
const buttonsGapSize = (0, _utils.useSpacingScale)(buttonsGap);
|
|
273
339
|
const getCopy = (0, _utils.useCopy)({
|
|
@@ -287,7 +353,7 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
|
|
|
287
353
|
}, "clear"));
|
|
288
354
|
}
|
|
289
355
|
|
|
290
|
-
if (
|
|
356
|
+
if (isPassword) {
|
|
291
357
|
textInputButtons === null || textInputButtons === void 0 ? void 0 : textInputButtons.unshift( /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
|
|
292
358
|
accessibilityLabel: !showPassword ? getCopy('hidePasswordAccessibilityLabel') : getCopy('showPasswordAccessibilityLabel'),
|
|
293
359
|
icon: !showPassword ? passwordShowButtonIcon : passwordHideButtonIcon,
|
|
@@ -313,6 +379,7 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
|
|
|
313
379
|
onMouseOut: handleMouseOut,
|
|
314
380
|
onChange: handleChangeText,
|
|
315
381
|
defaultValue: initialValue,
|
|
382
|
+
maxLength: type === 'card' ? 19 : undefined,
|
|
316
383
|
value: isControlled ? currentValue : undefined
|
|
317
384
|
};
|
|
318
385
|
const {
|
|
@@ -320,20 +387,29 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
|
|
|
320
387
|
} = (0, _ThemeProvider.useTheme)();
|
|
321
388
|
const nativeInputStyle = selectInputStyles({ ...themeTokens,
|
|
322
389
|
height
|
|
323
|
-
}, themeOptions, inactive);
|
|
390
|
+
}, themeOptions, inactive, type);
|
|
324
391
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_View.default, {
|
|
325
392
|
style: selectOuterBorderStyles(themeTokens),
|
|
326
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
393
|
+
children: [type === 'card' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
|
|
394
|
+
pointerEvents: "none",
|
|
395
|
+
style: [staticStyles.leftIconContainer, selectLeftIconContainerStyles(themeTokens)],
|
|
396
|
+
children: getIcon(currentValue, {
|
|
397
|
+
defaultCreditIcon,
|
|
398
|
+
amexIcon,
|
|
399
|
+
visaIcon,
|
|
400
|
+
masterCardIcon
|
|
401
|
+
})
|
|
402
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.default, {
|
|
327
403
|
ref: inputRef,
|
|
328
|
-
keyboardType:
|
|
329
|
-
inputMode:
|
|
404
|
+
keyboardType: isNumeric && 'numeric',
|
|
405
|
+
inputMode: isNumeric && 'numeric',
|
|
330
406
|
style: nativeInputStyle,
|
|
331
|
-
secureTextEntry:
|
|
407
|
+
secureTextEntry: isPassword && !showPassword,
|
|
332
408
|
...inputProps
|
|
333
409
|
}), IconComponent && /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
|
|
334
410
|
pointerEvents: "none" // avoid hijacking input press events
|
|
335
411
|
,
|
|
336
|
-
style: [staticStyles.
|
|
412
|
+
style: [staticStyles.rightIconContainer, selectIconContainerStyles({ ...themeTokens,
|
|
337
413
|
buttonsGapSize
|
|
338
414
|
}, buttons === null || buttons === void 0 ? void 0 : buttons.length)],
|
|
339
415
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(IconComponent, { ...selectIconTokens(themeTokens)
|
|
@@ -359,6 +435,7 @@ TextInputBase.propTypes = { ...selectedSystemPropTypes,
|
|
|
359
435
|
copy: _propTypes.default.oneOfType([_propTypes.default.oneOf(['en', 'fr']), _propTypes.default.shape({
|
|
360
436
|
clearButtonAccessibilityLabel: _propTypes.default.string
|
|
361
437
|
})]),
|
|
438
|
+
type: _propTypes.default.oneOfType([_propTypes.default.oneOf(['password', 'card', 'number'])]),
|
|
362
439
|
height: _propTypes.default.number,
|
|
363
440
|
inactive: _propTypes.default.bool,
|
|
364
441
|
initialValue: _propTypes.default.string,
|
|
@@ -387,9 +464,15 @@ const staticStyles = _StyleSheet.default.create({
|
|
|
387
464
|
bottom: 0,
|
|
388
465
|
justifyContent: 'center'
|
|
389
466
|
},
|
|
390
|
-
|
|
467
|
+
rightIconContainer: {
|
|
391
468
|
position: 'absolute',
|
|
392
469
|
right: 0,
|
|
393
470
|
bottom: 0
|
|
471
|
+
},
|
|
472
|
+
leftIconContainer: {
|
|
473
|
+
position: 'absolute',
|
|
474
|
+
left: 0,
|
|
475
|
+
bottom: 0,
|
|
476
|
+
zIndex: 1
|
|
394
477
|
}
|
|
395
478
|
});
|
|
@@ -18,6 +18,11 @@ const textInputPropTypes = {
|
|
|
18
18
|
*/
|
|
19
19
|
value: _propTypes.default.string,
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Use this to set the type of the input. Defaults to `text`.
|
|
23
|
+
*/
|
|
24
|
+
type: _propTypes.default.string,
|
|
25
|
+
|
|
21
26
|
/**
|
|
22
27
|
* Use this to set the initial value of an uncontrolled input.
|
|
23
28
|
* Updating `initialValue` will **not** update the actual value.
|
|
@@ -7,13 +7,14 @@ import View from "react-native-web/dist/exports/View";
|
|
|
7
7
|
import { applyTextStyles, useTheme, useThemeTokens, applyOuterBorder } from '../ThemeProvider';
|
|
8
8
|
import StackView from '../StackView';
|
|
9
9
|
import IconButton from '../IconButton';
|
|
10
|
+
import Icon from '../Icon';
|
|
10
11
|
import { a11yProps, getTokensPropType, selectSystemProps, textInputHandlerProps, textInputProps, useCopy, useInputValue, useSpacingScale, variantProp, viewProps } from '../utils';
|
|
11
12
|
import dictionary from './dictionary';
|
|
12
13
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
14
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
15
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, textInputHandlerProps, textInputProps, viewProps]);
|
|
15
16
|
|
|
16
|
-
const selectInputStyles = (_ref, themeOptions, inactive) => {
|
|
17
|
+
const selectInputStyles = (_ref, themeOptions, inactive, type) => {
|
|
17
18
|
let {
|
|
18
19
|
backgroundColor,
|
|
19
20
|
color,
|
|
@@ -72,7 +73,7 @@ const selectInputStyles = (_ref, themeOptions, inactive) => {
|
|
|
72
73
|
borderWidth,
|
|
73
74
|
borderColor,
|
|
74
75
|
borderRadius,
|
|
75
|
-
paddingLeft: offsetBorder(paddingLeft),
|
|
76
|
+
paddingLeft: type === 'card' ? offsetBorder(paddingLeft + 34) : offsetBorder(paddingLeft),
|
|
76
77
|
paddingRight: icon ? offsetBorder(paddingWithIcon) : offsetBorder(paddingRight),
|
|
77
78
|
paddingTop: offsetBorder(paddingTop),
|
|
78
79
|
paddingBottom: offsetBorder(paddingBottom),
|
|
@@ -126,16 +127,64 @@ const selectIconContainerStyles = (_ref4, buttonCount) => {
|
|
|
126
127
|
};
|
|
127
128
|
};
|
|
128
129
|
|
|
129
|
-
const
|
|
130
|
+
const selectLeftIconContainerStyles = _ref5 => {
|
|
130
131
|
let {
|
|
131
|
-
|
|
132
|
+
leftIconPaddingBottom
|
|
132
133
|
} = _ref5;
|
|
134
|
+
return {
|
|
135
|
+
// not tokenizing paddingLeft as it remains same across brands for now
|
|
136
|
+
paddingLeft: 10,
|
|
137
|
+
paddingBottom: leftIconPaddingBottom
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const selectButtonsContainerStyle = _ref6 => {
|
|
142
|
+
let {
|
|
143
|
+
buttonsPaddingRight
|
|
144
|
+
} = _ref6;
|
|
133
145
|
return {
|
|
134
146
|
paddingRight: buttonsPaddingRight
|
|
135
147
|
};
|
|
136
148
|
};
|
|
137
149
|
|
|
138
|
-
const
|
|
150
|
+
const getIcon = function () {
|
|
151
|
+
let cardNumber = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
152
|
+
let {
|
|
153
|
+
defaultCreditIcon,
|
|
154
|
+
amexIcon,
|
|
155
|
+
visaIcon,
|
|
156
|
+
masterCardIcon
|
|
157
|
+
} = arguments.length > 1 ? arguments[1] : undefined;
|
|
158
|
+
const cardType = {
|
|
159
|
+
1: {
|
|
160
|
+
icon: visaIcon,
|
|
161
|
+
testID: 'visa'
|
|
162
|
+
},
|
|
163
|
+
2: {
|
|
164
|
+
icon: amexIcon,
|
|
165
|
+
testID: 'amex'
|
|
166
|
+
},
|
|
167
|
+
4: {
|
|
168
|
+
icon: masterCardIcon,
|
|
169
|
+
testID: 'mastercard'
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const firstDigit = cardNumber ? cardNumber[0] : '';
|
|
173
|
+
const defaultIcon = {
|
|
174
|
+
icon: defaultCreditIcon,
|
|
175
|
+
testID: 'default'
|
|
176
|
+
};
|
|
177
|
+
const selectedIcon = cardNumber.length > 4 ? cardType[firstDigit] || defaultIcon : defaultIcon;
|
|
178
|
+
return /*#__PURE__*/_jsx(Icon, {
|
|
179
|
+
icon: selectedIcon.icon,
|
|
180
|
+
variant: {
|
|
181
|
+
size: 'large'
|
|
182
|
+
},
|
|
183
|
+
testID: selectedIcon.testID
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const TextInputBase = /*#__PURE__*/forwardRef((_ref7, ref) => {
|
|
139
188
|
let {
|
|
140
189
|
buttons = [],
|
|
141
190
|
copy = 'en',
|
|
@@ -154,8 +203,9 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
|
154
203
|
tokens,
|
|
155
204
|
value,
|
|
156
205
|
variant = {},
|
|
206
|
+
type,
|
|
157
207
|
...rest
|
|
158
|
-
} =
|
|
208
|
+
} = _ref7;
|
|
159
209
|
const [isFocused, setIsFocused] = useState(false);
|
|
160
210
|
const [showPassword, setShowPassword] = useState(false);
|
|
161
211
|
|
|
@@ -196,6 +246,12 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
|
196
246
|
onChange,
|
|
197
247
|
readOnly
|
|
198
248
|
});
|
|
249
|
+
const {
|
|
250
|
+
password,
|
|
251
|
+
numeric
|
|
252
|
+
} = variant;
|
|
253
|
+
const isNumeric = numeric || type === 'card' || type === 'number';
|
|
254
|
+
const isPassword = password || type === 'password';
|
|
199
255
|
const element = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current;
|
|
200
256
|
useEffect(() => {
|
|
201
257
|
if (Platform.OS === 'web' && pattern && element) {
|
|
@@ -208,11 +264,16 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
|
208
264
|
const handleChangeText = event => {
|
|
209
265
|
var _event$nativeEvent, _event$target;
|
|
210
266
|
|
|
211
|
-
const {
|
|
212
|
-
numeric
|
|
213
|
-
} = variant;
|
|
214
267
|
const text = ((_event$nativeEvent = event.nativeEvent) === null || _event$nativeEvent === void 0 ? void 0 : _event$nativeEvent.text) || ((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.value);
|
|
215
|
-
|
|
268
|
+
let filteredText = isNumeric ? text === null || text === void 0 ? void 0 : text.replace(/[^\d]/g, '') : text;
|
|
269
|
+
|
|
270
|
+
if (type === 'card' && filteredText) {
|
|
271
|
+
const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '');
|
|
272
|
+
const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g'); // Add a space every 4 digits starting from the 5th position
|
|
273
|
+
|
|
274
|
+
filteredText = formattedValue.replace(regex, '$1 ').trim();
|
|
275
|
+
}
|
|
276
|
+
|
|
216
277
|
setValue(filteredText, event);
|
|
217
278
|
if (typeof onChangeText === 'function') onChangeText(filteredText, event);
|
|
218
279
|
};
|
|
@@ -241,7 +302,11 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
|
241
302
|
clearButtonIcon: ClearButtonIcon,
|
|
242
303
|
icon: IconComponent,
|
|
243
304
|
passwordShowButtonIcon,
|
|
244
|
-
passwordHideButtonIcon
|
|
305
|
+
passwordHideButtonIcon,
|
|
306
|
+
defaultCreditIcon,
|
|
307
|
+
amexIcon,
|
|
308
|
+
visaIcon,
|
|
309
|
+
masterCardIcon
|
|
245
310
|
} = themeTokens;
|
|
246
311
|
const buttonsGapSize = useSpacingScale(buttonsGap);
|
|
247
312
|
const getCopy = useCopy({
|
|
@@ -261,7 +326,7 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
|
261
326
|
}, "clear"));
|
|
262
327
|
}
|
|
263
328
|
|
|
264
|
-
if (
|
|
329
|
+
if (isPassword) {
|
|
265
330
|
textInputButtons === null || textInputButtons === void 0 ? void 0 : textInputButtons.unshift( /*#__PURE__*/_jsx(IconButton, {
|
|
266
331
|
accessibilityLabel: !showPassword ? getCopy('hidePasswordAccessibilityLabel') : getCopy('showPasswordAccessibilityLabel'),
|
|
267
332
|
icon: !showPassword ? passwordShowButtonIcon : passwordHideButtonIcon,
|
|
@@ -287,6 +352,7 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
|
287
352
|
onMouseOut: handleMouseOut,
|
|
288
353
|
onChange: handleChangeText,
|
|
289
354
|
defaultValue: initialValue,
|
|
355
|
+
maxLength: type === 'card' ? 19 : undefined,
|
|
290
356
|
value: isControlled ? currentValue : undefined
|
|
291
357
|
};
|
|
292
358
|
const {
|
|
@@ -294,20 +360,29 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
|
294
360
|
} = useTheme();
|
|
295
361
|
const nativeInputStyle = selectInputStyles({ ...themeTokens,
|
|
296
362
|
height
|
|
297
|
-
}, themeOptions, inactive);
|
|
363
|
+
}, themeOptions, inactive, type);
|
|
298
364
|
return /*#__PURE__*/_jsxs(View, {
|
|
299
365
|
style: selectOuterBorderStyles(themeTokens),
|
|
300
|
-
children: [/*#__PURE__*/_jsx(
|
|
366
|
+
children: [type === 'card' && /*#__PURE__*/_jsx(View, {
|
|
367
|
+
pointerEvents: "none",
|
|
368
|
+
style: [staticStyles.leftIconContainer, selectLeftIconContainerStyles(themeTokens)],
|
|
369
|
+
children: getIcon(currentValue, {
|
|
370
|
+
defaultCreditIcon,
|
|
371
|
+
amexIcon,
|
|
372
|
+
visaIcon,
|
|
373
|
+
masterCardIcon
|
|
374
|
+
})
|
|
375
|
+
}), /*#__PURE__*/_jsx(NativeTextInput, {
|
|
301
376
|
ref: inputRef,
|
|
302
|
-
keyboardType:
|
|
303
|
-
inputMode:
|
|
377
|
+
keyboardType: isNumeric && 'numeric',
|
|
378
|
+
inputMode: isNumeric && 'numeric',
|
|
304
379
|
style: nativeInputStyle,
|
|
305
|
-
secureTextEntry:
|
|
380
|
+
secureTextEntry: isPassword && !showPassword,
|
|
306
381
|
...inputProps
|
|
307
382
|
}), IconComponent && /*#__PURE__*/_jsx(View, {
|
|
308
383
|
pointerEvents: "none" // avoid hijacking input press events
|
|
309
384
|
,
|
|
310
|
-
style: [staticStyles.
|
|
385
|
+
style: [staticStyles.rightIconContainer, selectIconContainerStyles({ ...themeTokens,
|
|
311
386
|
buttonsGapSize
|
|
312
387
|
}, buttons === null || buttons === void 0 ? void 0 : buttons.length)],
|
|
313
388
|
children: /*#__PURE__*/_jsx(IconComponent, { ...selectIconTokens(themeTokens)
|
|
@@ -333,6 +408,7 @@ TextInputBase.propTypes = { ...selectedSystemPropTypes,
|
|
|
333
408
|
copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), PropTypes.shape({
|
|
334
409
|
clearButtonAccessibilityLabel: PropTypes.string
|
|
335
410
|
})]),
|
|
411
|
+
type: PropTypes.oneOfType([PropTypes.oneOf(['password', 'card', 'number'])]),
|
|
336
412
|
height: PropTypes.number,
|
|
337
413
|
inactive: PropTypes.bool,
|
|
338
414
|
initialValue: PropTypes.string,
|
|
@@ -359,9 +435,15 @@ const staticStyles = StyleSheet.create({
|
|
|
359
435
|
bottom: 0,
|
|
360
436
|
justifyContent: 'center'
|
|
361
437
|
},
|
|
362
|
-
|
|
438
|
+
rightIconContainer: {
|
|
363
439
|
position: 'absolute',
|
|
364
440
|
right: 0,
|
|
365
441
|
bottom: 0
|
|
442
|
+
},
|
|
443
|
+
leftIconContainer: {
|
|
444
|
+
position: 'absolute',
|
|
445
|
+
left: 0,
|
|
446
|
+
bottom: 0,
|
|
447
|
+
zIndex: 1
|
|
366
448
|
}
|
|
367
449
|
});
|
|
@@ -8,6 +8,11 @@ const textInputPropTypes = {
|
|
|
8
8
|
*/
|
|
9
9
|
value: PropTypes.string,
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Use this to set the type of the input. Defaults to `text`.
|
|
13
|
+
*/
|
|
14
|
+
type: PropTypes.string,
|
|
15
|
+
|
|
11
16
|
/**
|
|
12
17
|
* Use this to set the initial value of an uncontrolled input.
|
|
13
18
|
* Updating `initialValue` will **not** update the actual value.
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"@floating-ui/react-native": "^0.8.1",
|
|
12
12
|
"@gorhom/portal": "^1.0.14",
|
|
13
13
|
"@telus-uds/system-constants": "^1.3.0",
|
|
14
|
-
"@telus-uds/system-theme-tokens": "^2.
|
|
14
|
+
"@telus-uds/system-theme-tokens": "^2.43.0",
|
|
15
15
|
"airbnb-prop-types": "^2.16.0",
|
|
16
16
|
"lodash.debounce": "^4.0.8",
|
|
17
17
|
"lodash.merge": "^4.6.2",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"standard-engine": {
|
|
74
74
|
"skip": true
|
|
75
75
|
},
|
|
76
|
-
"version": "1.
|
|
76
|
+
"version": "1.66.0"
|
|
77
77
|
}
|
|
@@ -4,6 +4,7 @@ import { Platform, StyleSheet, TextInput as NativeTextInput, View } from 'react-
|
|
|
4
4
|
import { applyTextStyles, useTheme, useThemeTokens, applyOuterBorder } from '../ThemeProvider'
|
|
5
5
|
import StackView from '../StackView'
|
|
6
6
|
import IconButton from '../IconButton'
|
|
7
|
+
import Icon from '../Icon'
|
|
7
8
|
import {
|
|
8
9
|
a11yProps,
|
|
9
10
|
getTokensPropType,
|
|
@@ -48,7 +49,8 @@ const selectInputStyles = (
|
|
|
48
49
|
height
|
|
49
50
|
},
|
|
50
51
|
themeOptions,
|
|
51
|
-
inactive
|
|
52
|
+
inactive,
|
|
53
|
+
type
|
|
52
54
|
) => {
|
|
53
55
|
// Subtract border width from padding so overall input width/height doesn't
|
|
54
56
|
// jump around if the border width changes (avoiding NaN and negative padding)
|
|
@@ -88,7 +90,7 @@ const selectInputStyles = (
|
|
|
88
90
|
borderWidth,
|
|
89
91
|
borderColor,
|
|
90
92
|
borderRadius,
|
|
91
|
-
paddingLeft: offsetBorder(paddingLeft),
|
|
93
|
+
paddingLeft: type === 'card' ? offsetBorder(paddingLeft + 34) : offsetBorder(paddingLeft),
|
|
92
94
|
paddingRight: icon ? offsetBorder(paddingWithIcon) : offsetBorder(paddingRight),
|
|
93
95
|
paddingTop: offsetBorder(paddingTop),
|
|
94
96
|
paddingBottom: offsetBorder(paddingBottom),
|
|
@@ -128,10 +130,29 @@ const selectIconContainerStyles = (
|
|
|
128
130
|
paddingBottom
|
|
129
131
|
})
|
|
130
132
|
|
|
133
|
+
const selectLeftIconContainerStyles = ({ leftIconPaddingBottom }) => ({
|
|
134
|
+
// not tokenizing paddingLeft as it remains same across brands for now
|
|
135
|
+
paddingLeft: 10,
|
|
136
|
+
paddingBottom: leftIconPaddingBottom
|
|
137
|
+
})
|
|
138
|
+
|
|
131
139
|
const selectButtonsContainerStyle = ({ buttonsPaddingRight }) => ({
|
|
132
140
|
paddingRight: buttonsPaddingRight
|
|
133
141
|
})
|
|
134
142
|
|
|
143
|
+
const getIcon = (cardNumber = '', { defaultCreditIcon, amexIcon, visaIcon, masterCardIcon }) => {
|
|
144
|
+
const cardType = {
|
|
145
|
+
1: { icon: visaIcon, testID: 'visa' },
|
|
146
|
+
2: { icon: amexIcon, testID: 'amex' },
|
|
147
|
+
4: { icon: masterCardIcon, testID: 'mastercard' }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const firstDigit = cardNumber ? cardNumber[0] : ''
|
|
151
|
+
const defaultIcon = { icon: defaultCreditIcon, testID: 'default' }
|
|
152
|
+
const selectedIcon = cardNumber.length > 4 ? cardType[firstDigit] || defaultIcon : defaultIcon
|
|
153
|
+
return <Icon icon={selectedIcon.icon} variant={{ size: 'large' }} testID={selectedIcon.testID} />
|
|
154
|
+
}
|
|
155
|
+
|
|
135
156
|
const TextInputBase = forwardRef(
|
|
136
157
|
(
|
|
137
158
|
{
|
|
@@ -152,6 +173,7 @@ const TextInputBase = forwardRef(
|
|
|
152
173
|
tokens,
|
|
153
174
|
value,
|
|
154
175
|
variant = {},
|
|
176
|
+
type,
|
|
155
177
|
...rest
|
|
156
178
|
},
|
|
157
179
|
ref
|
|
@@ -187,6 +209,9 @@ const TextInputBase = forwardRef(
|
|
|
187
209
|
onChange,
|
|
188
210
|
readOnly
|
|
189
211
|
})
|
|
212
|
+
const { password, numeric } = variant
|
|
213
|
+
const isNumeric = numeric || type === 'card' || type === 'number'
|
|
214
|
+
const isPassword = password || type === 'password'
|
|
190
215
|
|
|
191
216
|
const element = inputRef?.current
|
|
192
217
|
useEffect(() => {
|
|
@@ -198,9 +223,14 @@ const TextInputBase = forwardRef(
|
|
|
198
223
|
}, [element, pattern])
|
|
199
224
|
|
|
200
225
|
const handleChangeText = (event) => {
|
|
201
|
-
const { numeric } = variant
|
|
202
226
|
const text = event.nativeEvent?.text || event.target?.value
|
|
203
|
-
|
|
227
|
+
let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') : text
|
|
228
|
+
if (type === 'card' && filteredText) {
|
|
229
|
+
const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '')
|
|
230
|
+
const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g')
|
|
231
|
+
// Add a space every 4 digits starting from the 5th position
|
|
232
|
+
filteredText = formattedValue.replace(regex, '$1 ').trim()
|
|
233
|
+
}
|
|
204
234
|
setValue(filteredText, event)
|
|
205
235
|
if (typeof onChangeText === 'function') onChangeText(filteredText, event)
|
|
206
236
|
}
|
|
@@ -224,7 +254,11 @@ const TextInputBase = forwardRef(
|
|
|
224
254
|
clearButtonIcon: ClearButtonIcon,
|
|
225
255
|
icon: IconComponent,
|
|
226
256
|
passwordShowButtonIcon,
|
|
227
|
-
passwordHideButtonIcon
|
|
257
|
+
passwordHideButtonIcon,
|
|
258
|
+
defaultCreditIcon,
|
|
259
|
+
amexIcon,
|
|
260
|
+
visaIcon,
|
|
261
|
+
masterCardIcon
|
|
228
262
|
} = themeTokens
|
|
229
263
|
const buttonsGapSize = useSpacingScale(buttonsGap)
|
|
230
264
|
const getCopy = useCopy({ dictionary, copy })
|
|
@@ -242,7 +276,7 @@ const TextInputBase = forwardRef(
|
|
|
242
276
|
)
|
|
243
277
|
}
|
|
244
278
|
|
|
245
|
-
if (
|
|
279
|
+
if (isPassword) {
|
|
246
280
|
textInputButtons?.unshift(
|
|
247
281
|
<IconButton
|
|
248
282
|
accessibilityLabel={
|
|
@@ -268,27 +302,41 @@ const TextInputBase = forwardRef(
|
|
|
268
302
|
onMouseOut: handleMouseOut,
|
|
269
303
|
onChange: handleChangeText,
|
|
270
304
|
defaultValue: initialValue,
|
|
305
|
+
maxLength: type === 'card' ? 19 : undefined,
|
|
271
306
|
value: isControlled ? currentValue : undefined
|
|
272
307
|
}
|
|
273
308
|
|
|
274
309
|
const { themeOptions } = useTheme()
|
|
275
|
-
const nativeInputStyle = selectInputStyles(
|
|
310
|
+
const nativeInputStyle = selectInputStyles(
|
|
311
|
+
{ ...themeTokens, height },
|
|
312
|
+
themeOptions,
|
|
313
|
+
inactive,
|
|
314
|
+
type
|
|
315
|
+
)
|
|
276
316
|
|
|
277
317
|
return (
|
|
278
318
|
<View style={selectOuterBorderStyles(themeTokens)}>
|
|
319
|
+
{type === 'card' && (
|
|
320
|
+
<View
|
|
321
|
+
pointerEvents="none"
|
|
322
|
+
style={[staticStyles.leftIconContainer, selectLeftIconContainerStyles(themeTokens)]}
|
|
323
|
+
>
|
|
324
|
+
{getIcon(currentValue, { defaultCreditIcon, amexIcon, visaIcon, masterCardIcon })}
|
|
325
|
+
</View>
|
|
326
|
+
)}
|
|
279
327
|
<NativeTextInput
|
|
280
328
|
ref={inputRef}
|
|
281
|
-
keyboardType={
|
|
282
|
-
inputMode={
|
|
329
|
+
keyboardType={isNumeric && 'numeric'}
|
|
330
|
+
inputMode={isNumeric && 'numeric'}
|
|
283
331
|
style={nativeInputStyle}
|
|
284
|
-
secureTextEntry={
|
|
332
|
+
secureTextEntry={isPassword && !showPassword}
|
|
285
333
|
{...inputProps}
|
|
286
334
|
/>
|
|
287
335
|
{IconComponent && (
|
|
288
336
|
<View
|
|
289
337
|
pointerEvents="none" // avoid hijacking input press events
|
|
290
338
|
style={[
|
|
291
|
-
staticStyles.
|
|
339
|
+
staticStyles.rightIconContainer,
|
|
292
340
|
selectIconContainerStyles({ ...themeTokens, buttonsGapSize }, buttons?.length)
|
|
293
341
|
]}
|
|
294
342
|
>
|
|
@@ -321,6 +369,7 @@ TextInputBase.propTypes = {
|
|
|
321
369
|
clearButtonAccessibilityLabel: PropTypes.string
|
|
322
370
|
})
|
|
323
371
|
]),
|
|
372
|
+
type: PropTypes.oneOfType([PropTypes.oneOf(['password', 'card', 'number'])]),
|
|
324
373
|
height: PropTypes.number,
|
|
325
374
|
inactive: PropTypes.bool,
|
|
326
375
|
initialValue: PropTypes.string,
|
|
@@ -349,9 +398,15 @@ const staticStyles = StyleSheet.create({
|
|
|
349
398
|
bottom: 0,
|
|
350
399
|
justifyContent: 'center'
|
|
351
400
|
},
|
|
352
|
-
|
|
401
|
+
rightIconContainer: {
|
|
353
402
|
position: 'absolute',
|
|
354
403
|
right: 0,
|
|
355
404
|
bottom: 0
|
|
405
|
+
},
|
|
406
|
+
leftIconContainer: {
|
|
407
|
+
position: 'absolute',
|
|
408
|
+
left: 0,
|
|
409
|
+
bottom: 0,
|
|
410
|
+
zIndex: 1
|
|
356
411
|
}
|
|
357
412
|
})
|
|
@@ -8,6 +8,10 @@ const textInputPropTypes = {
|
|
|
8
8
|
* together with the `onChange` to pass down and update the lifted state.
|
|
9
9
|
*/
|
|
10
10
|
value: PropTypes.string,
|
|
11
|
+
/**
|
|
12
|
+
* Use this to set the type of the input. Defaults to `text`.
|
|
13
|
+
*/
|
|
14
|
+
type: PropTypes.string,
|
|
11
15
|
/**
|
|
12
16
|
* Use this to set the initial value of an uncontrolled input.
|
|
13
17
|
* Updating `initialValue` will **not** update the actual value.
|