@telus-uds/components-base 1.29.0 → 1.30.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 CHANGED
@@ -1,12 +1,21 @@
1
1
  # Change Log - @telus-uds/components-base
2
2
 
3
- This log was last generated on Tue, 07 Mar 2023 21:07:23 GMT and should not be manually modified.
3
+ This log was last generated on Mon, 20 Mar 2023 19:41:55 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 1.30.0
8
+
9
+ Mon, 20 Mar 2023 19:41:55 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - Password variant added to PM, koodo, Telus and Rebrand-PM (akshay.pandey1@telus.com)
14
+ - Bump @telus-uds/system-theme-tokens to v2.13.0
15
+
7
16
  ## 1.29.0
8
17
 
9
- Tue, 07 Mar 2023 21:07:23 GMT
18
+ Tue, 07 Mar 2023 21:13:39 GMT
10
19
 
11
20
  ### Minor changes
12
21
 
@@ -886,6 +886,8 @@
886
886
  "borderWidth": "border",
887
887
  "borderColor": "color",
888
888
  "borderRadius": "radius",
889
+ "passwordShowButtonIcon": "icon",
890
+ "passwordHideButtonIcon": "icon",
889
891
  "buttonSize": "size",
890
892
  "buttonsGap": "size",
891
893
  "buttonsPaddingRight": "size",
@@ -9716,6 +9718,55 @@
9716
9718
  }
9717
9719
  }
9718
9720
  },
9721
+ "Validator": {
9722
+ "docs": {
9723
+ "description": "",
9724
+ "props": {
9725
+ "value": {
9726
+ "defaultValue": {
9727
+ "value": "''",
9728
+ "computed": false
9729
+ },
9730
+ "type": {
9731
+ "name": "string"
9732
+ },
9733
+ "required": false,
9734
+ "description": "The value is a 6-digit code, may be only numeric characters, non numeric character aren't renderize"
9735
+ },
9736
+ "tokens": {
9737
+ "defaultValue": {
9738
+ "value": "{}",
9739
+ "computed": false
9740
+ },
9741
+ "required": false
9742
+ },
9743
+ "variant": {
9744
+ "defaultValue": {
9745
+ "value": "{}",
9746
+ "computed": false
9747
+ },
9748
+ "required": false
9749
+ },
9750
+ "inactive": {
9751
+ "type": {
9752
+ "name": "bool"
9753
+ },
9754
+ "required": false,
9755
+ "description": "If true, the component is inactive and non editable."
9756
+ },
9757
+ "onChange": {
9758
+ "type": {
9759
+ "name": "func"
9760
+ },
9761
+ "required": false,
9762
+ "description": "Use to react upon input's value changes. Required when the `value` prop is set. Will receive the input's value as an argument."
9763
+ }
9764
+ },
9765
+ "attributes": {
9766
+ "acceptsRNA11yProps": false
9767
+ }
9768
+ }
9769
+ },
9719
9770
  "ViewportProvider": {
9720
9771
  "docs": {
9721
9772
  "description": "Provides an up-to-date viewport value from system-constants, available via the `useViewport` hook",
@@ -13061,6 +13112,8 @@
13061
13112
  "borderWidth": "border",
13062
13113
  "borderColor": "color",
13063
13114
  "borderRadius": "radius",
13115
+ "passwordShowButtonIcon": "icon",
13116
+ "passwordHideButtonIcon": "icon",
13064
13117
  "buttonSize": "size",
13065
13118
  "buttonsGap": "size",
13066
13119
  "buttonsPaddingRight": "size",
@@ -13170,6 +13223,8 @@
13170
13223
  "borderWidth": "border",
13171
13224
  "borderColor": "color",
13172
13225
  "borderRadius": "radius",
13226
+ "passwordShowButtonIcon": "icon",
13227
+ "passwordHideButtonIcon": "icon",
13173
13228
  "buttonSize": "size",
13174
13229
  "buttonsGap": "size",
13175
13230
  "buttonsPaddingRight": "size",
@@ -183,6 +183,7 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
183
183
  ...rest
184
184
  } = _ref6;
185
185
  const [isFocused, setIsFocused] = (0, _react.useState)(false);
186
+ const [showPassword, setShowPassword] = (0, _react.useState)(false);
186
187
 
187
188
  const handleFocus = event => {
188
189
  setIsFocused(true);
@@ -244,27 +245,35 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
244
245
  inactive
245
246
  };
246
247
  const themeTokens = (0, _ThemeProvider.useThemeTokens)('TextInput', tokens, variant, states);
248
+
249
+ const handleClear = event => {
250
+ var _inputRef$current;
251
+
252
+ onClear === null || onClear === void 0 ? void 0 : onClear(event);
253
+ resetValue(event);
254
+ inputRef === null || inputRef === void 0 ? void 0 : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
255
+ };
256
+
257
+ const handleShowOrHide = () => {
258
+ if (!variant.inactive) setShowPassword(!showPassword);
259
+ };
260
+
247
261
  const {
248
262
  buttonsGap,
249
263
  clearButtonIcon: ClearButtonIcon,
250
- icon: IconComponent
264
+ icon: IconComponent,
265
+ passwordShowButtonIcon,
266
+ passwordHideButtonIcon
251
267
  } = themeTokens;
252
268
  const buttonsGapSize = (0, _utils.useSpacingScale)(buttonsGap);
253
269
  const getCopy = (0, _utils.useCopy)({
254
270
  dictionary: _dictionary.default,
255
271
  copy
256
272
  });
273
+ const textInputButtons = buttons;
257
274
 
258
275
  if (onClear && isDirty) {
259
- const handleClear = event => {
260
- var _inputRef$current;
261
-
262
- onClear === null || onClear === void 0 ? void 0 : onClear(event);
263
- resetValue(event);
264
- inputRef === null || inputRef === void 0 ? void 0 : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
265
- };
266
-
267
- buttons === null || buttons === void 0 ? void 0 : buttons.unshift( /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
276
+ textInputButtons === null || textInputButtons === void 0 ? void 0 : textInputButtons.unshift( /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
268
277
  accessibilityLabel: getCopy('clearButtonAccessibilityLabel'),
269
278
  icon: ClearButtonIcon,
270
279
  onPress: handleClear,
@@ -274,6 +283,19 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
274
283
  }, "clear"));
275
284
  }
276
285
 
286
+ if (variant.password) {
287
+ textInputButtons === null || textInputButtons === void 0 ? void 0 : textInputButtons.unshift( /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
288
+ accessibilityLabel: !showPassword ? getCopy('hidePasswordAccessibilityLabel') : getCopy('showPasswordAccessibilityLabel'),
289
+ icon: !showPassword ? passwordShowButtonIcon : passwordHideButtonIcon,
290
+ onPress: handleShowOrHide,
291
+ variant: {
292
+ compact: true,
293
+ password: true,
294
+ inactive: !!variant.inactive
295
+ }
296
+ }, !showPassword ? 'hide' : 'show'));
297
+ }
298
+
277
299
  const inputProps = { ...selectProps(rest),
278
300
  editable: !inactive,
279
301
  onFocus: handleFocus,
@@ -297,6 +319,7 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
297
319
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.default, {
298
320
  ref: inputRef,
299
321
  style: nativeInputStyle,
322
+ secureTextEntry: variant.password && !showPassword,
300
323
  ...inputProps
301
324
  }), IconComponent && /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
302
325
  pointerEvents: "none" // avoid hijacking input press events
@@ -311,7 +334,7 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref6, ref) => {
311
334
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_StackView.default, {
312
335
  direction: "row",
313
336
  space: buttonsGap,
314
- children: buttons
337
+ children: textInputButtons
315
338
  })
316
339
  })]
317
340
  });
@@ -6,10 +6,14 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _default = {
8
8
  en: {
9
- clearButtonAccessibilityLabel: 'Clear'
9
+ clearButtonAccessibilityLabel: 'Clear',
10
+ showPasswordAccessibilityLabel: 'Show Password',
11
+ hidePasswordAccessibilityLabel: 'Hide Password'
10
12
  },
11
13
  fr: {
12
- clearButtonAccessibilityLabel: 'Effacer'
14
+ clearButtonAccessibilityLabel: 'Effacer',
15
+ showPasswordAccessibilityLabel: 'montrer le mot de passe',
16
+ hidePasswordAccessibilityLabel: 'masquer le mot de passe'
13
17
  }
14
18
  };
15
19
  exports.default = _default;
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _react = _interopRequireWildcard(require("react"));
9
+
10
+ var _propTypes = _interopRequireDefault(require("prop-types"));
11
+
12
+ var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/View"));
13
+
14
+ var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
15
+
16
+ var _Platform = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Platform"));
17
+
18
+ var _utils = require("../utils");
19
+
20
+ var _TextInput = require("../TextInput");
21
+
22
+ var _StackView = _interopRequireDefault(require("../StackView"));
23
+
24
+ var _InputSupports = _interopRequireDefault(require("../InputSupports"));
25
+
26
+ var _ThemeProvider = require("../ThemeProvider");
27
+
28
+ var _jsxRuntime = require("react/jsx-runtime");
29
+
30
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
31
+
32
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
33
+
34
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
35
+
36
+ const [selectProps, selectedSystemPropTypes] = (0, _utils.selectSystemProps)([_utils.inputSupportsProps]);
37
+
38
+ const selectCodeTextInputTokens = _ref => {
39
+ let {
40
+ outerBorderColor,
41
+ outerBackgroundColor
42
+ } = _ref;
43
+ return {
44
+ outerBorderColor,
45
+ outerBackgroundColor,
46
+ icon: null
47
+ };
48
+ };
49
+
50
+ const Validator = /*#__PURE__*/(0, _react.forwardRef)((_ref2, ref) => {
51
+ let {
52
+ value = '',
53
+ inactive,
54
+ onChange,
55
+ tokens = {},
56
+ variant = {},
57
+ ...rest
58
+ } = _ref2;
59
+ const defaultRef = (0, _react.useRef)();
60
+ const codeRef = ref !== null && ref !== void 0 ? ref : defaultRef;
61
+ const {
62
+ supportsProps
63
+ } = selectProps(rest);
64
+ const strValidation = supportsProps.validation;
65
+ const [individualCodes, setIndividualCodes] = (0, _react.useState)({});
66
+ const [text, setText] = (0, _react.useState)(value);
67
+ const validatorsLength = 6;
68
+ const prefix = 'code';
69
+ const sufixValidation = 'Validation';
70
+ const [isHover, setIsHover] = (0, _react.useState)(false);
71
+
72
+ const handleMouseOver = () => {
73
+ setIsHover(true);
74
+ };
75
+
76
+ const handleMouseOut = () => {
77
+ setIsHover(false);
78
+ };
79
+
80
+ const themeTokens = (0, _ThemeProvider.useThemeTokens)('TextInput', tokens, variant, {
81
+ hover: isHover
82
+ });
83
+ const [codeReferences, singleCodes] = (0, _react.useMemo)(() => {
84
+ const codes = [];
85
+ const valueCodes = {};
86
+
87
+ for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
88
+ codes[prefix + i] = /*#__PURE__*/(0, _react.createRef)();
89
+ valueCodes[prefix + i] = '';
90
+ valueCodes[prefix + i + sufixValidation] = '';
91
+ }
92
+
93
+ return [codes, valueCodes];
94
+ }, []);
95
+
96
+ const handleSingleCodes = (codeId, val, validation) => {
97
+ singleCodes[codeId] = val;
98
+ singleCodes[codeId + sufixValidation] = validation;
99
+ /* eslint-disable no-unused-expressions */
100
+
101
+ setIndividualCodes({ ...individualCodes,
102
+ [codeId]: val
103
+ });
104
+ };
105
+
106
+ const handleChangeCode = () => {
107
+ let code = '';
108
+
109
+ for (let i = 0; i < validatorsLength; i += 1) code += singleCodes[prefix + i];
110
+
111
+ if (typeof onChange === 'function') onChange(code, singleCodes);
112
+ };
113
+
114
+ const handleChangeCodeValues = (event, codeId, nextIndex) => {
115
+ var _codeReferences$codeI, _event$nativeEvent, _event$target, _codeElement$value, _codeElement$value2, _codeElement$value3;
116
+
117
+ const codeElement = (_codeReferences$codeI = codeReferences[codeId]) === null || _codeReferences$codeI === void 0 ? void 0 : _codeReferences$codeI.current;
118
+ const val = ((_event$nativeEvent = event.nativeEvent) === null || _event$nativeEvent === void 0 ? void 0 : _event$nativeEvent.value) || ((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.value);
119
+
120
+ if (Number(val).toString() === 'NaN') {
121
+ var _singleCodes$codeId;
122
+
123
+ codeElement.value = (_singleCodes$codeId = singleCodes[codeId]) !== null && _singleCodes$codeId !== void 0 ? _singleCodes$codeId : '';
124
+ return;
125
+ }
126
+
127
+ if ((codeElement === null || codeElement === void 0 ? void 0 : (_codeElement$value = codeElement.value) === null || _codeElement$value === void 0 ? void 0 : _codeElement$value.length) > 1) {
128
+ const oldValue = singleCodes[codeId];
129
+ const newValue = codeElement.value.replace(oldValue, '');
130
+ codeElement.value = newValue;
131
+ handleSingleCodes(codeId, codeElement.value, 'success');
132
+ }
133
+
134
+ handleSingleCodes(codeId, (_codeElement$value2 = codeElement === null || codeElement === void 0 ? void 0 : codeElement.value) !== null && _codeElement$value2 !== void 0 ? _codeElement$value2 : singleCodes[codeId], 'success');
135
+ handleChangeCode();
136
+
137
+ if (nextIndex === validatorsLength) {
138
+ codeElement.blur();
139
+ return;
140
+ }
141
+
142
+ if ((codeElement === null || codeElement === void 0 ? void 0 : (_codeElement$value3 = codeElement.value) === null || _codeElement$value3 === void 0 ? void 0 : _codeElement$value3.length) > 0) codeReferences[prefix + nextIndex].current.focus();
143
+ };
144
+
145
+ const handleKeyPress = (event, currentIndex, previousIndex) => {
146
+ if (!(event.keyCode === 8 || event.code === 'Backspace')) return;
147
+
148
+ if (currentIndex > 0) {
149
+ codeReferences[prefix + currentIndex].current.value = '';
150
+ codeReferences[prefix + previousIndex].current.focus();
151
+ }
152
+
153
+ handleSingleCodes(prefix + currentIndex, '', '');
154
+ handleChangeCode();
155
+ };
156
+
157
+ const getCodeComponents = () => {
158
+ const components = [];
159
+
160
+ for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
161
+ var _codeReferences$codeI2;
162
+
163
+ const codeId = prefix + i;
164
+ const codeInputProps = {
165
+ nativeID: codeId,
166
+ ref: (_codeReferences$codeI2 = codeReferences[codeId]) !== null && _codeReferences$codeI2 !== void 0 ? _codeReferences$codeI2 : null,
167
+ validation: strValidation || singleCodes[codeId + sufixValidation],
168
+ tokens: selectCodeTextInputTokens(themeTokens),
169
+ onFocus: () => codeReferences[codeId].current.select(),
170
+ onKeyPress: event => handleKeyPress(event, i, i - 1),
171
+ onMouseOver: handleMouseOver,
172
+ onMouseOut: handleMouseOut,
173
+ inactive
174
+ };
175
+ codeInputProps.validation || delete codeInputProps.validation;
176
+ components.push( /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
177
+ style: staticStyles.codeInputWidth,
178
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.TextInput, { ...codeInputProps
179
+ })
180
+ }, codeId));
181
+ }
182
+
183
+ return components;
184
+ };
185
+
186
+ (0, _react.useEffect)(() => {
187
+ /* eslint-disable no-unused-expressions */
188
+ if (Number(value).toString() !== 'NaN') setText(value);
189
+ }, [value]);
190
+ /* eslint-disable react-hooks/exhaustive-deps */
191
+
192
+ (0, _react.useEffect)(() => {
193
+ for (let i = 0; i < validatorsLength; i += 1) {
194
+ var _text$i, _text$i2;
195
+
196
+ codeReferences[prefix + i].current.value = (_text$i = text[i]) !== null && _text$i !== void 0 ? _text$i : '';
197
+ handleSingleCodes(prefix + i, (_text$i2 = text[i]) !== null && _text$i2 !== void 0 ? _text$i2 : '', text[i] ? 'success' : '');
198
+ }
199
+ }, [text]);
200
+ /* eslint-disable react-hooks/exhaustive-deps */
201
+
202
+ (0, _react.useEffect)(() => {
203
+ const handlePasteCode = event => {
204
+ setText('');
205
+ const clipBoardText = event.clipboardData.getData('text');
206
+ if (Number(clipBoardText).toString() !== 'NaN') setText(clipBoardText);
207
+ };
208
+
209
+ const handleCopy = event => {
210
+ let clipBoardText = '';
211
+
212
+ for (let i = 0; i < validatorsLength; i += 1) singleCodes[prefix + i] && (clipBoardText += singleCodes[prefix + i]);
213
+
214
+ event.clipboardData.setData('text/plain', clipBoardText);
215
+ event.preventDefault();
216
+ };
217
+
218
+ if (_Platform.default.OS === 'web') {
219
+ for (let i = 0; i < validatorsLength; i += 1) {
220
+ codeReferences[prefix + i].current.addEventListener('paste', handlePasteCode);
221
+ codeReferences[prefix + i].current.addEventListener('copy', handleCopy);
222
+ codeReferences[prefix + i].current.addEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
223
+ }
224
+ }
225
+
226
+ return () => {
227
+ if (_Platform.default.oldValue === 'web') {
228
+ for (let i = 0; i < validatorsLength; i += 1) {
229
+ var _codeReferences, _codeReferences$curre, _codeReferences2, _codeReferences2$curr, _codeReferences3, _codeReferences3$curr;
230
+
231
+ (_codeReferences = codeReferences[prefix + i]) === null || _codeReferences === void 0 ? void 0 : (_codeReferences$curre = _codeReferences.current) === null || _codeReferences$curre === void 0 ? void 0 : _codeReferences$curre.removeEventListener('paste', handlePasteCode);
232
+ (_codeReferences2 = codeReferences[prefix + i]) === null || _codeReferences2 === void 0 ? void 0 : (_codeReferences2$curr = _codeReferences2.current) === null || _codeReferences2$curr === void 0 ? void 0 : _codeReferences2$curr.removeEventListener('copy', handleCopy);
233
+ (_codeReferences3 = codeReferences[prefix + i]) === null || _codeReferences3 === void 0 ? void 0 : (_codeReferences3$curr = _codeReferences3.current) === null || _codeReferences3$curr === void 0 ? void 0 : _codeReferences3$curr.removeEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
234
+ }
235
+ }
236
+ };
237
+ }, []);
238
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_InputSupports.default, { ...supportsProps,
239
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_StackView.default, {
240
+ space: 2,
241
+ direction: "row",
242
+ ref: codeRef,
243
+ children: getCodeComponents()
244
+ })
245
+ });
246
+ });
247
+ Validator.displayName = 'Validator';
248
+ Validator.propTypes = { ...selectedSystemPropTypes,
249
+
250
+ /**
251
+ * The value is a 6-digit code, may be only numeric characters, non numeric character aren't renderize
252
+ */
253
+ value: _propTypes.default.string,
254
+
255
+ /**
256
+ * If true, the component is inactive and non editable.
257
+ */
258
+ inactive: _propTypes.default.bool,
259
+
260
+ /**
261
+ * Use to react upon input's value changes. Required when the `value` prop is set. Will receive the input's value as an argument.
262
+ */
263
+ onChange: _propTypes.default.func
264
+ };
265
+ var _default = Validator;
266
+ exports.default = _default;
267
+
268
+ const staticStyles = _StyleSheet.default.create({
269
+ codeInputWidth: {
270
+ width: 43
271
+ }
272
+ });
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _Validator = _interopRequireDefault(require("./Validator"));
9
+
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
+
12
+ var _default = _Validator.default;
13
+ exports.default = _default;
package/lib/index.js CHANGED
@@ -52,6 +52,7 @@ var _exportNames = {
52
52
  useA11yInfo: true,
53
53
  BaseProvider: true,
54
54
  useHydrationContext: true,
55
+ Validator: true,
55
56
  ViewportProvider: true,
56
57
  useViewport: true,
57
58
  ViewportContext: true,
@@ -353,6 +354,12 @@ Object.defineProperty(exports, "Typography", {
353
354
  return _Typography.default;
354
355
  }
355
356
  });
357
+ Object.defineProperty(exports, "Validator", {
358
+ enumerable: true,
359
+ get: function () {
360
+ return _Validator.default;
361
+ }
362
+ });
356
363
  Object.defineProperty(exports, "ViewportContext", {
357
364
  enumerable: true,
358
365
  get: function () {
@@ -654,6 +661,8 @@ var _BaseProvider = _interopRequireDefault(require("./BaseProvider"));
654
661
 
655
662
  var _HydrationContext = require("./BaseProvider/HydrationContext");
656
663
 
664
+ var _Validator = _interopRequireDefault(require("./Validator"));
665
+
657
666
  var _ViewportProvider = _interopRequireWildcard(require("./ViewportProvider"));
658
667
 
659
668
  var _ThemeProvider = _interopRequireWildcard(require("./ThemeProvider"));
@@ -88,7 +88,12 @@ const textInputHandlerProps = {
88
88
  /**
89
89
  * onKeyDown handler (only supported on Web)
90
90
  */
91
- onKeyDown: _propTypes.default.func
91
+ onMouseOver: _propTypes.default.func,
92
+
93
+ /**
94
+ * onKeyDown handler (only supported on Web)
95
+ */
96
+ onMouseOut: _propTypes.default.func
92
97
  }
93
98
  };
94
99
  exports.textInputHandlerProps = textInputHandlerProps;
@@ -157,6 +157,7 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
157
157
  ...rest
158
158
  } = _ref6;
159
159
  const [isFocused, setIsFocused] = useState(false);
160
+ const [showPassword, setShowPassword] = useState(false);
160
161
 
161
162
  const handleFocus = event => {
162
163
  setIsFocused(true);
@@ -218,27 +219,35 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
218
219
  inactive
219
220
  };
220
221
  const themeTokens = useThemeTokens('TextInput', tokens, variant, states);
222
+
223
+ const handleClear = event => {
224
+ var _inputRef$current;
225
+
226
+ onClear === null || onClear === void 0 ? void 0 : onClear(event);
227
+ resetValue(event);
228
+ inputRef === null || inputRef === void 0 ? void 0 : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
229
+ };
230
+
231
+ const handleShowOrHide = () => {
232
+ if (!variant.inactive) setShowPassword(!showPassword);
233
+ };
234
+
221
235
  const {
222
236
  buttonsGap,
223
237
  clearButtonIcon: ClearButtonIcon,
224
- icon: IconComponent
238
+ icon: IconComponent,
239
+ passwordShowButtonIcon,
240
+ passwordHideButtonIcon
225
241
  } = themeTokens;
226
242
  const buttonsGapSize = useSpacingScale(buttonsGap);
227
243
  const getCopy = useCopy({
228
244
  dictionary,
229
245
  copy
230
246
  });
247
+ const textInputButtons = buttons;
231
248
 
232
249
  if (onClear && isDirty) {
233
- const handleClear = event => {
234
- var _inputRef$current;
235
-
236
- onClear === null || onClear === void 0 ? void 0 : onClear(event);
237
- resetValue(event);
238
- inputRef === null || inputRef === void 0 ? void 0 : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
239
- };
240
-
241
- buttons === null || buttons === void 0 ? void 0 : buttons.unshift( /*#__PURE__*/_jsx(IconButton, {
250
+ textInputButtons === null || textInputButtons === void 0 ? void 0 : textInputButtons.unshift( /*#__PURE__*/_jsx(IconButton, {
242
251
  accessibilityLabel: getCopy('clearButtonAccessibilityLabel'),
243
252
  icon: ClearButtonIcon,
244
253
  onPress: handleClear,
@@ -248,6 +257,19 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
248
257
  }, "clear"));
249
258
  }
250
259
 
260
+ if (variant.password) {
261
+ textInputButtons === null || textInputButtons === void 0 ? void 0 : textInputButtons.unshift( /*#__PURE__*/_jsx(IconButton, {
262
+ accessibilityLabel: !showPassword ? getCopy('hidePasswordAccessibilityLabel') : getCopy('showPasswordAccessibilityLabel'),
263
+ icon: !showPassword ? passwordShowButtonIcon : passwordHideButtonIcon,
264
+ onPress: handleShowOrHide,
265
+ variant: {
266
+ compact: true,
267
+ password: true,
268
+ inactive: !!variant.inactive
269
+ }
270
+ }, !showPassword ? 'hide' : 'show'));
271
+ }
272
+
251
273
  const inputProps = { ...selectProps(rest),
252
274
  editable: !inactive,
253
275
  onFocus: handleFocus,
@@ -271,6 +293,7 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
271
293
  children: [/*#__PURE__*/_jsx(NativeTextInput, {
272
294
  ref: inputRef,
273
295
  style: nativeInputStyle,
296
+ secureTextEntry: variant.password && !showPassword,
274
297
  ...inputProps
275
298
  }), IconComponent && /*#__PURE__*/_jsx(View, {
276
299
  pointerEvents: "none" // avoid hijacking input press events
@@ -285,7 +308,7 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
285
308
  children: /*#__PURE__*/_jsx(StackView, {
286
309
  direction: "row",
287
310
  space: buttonsGap,
288
- children: buttons
311
+ children: textInputButtons
289
312
  })
290
313
  })]
291
314
  });
@@ -1,8 +1,12 @@
1
1
  export default {
2
2
  en: {
3
- clearButtonAccessibilityLabel: 'Clear'
3
+ clearButtonAccessibilityLabel: 'Clear',
4
+ showPasswordAccessibilityLabel: 'Show Password',
5
+ hidePasswordAccessibilityLabel: 'Hide Password'
4
6
  },
5
7
  fr: {
6
- clearButtonAccessibilityLabel: 'Effacer'
8
+ clearButtonAccessibilityLabel: 'Effacer',
9
+ showPasswordAccessibilityLabel: 'montrer le mot de passe',
10
+ hidePasswordAccessibilityLabel: 'masquer le mot de passe'
7
11
  }
8
12
  };
@@ -0,0 +1,246 @@
1
+ import React, { forwardRef, createRef, useRef, useMemo, useEffect, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import View from "react-native-web/dist/exports/View";
4
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
5
+ import Platform from "react-native-web/dist/exports/Platform";
6
+ import { inputSupportsProps, selectSystemProps } from '../utils';
7
+ import { TextInput } from '../TextInput';
8
+ import StackView from '../StackView';
9
+ import InputSupports from '../InputSupports';
10
+ import { useThemeTokens } from '../ThemeProvider';
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([inputSupportsProps]);
13
+
14
+ const selectCodeTextInputTokens = _ref => {
15
+ let {
16
+ outerBorderColor,
17
+ outerBackgroundColor
18
+ } = _ref;
19
+ return {
20
+ outerBorderColor,
21
+ outerBackgroundColor,
22
+ icon: null
23
+ };
24
+ };
25
+
26
+ const Validator = /*#__PURE__*/forwardRef((_ref2, ref) => {
27
+ let {
28
+ value = '',
29
+ inactive,
30
+ onChange,
31
+ tokens = {},
32
+ variant = {},
33
+ ...rest
34
+ } = _ref2;
35
+ const defaultRef = useRef();
36
+ const codeRef = ref !== null && ref !== void 0 ? ref : defaultRef;
37
+ const {
38
+ supportsProps
39
+ } = selectProps(rest);
40
+ const strValidation = supportsProps.validation;
41
+ const [individualCodes, setIndividualCodes] = useState({});
42
+ const [text, setText] = useState(value);
43
+ const validatorsLength = 6;
44
+ const prefix = 'code';
45
+ const sufixValidation = 'Validation';
46
+ const [isHover, setIsHover] = useState(false);
47
+
48
+ const handleMouseOver = () => {
49
+ setIsHover(true);
50
+ };
51
+
52
+ const handleMouseOut = () => {
53
+ setIsHover(false);
54
+ };
55
+
56
+ const themeTokens = useThemeTokens('TextInput', tokens, variant, {
57
+ hover: isHover
58
+ });
59
+ const [codeReferences, singleCodes] = useMemo(() => {
60
+ const codes = [];
61
+ const valueCodes = {};
62
+
63
+ for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
64
+ codes[prefix + i] = /*#__PURE__*/createRef();
65
+ valueCodes[prefix + i] = '';
66
+ valueCodes[prefix + i + sufixValidation] = '';
67
+ }
68
+
69
+ return [codes, valueCodes];
70
+ }, []);
71
+
72
+ const handleSingleCodes = (codeId, val, validation) => {
73
+ singleCodes[codeId] = val;
74
+ singleCodes[codeId + sufixValidation] = validation;
75
+ /* eslint-disable no-unused-expressions */
76
+
77
+ setIndividualCodes({ ...individualCodes,
78
+ [codeId]: val
79
+ });
80
+ };
81
+
82
+ const handleChangeCode = () => {
83
+ let code = '';
84
+
85
+ for (let i = 0; i < validatorsLength; i += 1) code += singleCodes[prefix + i];
86
+
87
+ if (typeof onChange === 'function') onChange(code, singleCodes);
88
+ };
89
+
90
+ const handleChangeCodeValues = (event, codeId, nextIndex) => {
91
+ var _codeReferences$codeI, _event$nativeEvent, _event$target, _codeElement$value, _codeElement$value2, _codeElement$value3;
92
+
93
+ const codeElement = (_codeReferences$codeI = codeReferences[codeId]) === null || _codeReferences$codeI === void 0 ? void 0 : _codeReferences$codeI.current;
94
+ const val = ((_event$nativeEvent = event.nativeEvent) === null || _event$nativeEvent === void 0 ? void 0 : _event$nativeEvent.value) || ((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.value);
95
+
96
+ if (Number(val).toString() === 'NaN') {
97
+ var _singleCodes$codeId;
98
+
99
+ codeElement.value = (_singleCodes$codeId = singleCodes[codeId]) !== null && _singleCodes$codeId !== void 0 ? _singleCodes$codeId : '';
100
+ return;
101
+ }
102
+
103
+ if ((codeElement === null || codeElement === void 0 ? void 0 : (_codeElement$value = codeElement.value) === null || _codeElement$value === void 0 ? void 0 : _codeElement$value.length) > 1) {
104
+ const oldValue = singleCodes[codeId];
105
+ const newValue = codeElement.value.replace(oldValue, '');
106
+ codeElement.value = newValue;
107
+ handleSingleCodes(codeId, codeElement.value, 'success');
108
+ }
109
+
110
+ handleSingleCodes(codeId, (_codeElement$value2 = codeElement === null || codeElement === void 0 ? void 0 : codeElement.value) !== null && _codeElement$value2 !== void 0 ? _codeElement$value2 : singleCodes[codeId], 'success');
111
+ handleChangeCode();
112
+
113
+ if (nextIndex === validatorsLength) {
114
+ codeElement.blur();
115
+ return;
116
+ }
117
+
118
+ if ((codeElement === null || codeElement === void 0 ? void 0 : (_codeElement$value3 = codeElement.value) === null || _codeElement$value3 === void 0 ? void 0 : _codeElement$value3.length) > 0) codeReferences[prefix + nextIndex].current.focus();
119
+ };
120
+
121
+ const handleKeyPress = (event, currentIndex, previousIndex) => {
122
+ if (!(event.keyCode === 8 || event.code === 'Backspace')) return;
123
+
124
+ if (currentIndex > 0) {
125
+ codeReferences[prefix + currentIndex].current.value = '';
126
+ codeReferences[prefix + previousIndex].current.focus();
127
+ }
128
+
129
+ handleSingleCodes(prefix + currentIndex, '', '');
130
+ handleChangeCode();
131
+ };
132
+
133
+ const getCodeComponents = () => {
134
+ const components = [];
135
+
136
+ for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
137
+ var _codeReferences$codeI2;
138
+
139
+ const codeId = prefix + i;
140
+ const codeInputProps = {
141
+ nativeID: codeId,
142
+ ref: (_codeReferences$codeI2 = codeReferences[codeId]) !== null && _codeReferences$codeI2 !== void 0 ? _codeReferences$codeI2 : null,
143
+ validation: strValidation || singleCodes[codeId + sufixValidation],
144
+ tokens: selectCodeTextInputTokens(themeTokens),
145
+ onFocus: () => codeReferences[codeId].current.select(),
146
+ onKeyPress: event => handleKeyPress(event, i, i - 1),
147
+ onMouseOver: handleMouseOver,
148
+ onMouseOut: handleMouseOut,
149
+ inactive
150
+ };
151
+ codeInputProps.validation || delete codeInputProps.validation;
152
+ components.push( /*#__PURE__*/_jsx(View, {
153
+ style: staticStyles.codeInputWidth,
154
+ children: /*#__PURE__*/_jsx(TextInput, { ...codeInputProps
155
+ })
156
+ }, codeId));
157
+ }
158
+
159
+ return components;
160
+ };
161
+
162
+ useEffect(() => {
163
+ /* eslint-disable no-unused-expressions */
164
+ if (Number(value).toString() !== 'NaN') setText(value);
165
+ }, [value]);
166
+ /* eslint-disable react-hooks/exhaustive-deps */
167
+
168
+ useEffect(() => {
169
+ for (let i = 0; i < validatorsLength; i += 1) {
170
+ var _text$i, _text$i2;
171
+
172
+ codeReferences[prefix + i].current.value = (_text$i = text[i]) !== null && _text$i !== void 0 ? _text$i : '';
173
+ handleSingleCodes(prefix + i, (_text$i2 = text[i]) !== null && _text$i2 !== void 0 ? _text$i2 : '', text[i] ? 'success' : '');
174
+ }
175
+ }, [text]);
176
+ /* eslint-disable react-hooks/exhaustive-deps */
177
+
178
+ useEffect(() => {
179
+ const handlePasteCode = event => {
180
+ setText('');
181
+ const clipBoardText = event.clipboardData.getData('text');
182
+ if (Number(clipBoardText).toString() !== 'NaN') setText(clipBoardText);
183
+ };
184
+
185
+ const handleCopy = event => {
186
+ let clipBoardText = '';
187
+
188
+ for (let i = 0; i < validatorsLength; i += 1) singleCodes[prefix + i] && (clipBoardText += singleCodes[prefix + i]);
189
+
190
+ event.clipboardData.setData('text/plain', clipBoardText);
191
+ event.preventDefault();
192
+ };
193
+
194
+ if (Platform.OS === 'web') {
195
+ for (let i = 0; i < validatorsLength; i += 1) {
196
+ codeReferences[prefix + i].current.addEventListener('paste', handlePasteCode);
197
+ codeReferences[prefix + i].current.addEventListener('copy', handleCopy);
198
+ codeReferences[prefix + i].current.addEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
199
+ }
200
+ }
201
+
202
+ return () => {
203
+ if (Platform.oldValue === 'web') {
204
+ for (let i = 0; i < validatorsLength; i += 1) {
205
+ var _codeReferences, _codeReferences$curre, _codeReferences2, _codeReferences2$curr, _codeReferences3, _codeReferences3$curr;
206
+
207
+ (_codeReferences = codeReferences[prefix + i]) === null || _codeReferences === void 0 ? void 0 : (_codeReferences$curre = _codeReferences.current) === null || _codeReferences$curre === void 0 ? void 0 : _codeReferences$curre.removeEventListener('paste', handlePasteCode);
208
+ (_codeReferences2 = codeReferences[prefix + i]) === null || _codeReferences2 === void 0 ? void 0 : (_codeReferences2$curr = _codeReferences2.current) === null || _codeReferences2$curr === void 0 ? void 0 : _codeReferences2$curr.removeEventListener('copy', handleCopy);
209
+ (_codeReferences3 = codeReferences[prefix + i]) === null || _codeReferences3 === void 0 ? void 0 : (_codeReferences3$curr = _codeReferences3.current) === null || _codeReferences3$curr === void 0 ? void 0 : _codeReferences3$curr.removeEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
210
+ }
211
+ }
212
+ };
213
+ }, []);
214
+ return /*#__PURE__*/_jsx(InputSupports, { ...supportsProps,
215
+ children: /*#__PURE__*/_jsx(StackView, {
216
+ space: 2,
217
+ direction: "row",
218
+ ref: codeRef,
219
+ children: getCodeComponents()
220
+ })
221
+ });
222
+ });
223
+ Validator.displayName = 'Validator';
224
+ Validator.propTypes = { ...selectedSystemPropTypes,
225
+
226
+ /**
227
+ * The value is a 6-digit code, may be only numeric characters, non numeric character aren't renderize
228
+ */
229
+ value: PropTypes.string,
230
+
231
+ /**
232
+ * If true, the component is inactive and non editable.
233
+ */
234
+ inactive: PropTypes.bool,
235
+
236
+ /**
237
+ * Use to react upon input's value changes. Required when the `value` prop is set. Will receive the input's value as an argument.
238
+ */
239
+ onChange: PropTypes.func
240
+ };
241
+ export default Validator;
242
+ const staticStyles = StyleSheet.create({
243
+ codeInputWidth: {
244
+ width: 43
245
+ }
246
+ });
@@ -0,0 +1,2 @@
1
+ import Validator from './Validator';
2
+ export default Validator;
@@ -52,6 +52,7 @@ export { default as Typography } from './Typography';
52
52
  export { default as A11yInfoProvider, useA11yInfo } from './A11yInfoProvider';
53
53
  export { default as BaseProvider } from './BaseProvider';
54
54
  export { useHydrationContext } from './BaseProvider/HydrationContext';
55
+ export { default as Validator } from './Validator';
55
56
  export { default as ViewportProvider, useViewport, ViewportContext } from './ViewportProvider';
56
57
  export { default as ThemeProvider, useTheme, useSetTheme, useThemeTokens, getThemeTokens, applyOuterBorder, applyTextStyles, applyShadowToken } from './ThemeProvider';
57
58
  export * from './utils';
@@ -75,7 +75,12 @@ const textInputHandlerProps = {
75
75
  /**
76
76
  * onKeyDown handler (only supported on Web)
77
77
  */
78
- onKeyDown: PropTypes.func
78
+ onMouseOver: PropTypes.func,
79
+
80
+ /**
81
+ * onKeyDown handler (only supported on Web)
82
+ */
83
+ onMouseOut: PropTypes.func
79
84
  }
80
85
  };
81
86
  const selectTextInputHandlers = getPropSelector(textInputHandlerProps.types);
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.2.0",
14
- "@telus-uds/system-theme-tokens": "^2.12.0",
14
+ "@telus-uds/system-theme-tokens": "^2.13.0",
15
15
  "airbnb-prop-types": "^2.16.0",
16
16
  "lodash.debounce": "^4.0.8",
17
17
  "lodash.merge": "^4.6.2",
@@ -72,5 +72,5 @@
72
72
  "standard-engine": {
73
73
  "skip": true
74
74
  },
75
- "version": "1.29.0"
75
+ "version": "1.30.0"
76
76
  }
@@ -157,6 +157,7 @@ const TextInputBase = forwardRef(
157
157
  ref
158
158
  ) => {
159
159
  const [isFocused, setIsFocused] = useState(false)
160
+ const [showPassword, setShowPassword] = useState(false)
160
161
  const handleFocus = (event) => {
161
162
  setIsFocused(true)
162
163
  if (typeof onFocus === 'function') onFocus(event)
@@ -206,16 +207,29 @@ const TextInputBase = forwardRef(
206
207
 
207
208
  const themeTokens = useThemeTokens('TextInput', tokens, variant, states)
208
209
 
209
- const { buttonsGap, clearButtonIcon: ClearButtonIcon, icon: IconComponent } = themeTokens
210
+ const handleClear = (event) => {
211
+ onClear?.(event)
212
+ resetValue(event)
213
+ inputRef?.current?.focus()
214
+ }
215
+
216
+ const handleShowOrHide = () => {
217
+ if (!variant.inactive) setShowPassword(!showPassword)
218
+ }
219
+
220
+ const {
221
+ buttonsGap,
222
+ clearButtonIcon: ClearButtonIcon,
223
+ icon: IconComponent,
224
+ passwordShowButtonIcon,
225
+ passwordHideButtonIcon
226
+ } = themeTokens
210
227
  const buttonsGapSize = useSpacingScale(buttonsGap)
211
228
  const getCopy = useCopy({ dictionary, copy })
229
+ const textInputButtons = buttons
230
+
212
231
  if (onClear && isDirty) {
213
- const handleClear = (event) => {
214
- onClear?.(event)
215
- resetValue(event)
216
- inputRef?.current?.focus()
217
- }
218
- buttons?.unshift(
232
+ textInputButtons?.unshift(
219
233
  <IconButton
220
234
  accessibilityLabel={getCopy('clearButtonAccessibilityLabel')}
221
235
  icon={ClearButtonIcon}
@@ -226,6 +240,22 @@ const TextInputBase = forwardRef(
226
240
  )
227
241
  }
228
242
 
243
+ if (variant.password) {
244
+ textInputButtons?.unshift(
245
+ <IconButton
246
+ accessibilityLabel={
247
+ !showPassword
248
+ ? getCopy('hidePasswordAccessibilityLabel')
249
+ : getCopy('showPasswordAccessibilityLabel')
250
+ }
251
+ icon={!showPassword ? passwordShowButtonIcon : passwordHideButtonIcon}
252
+ key={!showPassword ? 'hide' : 'show'}
253
+ onPress={handleShowOrHide}
254
+ variant={{ compact: true, password: true, inactive: !!variant.inactive }}
255
+ />
256
+ )
257
+ }
258
+
229
259
  const inputProps = {
230
260
  ...selectProps(rest),
231
261
  editable: !inactive,
@@ -245,7 +275,12 @@ const TextInputBase = forwardRef(
245
275
 
246
276
  return (
247
277
  <View style={selectOuterBorderStyles(themeTokens)}>
248
- <NativeTextInput ref={inputRef} style={nativeInputStyle} {...inputProps} />
278
+ <NativeTextInput
279
+ ref={inputRef}
280
+ style={nativeInputStyle}
281
+ secureTextEntry={variant.password && !showPassword}
282
+ {...inputProps}
283
+ />
249
284
  {IconComponent && (
250
285
  <View
251
286
  pointerEvents="none" // avoid hijacking input press events
@@ -260,7 +295,7 @@ const TextInputBase = forwardRef(
260
295
  {buttons?.length > 0 && (
261
296
  <View style={[staticStyles.buttonsContainer, selectButtonsContainerStyle(themeTokens)]}>
262
297
  <StackView direction="row" space={buttonsGap}>
263
- {buttons}
298
+ {textInputButtons}
264
299
  </StackView>
265
300
  </View>
266
301
  )}
@@ -1,8 +1,12 @@
1
1
  export default {
2
2
  en: {
3
- clearButtonAccessibilityLabel: 'Clear'
3
+ clearButtonAccessibilityLabel: 'Clear',
4
+ showPasswordAccessibilityLabel: 'Show Password',
5
+ hidePasswordAccessibilityLabel: 'Hide Password'
4
6
  },
5
7
  fr: {
6
- clearButtonAccessibilityLabel: 'Effacer'
8
+ clearButtonAccessibilityLabel: 'Effacer',
9
+ showPasswordAccessibilityLabel: 'montrer le mot de passe',
10
+ hidePasswordAccessibilityLabel: 'masquer le mot de passe'
7
11
  }
8
12
  }
@@ -0,0 +1,217 @@
1
+ import React, { forwardRef, createRef, useRef, useMemo, useEffect, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { View, StyleSheet, Platform } from 'react-native'
4
+ import { inputSupportsProps, selectSystemProps } from '../utils'
5
+ import { TextInput } from '../TextInput'
6
+ import StackView from '../StackView'
7
+ import InputSupports from '../InputSupports'
8
+ import { useThemeTokens } from '../ThemeProvider'
9
+
10
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([inputSupportsProps])
11
+
12
+ const selectCodeTextInputTokens = ({ outerBorderColor, outerBackgroundColor }) => {
13
+ return {
14
+ outerBorderColor,
15
+ outerBackgroundColor,
16
+ icon: null
17
+ }
18
+ }
19
+
20
+ const Validator = forwardRef(
21
+ ({ value = '', inactive, onChange, tokens = {}, variant = {}, ...rest }, ref) => {
22
+ const defaultRef = useRef()
23
+ const codeRef = ref ?? defaultRef
24
+
25
+ const { supportsProps } = selectProps(rest)
26
+ const strValidation = supportsProps.validation
27
+ const [individualCodes, setIndividualCodes] = useState({})
28
+ const [text, setText] = useState(value)
29
+ const validatorsLength = 6
30
+ const prefix = 'code'
31
+ const sufixValidation = 'Validation'
32
+
33
+ const [isHover, setIsHover] = useState(false)
34
+ const handleMouseOver = () => {
35
+ setIsHover(true)
36
+ }
37
+
38
+ const handleMouseOut = () => {
39
+ setIsHover(false)
40
+ }
41
+
42
+ const themeTokens = useThemeTokens('TextInput', tokens, variant, { hover: isHover })
43
+
44
+ const [codeReferences, singleCodes] = useMemo(() => {
45
+ const codes = []
46
+ const valueCodes = {}
47
+ for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
48
+ codes[prefix + i] = createRef()
49
+ valueCodes[prefix + i] = ''
50
+ valueCodes[prefix + i + sufixValidation] = ''
51
+ }
52
+ return [codes, valueCodes]
53
+ }, [])
54
+
55
+ const handleSingleCodes = (codeId, val, validation) => {
56
+ singleCodes[codeId] = val
57
+ singleCodes[codeId + sufixValidation] = validation
58
+ /* eslint-disable no-unused-expressions */
59
+ setIndividualCodes({
60
+ ...individualCodes,
61
+ [codeId]: val
62
+ })
63
+ }
64
+
65
+ const handleChangeCode = () => {
66
+ let code = ''
67
+ for (let i = 0; i < validatorsLength; i += 1) code += singleCodes[prefix + i]
68
+ if (typeof onChange === 'function') onChange(code, singleCodes)
69
+ }
70
+
71
+ const handleChangeCodeValues = (event, codeId, nextIndex) => {
72
+ const codeElement = codeReferences[codeId]?.current
73
+ const val = event.nativeEvent?.value || event.target?.value
74
+
75
+ if (Number(val).toString() === 'NaN') {
76
+ codeElement.value = singleCodes[codeId] ?? ''
77
+ return
78
+ }
79
+ if (codeElement?.value?.length > 1) {
80
+ const oldValue = singleCodes[codeId]
81
+ const newValue = codeElement.value.replace(oldValue, '')
82
+ codeElement.value = newValue
83
+ handleSingleCodes(codeId, codeElement.value, 'success')
84
+ }
85
+
86
+ handleSingleCodes(codeId, codeElement?.value ?? singleCodes[codeId], 'success')
87
+ handleChangeCode()
88
+ if (nextIndex === validatorsLength) {
89
+ codeElement.blur()
90
+ return
91
+ }
92
+ if (codeElement?.value?.length > 0) codeReferences[prefix + nextIndex].current.focus()
93
+ }
94
+
95
+ const handleKeyPress = (event, currentIndex, previousIndex) => {
96
+ if (!(event.keyCode === 8 || event.code === 'Backspace')) return
97
+ if (currentIndex > 0) {
98
+ codeReferences[prefix + currentIndex].current.value = ''
99
+ codeReferences[prefix + previousIndex].current.focus()
100
+ }
101
+ handleSingleCodes(prefix + currentIndex, '', '')
102
+ handleChangeCode()
103
+ }
104
+
105
+ const getCodeComponents = () => {
106
+ const components = []
107
+
108
+ for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
109
+ const codeId = prefix + i
110
+ const codeInputProps = {
111
+ nativeID: codeId,
112
+ ref: codeReferences[codeId] ?? null,
113
+ validation: strValidation || singleCodes[codeId + sufixValidation],
114
+ tokens: selectCodeTextInputTokens(themeTokens),
115
+ onFocus: () => codeReferences[codeId].current.select(),
116
+ onKeyPress: (event) => handleKeyPress(event, i, i - 1),
117
+ onMouseOver: handleMouseOver,
118
+ onMouseOut: handleMouseOut,
119
+ inactive
120
+ }
121
+ codeInputProps.validation || delete codeInputProps.validation
122
+
123
+ components.push(
124
+ <View key={codeId} style={staticStyles.codeInputWidth}>
125
+ <TextInput {...codeInputProps} />
126
+ </View>
127
+ )
128
+ }
129
+ return components
130
+ }
131
+
132
+ useEffect(() => {
133
+ /* eslint-disable no-unused-expressions */
134
+ if (Number(value).toString() !== 'NaN') setText(value)
135
+ }, [value])
136
+
137
+ /* eslint-disable react-hooks/exhaustive-deps */
138
+ useEffect(() => {
139
+ for (let i = 0; i < validatorsLength; i += 1) {
140
+ codeReferences[prefix + i].current.value = text[i] ?? ''
141
+ handleSingleCodes(prefix + i, text[i] ?? '', text[i] ? 'success' : '')
142
+ }
143
+ }, [text])
144
+
145
+ /* eslint-disable react-hooks/exhaustive-deps */
146
+ useEffect(() => {
147
+ const handlePasteCode = (event) => {
148
+ setText('')
149
+ const clipBoardText = event.clipboardData.getData('text')
150
+ if (Number(clipBoardText).toString() !== 'NaN') setText(clipBoardText)
151
+ }
152
+
153
+ const handleCopy = (event) => {
154
+ let clipBoardText = ''
155
+ for (let i = 0; i < validatorsLength; i += 1)
156
+ singleCodes[prefix + i] && (clipBoardText += singleCodes[prefix + i])
157
+ event.clipboardData.setData('text/plain', clipBoardText)
158
+ event.preventDefault()
159
+ }
160
+
161
+ if (Platform.OS === 'web') {
162
+ for (let i = 0; i < validatorsLength; i += 1) {
163
+ codeReferences[prefix + i].current.addEventListener('paste', handlePasteCode)
164
+ codeReferences[prefix + i].current.addEventListener('copy', handleCopy)
165
+ codeReferences[prefix + i].current.addEventListener('input', (event) =>
166
+ handleChangeCodeValues(event, prefix + i, i + 1)
167
+ )
168
+ }
169
+ }
170
+
171
+ return () => {
172
+ if (Platform.oldValue === 'web') {
173
+ for (let i = 0; i < validatorsLength; i += 1) {
174
+ codeReferences[prefix + i]?.current?.removeEventListener('paste', handlePasteCode)
175
+ codeReferences[prefix + i]?.current?.removeEventListener('copy', handleCopy)
176
+ codeReferences[prefix + i]?.current?.removeEventListener('input', (event) =>
177
+ handleChangeCodeValues(event, prefix + i, i + 1)
178
+ )
179
+ }
180
+ }
181
+ }
182
+ }, [])
183
+
184
+ return (
185
+ <InputSupports {...supportsProps}>
186
+ <StackView space={2} direction="row" ref={codeRef}>
187
+ {getCodeComponents()}
188
+ </StackView>
189
+ </InputSupports>
190
+ )
191
+ }
192
+ )
193
+ Validator.displayName = 'Validator'
194
+
195
+ Validator.propTypes = {
196
+ ...selectedSystemPropTypes,
197
+ /**
198
+ * The value is a 6-digit code, may be only numeric characters, non numeric character aren't renderize
199
+ */
200
+ value: PropTypes.string,
201
+ /**
202
+ * If true, the component is inactive and non editable.
203
+ */
204
+ inactive: PropTypes.bool,
205
+ /**
206
+ * Use to react upon input's value changes. Required when the `value` prop is set. Will receive the input's value as an argument.
207
+ */
208
+ onChange: PropTypes.func
209
+ }
210
+
211
+ export default Validator
212
+
213
+ const staticStyles = StyleSheet.create({
214
+ codeInputWidth: {
215
+ width: 43
216
+ }
217
+ })
@@ -0,0 +1,3 @@
1
+ import Validator from './Validator'
2
+
3
+ export default Validator
package/src/index.js CHANGED
@@ -53,6 +53,7 @@ export { default as Typography } from './Typography'
53
53
  export { default as A11yInfoProvider, useA11yInfo } from './A11yInfoProvider'
54
54
  export { default as BaseProvider } from './BaseProvider'
55
55
  export { useHydrationContext } from './BaseProvider/HydrationContext'
56
+ export { default as Validator } from './Validator'
56
57
  export { default as ViewportProvider, useViewport, ViewportContext } from './ViewportProvider'
57
58
  export {
58
59
  default as ThemeProvider,
@@ -65,7 +65,11 @@ const textInputHandlerProps = {
65
65
  /**
66
66
  * onKeyDown handler (only supported on Web)
67
67
  */
68
- onKeyDown: PropTypes.func
68
+ onMouseOver: PropTypes.func,
69
+ /**
70
+ * onKeyDown handler (only supported on Web)
71
+ */
72
+ onMouseOut: PropTypes.func
69
73
  }
70
74
  }
71
75
  const selectTextInputHandlers = getPropSelector(textInputHandlerProps.types)