@telus-uds/components-base 3.8.0 → 3.9.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.
@@ -37,7 +37,7 @@ const Validator = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
37
37
  supportsProps
38
38
  } = selectProps(rest);
39
39
  const strValidation = supportsProps.validation;
40
- const [individualCodes, setIndividualCodes] = React.useState({});
40
+ const [, setIndividualCodes] = React.useState({});
41
41
  const [text, setText] = React.useState(value);
42
42
  const validatorsLength = 6;
43
43
  const prefix = 'code';
@@ -55,64 +55,85 @@ const Validator = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
55
55
  const [codeReferences, singleCodes] = React.useMemo(() => {
56
56
  const codes = [];
57
57
  const valueCodes = {};
58
- for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
58
+ Array.from({
59
+ length: validatorsLength
60
+ }, (_, i) => {
59
61
  codes[prefix + i] = /*#__PURE__*/React.createRef();
60
62
  valueCodes[prefix + i] = '';
61
63
  valueCodes[prefix + i + sufixValidation] = '';
62
- }
64
+ return null;
65
+ });
63
66
  return [codes, valueCodes];
64
- }, []);
65
- const handleSingleCodes = (codeId, val, validation) => {
67
+ }, [validatorsLength, prefix, sufixValidation]);
68
+ const handleSingleCodes = React.useCallback((codeId, val, validation) => {
66
69
  singleCodes[codeId] = val;
67
70
  singleCodes[codeId + sufixValidation] = validation;
68
- /* eslint-disable no-unused-expressions */
69
- setIndividualCodes({
70
- ...individualCodes,
71
+ setIndividualCodes(prev => ({
72
+ ...prev,
71
73
  [codeId]: val
72
- });
73
- };
74
- const changeDataMasking = boxElement => {
74
+ }));
75
+ }, [singleCodes, sufixValidation]);
76
+ const changeDataMasking = React.useCallback(boxElement => {
75
77
  let charMasking = '';
76
78
  const element = boxElement;
77
- if (mask && mask.length === 1) charMasking = mask;else if (mask && mask.length > 1) charMasking = mask.substring(0, 1);
78
- if (charMasking) element.value = charMasking;
79
- };
80
- const handleChangeCode = () => {
81
- let code = '';
82
- for (let i = 0; i < validatorsLength; i += 1) code += singleCodes[prefix + i];
83
- if (typeof onChange === 'function') onChange(code, singleCodes);
84
- };
85
- const handleChangeCodeValues = (event, codeId, nextIndex) => {
79
+ if (mask && mask.length === 1) {
80
+ charMasking = mask;
81
+ } else if (mask && mask.length > 1) {
82
+ charMasking = mask.substring(0, 1);
83
+ }
84
+ if (charMasking && element) {
85
+ element.value = charMasking;
86
+ }
87
+ }, [mask]);
88
+ const handleChangeCode = React.useCallback(() => {
89
+ const code = Array.from({
90
+ length: validatorsLength
91
+ }, (_, i) => singleCodes[prefix + i] || '').join('');
92
+ if (typeof onChange === 'function') {
93
+ onChange(code, singleCodes);
94
+ }
95
+ }, [validatorsLength, singleCodes, prefix, onChange]);
96
+ const handleChangeCodeValues = React.useCallback((event, codeId, nextIndex) => {
86
97
  const codeElement = codeReferences[codeId]?.current;
87
98
  const val = event.nativeEvent?.value || event.target?.value;
88
- if (Number(val).toString() === 'NaN') {
89
- codeElement.value = singleCodes[codeId] ?? '';
90
- return;
99
+
100
+ // Only allow numeric characters and limit to single digit
101
+ const numericOnly = val.replace(/\D/g, '').substring(0, 1);
102
+ if (codeElement && codeElement.value) {
103
+ codeElement.value = numericOnly;
91
104
  }
92
- handleSingleCodes(codeId, codeElement?.value ?? singleCodes[codeId], 'success');
105
+ handleSingleCodes(codeId, numericOnly, numericOnly ? 'success' : '');
93
106
  handleChangeCode();
94
107
  if (nextIndex === validatorsLength) {
95
- codeElement.blur();
108
+ codeElement?.blur();
96
109
  changeDataMasking(codeElement);
97
110
  return;
98
111
  }
99
- if (codeElement?.value?.length > 0) {
100
- codeReferences[prefix + nextIndex].current.focus();
112
+ if (numericOnly.length > 0) {
113
+ const nextElement = codeReferences[prefix + nextIndex]?.current;
114
+ nextElement?.focus();
101
115
  changeDataMasking(codeElement);
102
116
  }
103
- };
117
+ }, [codeReferences, handleSingleCodes, handleChangeCode, validatorsLength, changeDataMasking, prefix]);
104
118
  const handleKeyPress = (event, currentIndex, previousIndex) => {
105
- if (!(event.keyCode === 8 || event.code === 'Backspace')) return;
119
+ if (!(event.keyCode === 8 || event.code === 'Backspace')) {
120
+ return;
121
+ }
106
122
  if (currentIndex > 0) {
107
- codeReferences[prefix + currentIndex].current.value = '';
108
- codeReferences[prefix + previousIndex].current.focus();
123
+ const currentElement = codeReferences[prefix + currentIndex]?.current;
124
+ const previousElement = codeReferences[prefix + previousIndex]?.current;
125
+ if (currentElement && currentElement.value) {
126
+ currentElement.value = '';
127
+ }
128
+ previousElement?.focus();
109
129
  }
110
130
  handleSingleCodes(prefix + currentIndex, '', '');
111
131
  handleChangeCode();
112
132
  };
113
133
  const getCodeComponents = () => {
114
- const components = [];
115
- for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
134
+ return Array.from({
135
+ length: validatorsLength
136
+ }, (_, i) => {
116
137
  const codeId = prefix + i;
117
138
  const codeInputProps = {
118
139
  nativeID: codeId,
@@ -120,65 +141,115 @@ const Validator = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
120
141
  ref: codeReferences[codeId] ?? null,
121
142
  validation: strValidation || singleCodes[codeId + sufixValidation],
122
143
  tokens: selectCodeTextInputTokens(themeTokens),
123
- onFocus: () => codeReferences[codeId]?.current?.select() ?? null,
144
+ onFocus: () => {
145
+ const element = codeReferences[codeId]?.current;
146
+ if (Platform.OS === 'web' && element?.select) {
147
+ return element.select() ?? null;
148
+ }
149
+ if (element?.focus) {
150
+ return element.focus() ?? null;
151
+ }
152
+ return null;
153
+ },
124
154
  onKeyPress: event => handleKeyPress(event, i, i - 1),
125
155
  onMouseOver: handleMouseOver,
126
156
  onMouseOut: handleMouseOut,
127
157
  inactive
128
158
  };
129
- codeInputProps.validation || delete codeInputProps.validation;
130
- components.push(/*#__PURE__*/_jsx(View, {
159
+ if (!codeInputProps.validation) {
160
+ delete codeInputProps.validation;
161
+ }
162
+ return /*#__PURE__*/_jsx(View, {
131
163
  style: staticStyles.codeInputWidth,
132
164
  children: /*#__PURE__*/_jsx(TextInput, {
133
165
  ...codeInputProps
134
166
  })
135
- }, codeId));
136
- }
137
- return components;
167
+ }, codeId);
168
+ });
138
169
  };
139
170
  React.useEffect(() => {
140
- /* eslint-disable no-unused-expressions */
141
- if (Number(value).toString() !== 'NaN') setText(value);
171
+ if (Number(value).toString() !== 'NaN') {
172
+ setText(value);
173
+ }
142
174
  }, [value]);
143
-
144
- /* eslint-disable react-hooks/exhaustive-deps */
145
175
  React.useEffect(() => {
146
- for (let i = 0; i < validatorsLength; i += 1) {
147
- if (mask && text[i]) codeReferences[prefix + i].current.value = mask;else codeReferences[prefix + i].current.value = text[i] ?? '';
176
+ Array.from({
177
+ length: validatorsLength
178
+ }, (_, i) => {
179
+ const element = codeReferences[prefix + i]?.current;
180
+ if (element && element.value !== undefined) {
181
+ if (mask && text[i]) {
182
+ element.value = mask;
183
+ } else {
184
+ element.value = text[i] ?? '';
185
+ }
186
+ }
148
187
  handleSingleCodes(prefix + i, text[i] ?? '', text[i] ? 'success' : '');
149
- }
150
- }, [text]);
151
-
152
- /* eslint-disable react-hooks/exhaustive-deps */
188
+ return null;
189
+ });
190
+ }, [text, mask, validatorsLength, prefix, codeReferences, handleSingleCodes]);
153
191
  React.useEffect(() => {
154
192
  const handlePasteCode = event => {
193
+ event.preventDefault();
194
+
195
+ // Clear current state first
155
196
  setText('');
197
+
198
+ // Clear all individual input fields and their state
199
+ Array.from({
200
+ length: validatorsLength
201
+ }, (_, i) => {
202
+ const element = codeReferences[prefix + i]?.current;
203
+ if (element && element.value !== undefined) {
204
+ element.value = '';
205
+ }
206
+ handleSingleCodes(prefix + i, '', '');
207
+ return null;
208
+ });
156
209
  const clipBoardText = event.clipboardData.getData('text');
157
- if (Number(clipBoardText).toString() !== 'NaN') setText(clipBoardText);
210
+
211
+ // Validate that input contains only digits and truncate to 6 characters
212
+ const numericOnly = clipBoardText.replace(/\D/g, '').substring(0, validatorsLength);
213
+ if (numericOnly.length > 0) {
214
+ setText(numericOnly);
215
+ }
158
216
  };
159
217
  const handleCopy = event => {
160
- let clipBoardText = '';
161
- for (let i = 0; i < validatorsLength; i += 1) singleCodes[prefix + i] && (clipBoardText += singleCodes[prefix + i]);
218
+ const clipBoardText = Array.from({
219
+ length: validatorsLength
220
+ }, (_, i) => singleCodes[prefix + i] || '').join('');
162
221
  event.clipboardData.setData('text/plain', clipBoardText);
163
222
  event.preventDefault();
164
223
  };
165
224
  if (Platform.OS === 'web') {
166
- for (let i = 0; i < validatorsLength; i += 1) {
167
- codeReferences[prefix + i].current.addEventListener('paste', handlePasteCode);
168
- codeReferences[prefix + i].current.addEventListener('copy', handleCopy);
169
- codeReferences[prefix + i].current.addEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
170
- }
225
+ Array.from({
226
+ length: validatorsLength
227
+ }, (_, i) => {
228
+ const element = codeReferences[prefix + i]?.current;
229
+ if (element && typeof element.addEventListener === 'function') {
230
+ element.addEventListener('paste', handlePasteCode);
231
+ element.addEventListener('copy', handleCopy);
232
+ element.addEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
233
+ }
234
+ return null;
235
+ });
171
236
  }
172
237
  return () => {
173
- if (Platform.oldValue === 'web') {
174
- for (let i = 0; i < validatorsLength; i += 1) {
175
- codeReferences[prefix + i]?.current?.removeEventListener('paste', handlePasteCode);
176
- codeReferences[prefix + i]?.current?.removeEventListener('copy', handleCopy);
177
- codeReferences[prefix + i]?.current?.removeEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
178
- }
238
+ if (Platform.OS === 'web') {
239
+ Array.from({
240
+ length: validatorsLength
241
+ }, (_, i) => {
242
+ const element = codeReferences[prefix + i]?.current;
243
+ if (element && typeof element.removeEventListener === 'function') {
244
+ element.removeEventListener('paste', handlePasteCode);
245
+ element.removeEventListener('copy', handleCopy);
246
+ element.removeEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
247
+ }
248
+ return null;
249
+ });
179
250
  }
180
251
  };
181
- }, []);
252
+ }, [validatorsLength, prefix, codeReferences, handleChangeCodeValues, handleSingleCodes, singleCodes]);
182
253
  return /*#__PURE__*/_jsx(InputSupports, {
183
254
  ...supportsProps,
184
255
  feedbackProps: {
package/lib/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.6.0",
15
+ "@telus-uds/system-theme-tokens": "^4.7.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.8.0",
87
+ "version": "3.9.0",
88
88
  "types": "types/index.d.ts"
89
89
  }
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.6.0",
15
+ "@telus-uds/system-theme-tokens": "^4.7.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.8.0",
87
+ "version": "3.9.0",
88
88
  "types": "types/index.d.ts"
89
89
  }
@@ -50,6 +50,7 @@ const staticStyles = StyleSheet.create({
50
50
 
51
51
  const selectContainerStyle = (enableFullscreen, themeTokens) => ({
52
52
  borderColor: themeTokens.borderColor,
53
+ backgroundColor: themeTokens.backgroundColor,
53
54
  ...Platform.select({
54
55
  web: {
55
56
  position: enableFullscreen ? 'fixed' : 'absolute'
@@ -99,7 +100,7 @@ const ModalOverlay = React.forwardRef(
99
100
  maxHeight: maxHeightSize
100
101
  }
101
102
 
102
- const { closeIcon: CloseIconComponent } = themeTokens
103
+ const { closeIcon: CloseIconComponent, backgroundColor } = themeTokens
103
104
 
104
105
  const getCopy = useCopy({ dictionary, copy })
105
106
  const closeLabel = getCopy('closeButton')
@@ -117,7 +118,7 @@ const ModalOverlay = React.forwardRef(
117
118
  selectContainerStyle(enableFullscreen, themeTokens)
118
119
  ]}
119
120
  >
120
- <Card tokens={staticStyles.card}>
121
+ <Card tokens={{ ...staticStyles.card, backgroundColor }}>
121
122
  <View
122
123
  style={[
123
124
  staticStyles.closeButtonContainer,
@@ -139,6 +139,7 @@ const MultiSelectFilter = React.forwardRef(
139
139
  buttonBackgroundColor,
140
140
  iconColorSelected,
141
141
  buttonBackgroundColorSelected,
142
+ containerBorderColor,
142
143
  ...restTokens
143
144
  } = useThemeTokens(
144
145
  'MultiSelectFilter',
@@ -270,7 +271,7 @@ const MultiSelectFilter = React.forwardRef(
270
271
  <Row>
271
272
  <View>
272
273
  <Typography tokens={{ ...headerStyles, lineHeight: headerLineHeight }}>
273
- {getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label.toLowerCase())}
274
+ {getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label)}
274
275
  </Typography>
275
276
  </View>
276
277
  </Row>
@@ -286,7 +287,7 @@ const MultiSelectFilter = React.forwardRef(
286
287
  )}
287
288
  <Spacer space={4} />
288
289
  <View style={styles.options}>
289
- <ScrollView onLayout={handleScrollViewLayout}>
290
+ <ScrollView onLayout={handleScrollViewLayout} style={styles.scrollContainer}>
290
291
  <Row distribute="between" onLayout={handleRowLayout}>
291
292
  {[...Array(colSize).keys()].map((i) => (
292
293
  <Col xs={TOTAL_COLUMNS / colSize} key={i}>
@@ -306,16 +307,14 @@ const MultiSelectFilter = React.forwardRef(
306
307
 
307
308
  const controlsContent = (
308
309
  <>
309
- {isScrolling ? (
310
+ {isScrolling && (
310
311
  <Divider
311
312
  tokens={{
312
313
  color: dividerColor
313
314
  }}
314
- space={4}
315
315
  />
316
- ) : (
317
- <Spacer space={4} />
318
316
  )}
317
+ <Spacer space={4} />
319
318
  <View style={selectControlsTokens(restTokens)}>
320
319
  <Row horizontalAlign={buttonDirection === 'column' ? 'center' : 'start'}>
321
320
  <StackView
@@ -368,7 +367,8 @@ const MultiSelectFilter = React.forwardRef(
368
367
  minWidth={windowWidth}
369
368
  tokens={{
370
369
  ...tokens,
371
- maxWidth: true
370
+ maxWidth: true,
371
+ borderColor: containerBorderColor
372
372
  }}
373
373
  copy={copy}
374
374
  isReady={isReady}
@@ -397,7 +397,8 @@ const MultiSelectFilter = React.forwardRef(
397
397
  minWidth={minWidth}
398
398
  tokens={{
399
399
  ...tokens,
400
- maxWidth: items.length > MAX_ITEMS_THRESHOLD ? true : maxWidth
400
+ maxWidth: items.length > MAX_ITEMS_THRESHOLD ? true : maxWidth,
401
+ borderColor: containerBorderColor
401
402
  }}
402
403
  copy={copy}
403
404
  isReady={isReady}
@@ -430,6 +431,9 @@ const styles = StyleSheet.create({
430
431
  },
431
432
  options: {
432
433
  flex: 1
434
+ },
435
+ scrollContainer: {
436
+ padding: 1
433
437
  }
434
438
  })
435
439
 
@@ -77,6 +77,18 @@ const selectSwitchStyles = ({
77
77
  })
78
78
  })
79
79
 
80
+ const selectMobileTokens = (tokens) => ({
81
+ ...Platform.select({
82
+ web: {},
83
+ default: {
84
+ switchSize: tokens?.mobileSwitchSize,
85
+ trackHeight: tokens?.mobileTrackHeight,
86
+ width: tokens?.mobileWidth,
87
+ trackBorderWidth: tokens?.mobileTrackBorderWidth
88
+ }
89
+ })
90
+ })
91
+
80
92
  const selectLabelStyles = ({ labelMarginLeft }) => ({ marginLeft: labelMarginLeft })
81
93
  const selectLabelTokens = ({
82
94
  labelColor,
@@ -123,7 +135,10 @@ const ToggleSwitch = React.forwardRef(
123
135
 
124
136
  const handlePress = (event) => setValue(!currentValue, event)
125
137
  const getButtonTokens = (buttonState) =>
126
- selectButtonTokens(getTokens(buttonState), getTokens(themeTokens))
138
+ selectButtonTokens(
139
+ getTokens(buttonState, selectMobileTokens(themeTokens)),
140
+ getTokens(themeTokens, selectMobileTokens(themeTokens))
141
+ )
127
142
  const uniqueId = useUniqueId('toggleSwitch')
128
143
  const inputId = id ?? uniqueId
129
144
 
@@ -157,7 +172,7 @@ const ToggleSwitch = React.forwardRef(
157
172
  {...selectProps(rest)}
158
173
  >
159
174
  {(buttonState) => {
160
- const stateTokens = getTokens(buttonState)
175
+ const stateTokens = getTokens(buttonState, selectMobileTokens(themeTokens))
161
176
  const IconComponent = stateTokens.icon
162
177
  const switchStyles = selectSwitchStyles(stateTokens)
163
178
  const trackStyles = selectTrackStyles(stateTokens)