@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.
@@ -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)