@telus-uds/components-base 1.28.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.
@@ -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
  };
@@ -21,14 +21,13 @@ const selectInnerContainerStyles = _ref => {
21
21
  const selectIconTokens = _ref2 => {
22
22
  let {
23
23
  iconSize,
24
- iconColor
25
- /* iconScale = 1 */
26
-
24
+ iconColor,
25
+ iconScale = 1
27
26
  } = _ref2;
28
27
  return {
29
28
  size: iconSize,
30
- color: iconColor // scale: iconScale TODO re-enable with icon component
31
-
29
+ color: iconColor,
30
+ scale: iconScale
32
31
  };
33
32
  };
34
33
  /**
@@ -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.11.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.28.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
  }
@@ -9,10 +9,10 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
9
9
 
10
10
  const selectInnerContainerStyles = ({ borderRadius, width }) => ({ borderRadius, width })
11
11
 
12
- const selectIconTokens = ({ iconSize, iconColor /* iconScale = 1 */ }) => ({
12
+ const selectIconTokens = ({ iconSize, iconColor, iconScale = 1 }) => ({
13
13
  size: iconSize,
14
- color: iconColor
15
- // scale: iconScale TODO re-enable with icon component
14
+ color: iconColor,
15
+ scale: iconScale
16
16
  })
17
17
 
18
18
  /**