goobs-frontend 0.7.57 → 0.7.59
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/package.json +14 -14
- package/src/components/Button/index.tsx +6 -65
- package/src/components/ConfirmationCodeInput/index.tsx +40 -5
- package/src/components/Content/Structure/styledcomponent/useStyledComponent.tsx +0 -6
- package/src/components/Form/Popup/index.tsx +143 -106
- package/src/components/StyledComponent/{adornments.tsx → adornment/index.tsx} +17 -12
- package/src/components/StyledComponent/helperfooter/useHelperFooter.tsx +225 -89
- package/src/components/StyledComponent/index.tsx +99 -182
- package/src/components/StyledComponent/useEffects/index.tsx +54 -0
- package/src/index.ts +1 -2
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
4
|
-
useCallback,
|
|
5
|
-
useState,
|
|
6
|
-
useRef,
|
|
7
|
-
useEffect,
|
|
8
|
-
RefObject,
|
|
9
|
-
} from 'react'
|
|
3
|
+
import React, { useState, useRef, useEffect } from 'react'
|
|
10
4
|
import { Box, InputLabel, OutlinedInput, styled } from '@mui/material'
|
|
11
5
|
import { useDropdown } from './hooks/useDropdown'
|
|
12
6
|
import { usePhoneNumber } from './hooks/usePhoneNumber'
|
|
@@ -14,22 +8,13 @@ import { usePassword } from './hooks/usePassword'
|
|
|
14
8
|
import { useSplitButton } from './hooks/useSplitButton'
|
|
15
9
|
import { Typography } from './../Typography'
|
|
16
10
|
import { red, green } from '../../styles/palette'
|
|
17
|
-
import { StartAdornment, EndAdornment } from './
|
|
18
|
-
import {
|
|
11
|
+
import { StartAdornment, EndAdornment } from './adornment'
|
|
12
|
+
import {
|
|
13
|
+
useHelperFooter,
|
|
14
|
+
HelperFooterMessage,
|
|
15
|
+
} from './helperfooter/useHelperFooter'
|
|
19
16
|
import labelStyles from '../../styles/StyledComponent/Label'
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Interface for helper footer messages
|
|
24
|
-
*/
|
|
25
|
-
export interface HelperFooterMessage {
|
|
26
|
-
status: 'error' | 'success'
|
|
27
|
-
statusMessage: string
|
|
28
|
-
spreadMessage: string
|
|
29
|
-
spreadMessagePriority: number
|
|
30
|
-
formname: string
|
|
31
|
-
required: boolean
|
|
32
|
-
}
|
|
17
|
+
import { useHasInputEffect, usePreventAutocompleteEffect } from './useEffects'
|
|
33
18
|
|
|
34
19
|
/**
|
|
35
20
|
* Props interface for the StyledComponent
|
|
@@ -70,26 +55,13 @@ export interface StyledComponentProps {
|
|
|
70
55
|
shrunklabellocation?: 'onnotch' | 'above'
|
|
71
56
|
value?: string
|
|
72
57
|
valuestatus?: boolean
|
|
73
|
-
defaultValue?: string
|
|
74
|
-
inputRef?: RefObject<HTMLInputElement>
|
|
75
58
|
focused?: boolean
|
|
76
59
|
required?: boolean
|
|
77
60
|
formSubmitted?: boolean
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Props interface for the Adornment components
|
|
85
|
-
*/
|
|
86
|
-
export interface AdornmentProps {
|
|
87
|
-
componentvariant: string
|
|
88
|
-
iconcolor?: string
|
|
89
|
-
passwordVisible?: boolean
|
|
90
|
-
marginRight?: number | string
|
|
91
|
-
handleIncrement?: () => void
|
|
92
|
-
handleDecrement?: () => void
|
|
61
|
+
'aria-label'?: string
|
|
62
|
+
'aria-required'?: boolean
|
|
63
|
+
'aria-invalid'?: boolean
|
|
64
|
+
'aria-describedby'?: string
|
|
93
65
|
}
|
|
94
66
|
|
|
95
67
|
/**
|
|
@@ -121,7 +93,6 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
121
93
|
const {
|
|
122
94
|
label,
|
|
123
95
|
componentvariant,
|
|
124
|
-
inputRef,
|
|
125
96
|
name,
|
|
126
97
|
backgroundcolor,
|
|
127
98
|
iconcolor,
|
|
@@ -135,63 +106,29 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
135
106
|
required = false,
|
|
136
107
|
formname,
|
|
137
108
|
formSubmitted = false,
|
|
138
|
-
|
|
109
|
+
'aria-label': ariaLabel,
|
|
110
|
+
'aria-required': ariaRequired,
|
|
111
|
+
'aria-invalid': ariaInvalid,
|
|
112
|
+
'aria-describedby': ariaDescribedBy,
|
|
139
113
|
} = props
|
|
140
114
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
115
|
+
console.log('StyledComponent: Initializing with props', {
|
|
116
|
+
name,
|
|
117
|
+
label,
|
|
118
|
+
required,
|
|
119
|
+
formname,
|
|
120
|
+
formSubmitted,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const { validateField, validateRequiredField, helperFooterValue } =
|
|
124
|
+
useHelperFooter()
|
|
145
125
|
const [isFocused, setIsFocused] = useState(false)
|
|
146
126
|
const [hasInput, setHasInput] = useState(false)
|
|
147
127
|
const [showError, setShowError] = useState(false)
|
|
148
128
|
const inputRefInternal = useRef<HTMLInputElement>(null)
|
|
149
129
|
const inputBoxRef = useRef<HTMLDivElement>(null)
|
|
150
130
|
|
|
151
|
-
//
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
const fetchHelperFooter = async () => {
|
|
154
|
-
const result = await get('helperFooter', 'client')
|
|
155
|
-
if (result && typeof result === 'object' && 'value' in result) {
|
|
156
|
-
setHelperFooterValue(
|
|
157
|
-
(result as JSONValue).value as Record<string, HelperFooterMessage>
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
fetchHelperFooter()
|
|
162
|
-
}, [])
|
|
163
|
-
|
|
164
|
-
// Update hasInput state when value or valuestatus changes
|
|
165
|
-
useEffect(() => {
|
|
166
|
-
setHasInput(!!value || !!valuestatus)
|
|
167
|
-
}, [value, valuestatus])
|
|
168
|
-
|
|
169
|
-
// Set input attributes to prevent autocomplete and related features
|
|
170
|
-
useEffect(() => {
|
|
171
|
-
const input = inputRefInternal.current || inputRef?.current
|
|
172
|
-
if (input) {
|
|
173
|
-
input.setAttribute('autocomplete', 'new-password')
|
|
174
|
-
input.setAttribute('autocorrect', 'off')
|
|
175
|
-
input.setAttribute('autocapitalize', 'none')
|
|
176
|
-
input.setAttribute('spellcheck', 'false')
|
|
177
|
-
}
|
|
178
|
-
}, [inputRef])
|
|
179
|
-
|
|
180
|
-
// Validate field if required and necessary props are provided
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
if (required && formname && name && label) {
|
|
183
|
-
const emptyFormData = new FormData()
|
|
184
|
-
emptyFormData.append(name, '')
|
|
185
|
-
validateField(name, emptyFormData, label, required, formname)
|
|
186
|
-
}
|
|
187
|
-
}, [required, formname, name, label, validateField])
|
|
188
|
-
|
|
189
|
-
// Update showError state based on form submission and input state
|
|
190
|
-
useEffect(() => {
|
|
191
|
-
setShowError(formSubmitted || (hasInput && !isFocused))
|
|
192
|
-
}, [formSubmitted, hasInput, isFocused])
|
|
193
|
-
|
|
194
|
-
// Custom hooks for specific component variants
|
|
131
|
+
// Custom hooks
|
|
195
132
|
const { renderMenu, selectedOption, isDropdownOpen } = useDropdown(
|
|
196
133
|
props,
|
|
197
134
|
inputBoxRef
|
|
@@ -204,111 +141,67 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
204
141
|
handleDecrement,
|
|
205
142
|
} = useSplitButton(props)
|
|
206
143
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*/
|
|
211
|
-
const handleChange = useCallback(
|
|
212
|
-
async (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
213
|
-
if (componentvariant === 'phonenumber') {
|
|
214
|
-
handlePhoneNumberChange(e)
|
|
215
|
-
} else if (componentvariant === 'splitbutton') {
|
|
216
|
-
// Only allow numbers for splitbutton
|
|
217
|
-
const numValue = e.target.value.replace(/[^0-9]/g, '')
|
|
218
|
-
e.target.value = numValue
|
|
219
|
-
}
|
|
144
|
+
// useEffect hooks
|
|
145
|
+
useHasInputEffect(value, valuestatus, setHasInput)
|
|
146
|
+
usePreventAutocompleteEffect(inputRefInternal)
|
|
220
147
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (required && formname && name && label) {
|
|
150
|
+
validateRequiredField(required, formname, name, label)
|
|
151
|
+
}
|
|
152
|
+
}, [required, formname, name, label, validateRequiredField])
|
|
226
153
|
|
|
227
|
-
|
|
228
|
-
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
const timer = setTimeout(() => {
|
|
156
|
+
setShowError(formSubmitted || hasInput)
|
|
157
|
+
}, 1000)
|
|
229
158
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
159
|
+
return () => clearTimeout(timer)
|
|
160
|
+
}, [formSubmitted, hasInput])
|
|
233
161
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
let result: HelperFooterMessage | undefined
|
|
162
|
+
const currentHelperFooter = name ? helperFooterValue[name] : undefined
|
|
163
|
+
console.log('StyledComponent: Current helper footer', currentHelperFooter)
|
|
237
164
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
componentvariant === 'email' &&
|
|
250
|
-
!/\S+@\S+\.\S+/.test(e.target.value)
|
|
251
|
-
) {
|
|
252
|
-
result = {
|
|
253
|
-
status: 'error',
|
|
254
|
-
statusMessage: 'Invalid email format.',
|
|
255
|
-
spreadMessage: 'Invalid email format.',
|
|
256
|
-
spreadMessagePriority: 1,
|
|
257
|
-
formname: formname || '',
|
|
258
|
-
required: required,
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// Add more validation rules here as needed
|
|
165
|
+
/**
|
|
166
|
+
* Handle the change event of the input element.
|
|
167
|
+
* @param e The change event.
|
|
168
|
+
*/
|
|
169
|
+
const handleChange = (
|
|
170
|
+
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
171
|
+
) => {
|
|
172
|
+
console.log('StyledComponent: handleChange called', {
|
|
173
|
+
name: e.target.name,
|
|
174
|
+
value: e.target.value,
|
|
175
|
+
})
|
|
262
176
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
setHelperFooterValue(prevState => {
|
|
271
|
-
const newState = { ...prevState }
|
|
272
|
-
delete newState[name]
|
|
273
|
-
return newState
|
|
274
|
-
})
|
|
275
|
-
}
|
|
276
|
-
}
|
|
177
|
+
if (componentvariant === 'phonenumber') {
|
|
178
|
+
handlePhoneNumberChange(e)
|
|
179
|
+
} else if (componentvariant === 'splitbutton') {
|
|
180
|
+
// Only allow numbers for splitbutton
|
|
181
|
+
const numValue = e.target.value.replace(/[^0-9]/g, '')
|
|
182
|
+
e.target.value = numValue
|
|
183
|
+
}
|
|
277
184
|
|
|
278
|
-
|
|
279
|
-
const helperFooterResult = await get('helperFooter', 'client')
|
|
280
|
-
if (
|
|
281
|
-
helperFooterResult &&
|
|
282
|
-
typeof helperFooterResult === 'object' &&
|
|
283
|
-
'value' in helperFooterResult
|
|
284
|
-
) {
|
|
285
|
-
setHelperFooterValue(prevState => ({
|
|
286
|
-
...prevState,
|
|
287
|
-
...((helperFooterResult as JSONValue).value as Record<
|
|
288
|
-
string,
|
|
289
|
-
HelperFooterMessage
|
|
290
|
-
>),
|
|
291
|
-
}))
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
[
|
|
295
|
-
componentvariant,
|
|
296
|
-
handlePhoneNumberChange,
|
|
297
|
-
onChange,
|
|
298
|
-
validateField,
|
|
299
|
-
name,
|
|
300
|
-
label,
|
|
301
|
-
required,
|
|
302
|
-
formname,
|
|
303
|
-
]
|
|
304
|
-
)
|
|
185
|
+
setHasInput(!!e.target.value)
|
|
305
186
|
|
|
306
|
-
|
|
187
|
+
const formData = new FormData()
|
|
188
|
+
formData.append(e.target.name, e.target.value)
|
|
189
|
+
if (name && label && formname) {
|
|
190
|
+
console.log('StyledComponent: Calling validateField', {
|
|
191
|
+
name,
|
|
192
|
+
label,
|
|
193
|
+
required,
|
|
194
|
+
formname,
|
|
195
|
+
})
|
|
196
|
+
validateField(name, formData, label, required, formname)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
307
199
|
|
|
308
200
|
/**
|
|
309
201
|
* Handle the focus event of the input element.
|
|
310
202
|
*/
|
|
311
203
|
const handleFocus = () => {
|
|
204
|
+
console.log('StyledComponent: handleFocus called')
|
|
312
205
|
setIsFocused(true)
|
|
313
206
|
}
|
|
314
207
|
|
|
@@ -316,8 +209,15 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
316
209
|
* Handle the blur event of the input element.
|
|
317
210
|
*/
|
|
318
211
|
const handleBlur = () => {
|
|
212
|
+
console.log('StyledComponent: handleBlur called')
|
|
319
213
|
setIsFocused(false)
|
|
320
214
|
if (name && label && !hasInput && formname) {
|
|
215
|
+
console.log('StyledComponent: Calling validateField on blur', {
|
|
216
|
+
name,
|
|
217
|
+
label,
|
|
218
|
+
required,
|
|
219
|
+
formname,
|
|
220
|
+
})
|
|
321
221
|
const formData = new FormData()
|
|
322
222
|
formData.append(name, '')
|
|
323
223
|
validateField(name, formData, label, required, formname)
|
|
@@ -344,6 +244,14 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
344
244
|
hasInput ||
|
|
345
245
|
componentvariant === 'phonenumber'
|
|
346
246
|
|
|
247
|
+
console.log('StyledComponent: Rendering', {
|
|
248
|
+
name,
|
|
249
|
+
showError,
|
|
250
|
+
hasHelperFooter: !!currentHelperFooter,
|
|
251
|
+
helperFooterStatus: currentHelperFooter?.status,
|
|
252
|
+
helperFooterMessage: currentHelperFooter?.statusMessage,
|
|
253
|
+
})
|
|
254
|
+
|
|
347
255
|
return (
|
|
348
256
|
<Box
|
|
349
257
|
sx={{
|
|
@@ -376,13 +284,14 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
376
284
|
focused: shouldShrinkLabel,
|
|
377
285
|
})}
|
|
378
286
|
shrink={shouldShrinkLabel}
|
|
287
|
+
htmlFor={name}
|
|
379
288
|
>
|
|
380
289
|
{label}
|
|
381
290
|
</InputLabel>
|
|
382
291
|
)}
|
|
383
292
|
<Box ref={inputBoxRef} sx={{ width: '100%' }}>
|
|
384
293
|
<NoAutofillOutlinedInput
|
|
385
|
-
ref={
|
|
294
|
+
ref={inputRefInternal}
|
|
386
295
|
style={{
|
|
387
296
|
backgroundColor: backgroundcolor || 'inherit',
|
|
388
297
|
width: '100%',
|
|
@@ -409,6 +318,13 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
409
318
|
: 'text',
|
|
410
319
|
},
|
|
411
320
|
placeholder: placeholder || '',
|
|
321
|
+
'aria-label': ariaLabel,
|
|
322
|
+
'aria-invalid': ariaInvalid,
|
|
323
|
+
'aria-required': ariaRequired,
|
|
324
|
+
'aria-describedby':
|
|
325
|
+
ariaDescribedBy || currentHelperFooter?.statusMessage
|
|
326
|
+
? `${name}-helper-text`
|
|
327
|
+
: undefined,
|
|
412
328
|
}}
|
|
413
329
|
type={
|
|
414
330
|
componentvariant === 'password' && !passwordVisible
|
|
@@ -459,6 +375,7 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
459
375
|
</Box>
|
|
460
376
|
{showError && currentHelperFooter?.statusMessage && (
|
|
461
377
|
<Typography
|
|
378
|
+
id={`${name}-helper-text`}
|
|
462
379
|
fontvariant="merrihelperfooter"
|
|
463
380
|
fontcolor={
|
|
464
381
|
currentHelperFooter?.status === 'error'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom hook that tracks whether an input field has a value or a specific status.
|
|
5
|
+
*
|
|
6
|
+
* @param {string | undefined} value - The current value of the input field.
|
|
7
|
+
* @param {boolean | undefined} valuestatus - A boolean status associated with the input field.
|
|
8
|
+
* @param {React.Dispatch<React.SetStateAction<boolean>>} setHasInput - State setter function to update the hasInput state.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const [hasInput, setHasInput] = useState(false);
|
|
12
|
+
* useHasInputEffect(inputValue, inputStatus, setHasInput);
|
|
13
|
+
*/
|
|
14
|
+
export const useHasInputEffect = (
|
|
15
|
+
value: string | undefined,
|
|
16
|
+
valuestatus: boolean | undefined,
|
|
17
|
+
setHasInput: React.Dispatch<React.SetStateAction<boolean>>
|
|
18
|
+
) => {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const hasInput = !!value || !!valuestatus
|
|
21
|
+
console.log('useHasInputEffect: Setting hasInput to', hasInput)
|
|
22
|
+
setHasInput(hasInput)
|
|
23
|
+
}, [value, valuestatus, setHasInput])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Custom hook that prevents autocomplete, autocorrect, autocapitalize, and spellcheck on an input field.
|
|
28
|
+
* This is particularly useful for password fields or other sensitive inputs.
|
|
29
|
+
*
|
|
30
|
+
* @param {React.RefObject<HTMLInputElement>} inputRefInternal - React ref object for the input element.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const inputRef = useRef<HTMLInputElement>(null);
|
|
34
|
+
* usePreventAutocompleteEffect(inputRef);
|
|
35
|
+
* // In your JSX:
|
|
36
|
+
* <input ref={inputRef} ... />
|
|
37
|
+
*/
|
|
38
|
+
export const usePreventAutocompleteEffect = (
|
|
39
|
+
inputRefInternal: React.RefObject<HTMLInputElement>
|
|
40
|
+
) => {
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
console.log('usePreventAutocompleteEffect: Starting effect')
|
|
43
|
+
const input = inputRefInternal.current
|
|
44
|
+
if (input) {
|
|
45
|
+
console.log('usePreventAutocompleteEffect: Setting input attributes')
|
|
46
|
+
input.setAttribute('autocomplete', 'new-password')
|
|
47
|
+
input.setAttribute('autocorrect', 'off')
|
|
48
|
+
input.setAttribute('autocapitalize', 'none')
|
|
49
|
+
input.setAttribute('spellcheck', 'false')
|
|
50
|
+
} else {
|
|
51
|
+
console.log('usePreventAutocompleteEffect: Input ref is null')
|
|
52
|
+
}
|
|
53
|
+
}, [inputRefInternal])
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,6 @@ import CustomGrid, {
|
|
|
9
9
|
} from './components/Grid'
|
|
10
10
|
import StyledComponent, {
|
|
11
11
|
StyledComponentProps,
|
|
12
|
-
AdornmentProps,
|
|
13
12
|
} from './components/StyledComponent'
|
|
14
13
|
import Typography, {
|
|
15
14
|
FontFamily,
|
|
@@ -172,7 +171,7 @@ export type { CustomButtonProps }
|
|
|
172
171
|
export type { ButtonAlignment }
|
|
173
172
|
export type { CustomGridProps }
|
|
174
173
|
export type { Alignment, BorderProp, columnconfig, gridconfig, cellconfig }
|
|
175
|
-
export type { StyledComponentProps
|
|
174
|
+
export type { StyledComponentProps }
|
|
176
175
|
export type {
|
|
177
176
|
FontFamily,
|
|
178
177
|
TypographyVariant,
|