goobs-frontend 0.7.57 → 0.7.58
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 +1 -1
- 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 +30 -8
- package/src/components/StyledComponent/{adornments.tsx → adornment/index.tsx} +17 -12
- package/src/components/StyledComponent/index.tsx +58 -180
- package/src/components/StyledComponent/useEffects/index.tsx +76 -0
- package/src/index.ts +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goobs-frontend",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.58",
|
|
4
4
|
"description": "A comprehensive React-based UI library built on Material-UI, offering a wide range of customizable components including grids, typography, buttons, cards, forms, navigation, pricing tables, steppers, tooltips, accordions, and more. Designed for building responsive and consistent user interfaces with advanced features like form validation, theming, and code syntax highlighting.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -4,12 +4,16 @@ import { Input, Box } from '@mui/material'
|
|
|
4
4
|
import { useCodeConfirmation } from './utils/useCodeConfirmation'
|
|
5
5
|
import { columnconfig } from '../../components/Grid'
|
|
6
6
|
import { red, green } from '../../styles/palette'
|
|
7
|
+
import { set } from 'goobs-cache'
|
|
7
8
|
|
|
8
9
|
export interface ConfirmationCodeInputsProps {
|
|
9
10
|
identifier?: string
|
|
10
11
|
columnconfig?: columnconfig
|
|
11
12
|
isValid: boolean
|
|
12
13
|
codeLength?: number
|
|
14
|
+
'aria-label'?: string
|
|
15
|
+
'aria-required'?: boolean
|
|
16
|
+
'aria-invalid'?: boolean
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
/**
|
|
@@ -21,12 +25,17 @@ export interface ConfirmationCodeInputsProps {
|
|
|
21
25
|
const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
|
|
22
26
|
codeLength = 6,
|
|
23
27
|
isValid,
|
|
28
|
+
'aria-label': ariaLabel,
|
|
29
|
+
'aria-required': ariaRequired,
|
|
30
|
+
'aria-invalid': ariaInvalid,
|
|
24
31
|
...props
|
|
25
32
|
}) => {
|
|
26
|
-
const { handleCodeChange, handleKeyDown } = useCodeConfirmation(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
const { handleCodeChange, handleKeyDown, combinedCode } = useCodeConfirmation(
|
|
34
|
+
{
|
|
35
|
+
codeLength,
|
|
36
|
+
isValid,
|
|
37
|
+
}
|
|
38
|
+
)
|
|
30
39
|
|
|
31
40
|
/**
|
|
32
41
|
* handleChange function is called when the value of an input field changes.
|
|
@@ -63,8 +72,29 @@ const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
|
|
|
63
72
|
handleKeyDown(event, index)
|
|
64
73
|
}
|
|
65
74
|
|
|
75
|
+
/**
|
|
76
|
+
* useEffect hook is used to set the verification code into an atom using goobs-cache.
|
|
77
|
+
* It sets the code whenever the combinedCode changes and the code is valid.
|
|
78
|
+
*/
|
|
79
|
+
React.useEffect(() => {
|
|
80
|
+
if (isValid) {
|
|
81
|
+
set(
|
|
82
|
+
'verificationCode',
|
|
83
|
+
combinedCode,
|
|
84
|
+
new Date(Date.now() + 3600000),
|
|
85
|
+
'memory'
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}, [combinedCode, isValid])
|
|
89
|
+
|
|
66
90
|
return (
|
|
67
|
-
<Box
|
|
91
|
+
<Box
|
|
92
|
+
display="flex"
|
|
93
|
+
flexDirection="row"
|
|
94
|
+
alignItems="center"
|
|
95
|
+
role="group"
|
|
96
|
+
aria-label={ariaLabel || 'Confirmation Code'}
|
|
97
|
+
>
|
|
68
98
|
<Box display="flex" gap={1}>
|
|
69
99
|
{Array.from({ length: codeLength }, (_, index) => (
|
|
70
100
|
<Input
|
|
@@ -72,6 +102,9 @@ const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
|
|
|
72
102
|
name={`code${index + 1}`}
|
|
73
103
|
inputProps={{
|
|
74
104
|
maxLength: 1,
|
|
105
|
+
'aria-label': `Code Digit ${index + 1}`,
|
|
106
|
+
'aria-required': ariaRequired,
|
|
107
|
+
'aria-invalid': ariaInvalid,
|
|
75
108
|
}}
|
|
76
109
|
sx={{
|
|
77
110
|
border: '1px solid',
|
|
@@ -100,6 +133,8 @@ const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
|
|
|
100
133
|
borderRadius="50%"
|
|
101
134
|
bgcolor={isValid ? green.main : red.main}
|
|
102
135
|
ml={2}
|
|
136
|
+
role="status"
|
|
137
|
+
aria-label={isValid ? 'Code is valid' : 'Code is invalid'}
|
|
103
138
|
/>
|
|
104
139
|
</Box>
|
|
105
140
|
)
|
|
@@ -42,9 +42,6 @@ const useStyledComponent = (grid: {
|
|
|
42
42
|
label,
|
|
43
43
|
shrunklabellocation,
|
|
44
44
|
value,
|
|
45
|
-
onChange,
|
|
46
|
-
defaultValue,
|
|
47
|
-
inputRef,
|
|
48
45
|
columnconfig: itemColumnConfig,
|
|
49
46
|
valuestatus,
|
|
50
47
|
cellconfig,
|
|
@@ -88,9 +85,6 @@ const useStyledComponent = (grid: {
|
|
|
88
85
|
label={label}
|
|
89
86
|
shrunklabellocation={shrunklabellocation}
|
|
90
87
|
value={value}
|
|
91
|
-
onChange={onChange}
|
|
92
|
-
defaultValue={defaultValue}
|
|
93
|
-
inputRef={inputRef}
|
|
94
88
|
valuestatus={valuestatus}
|
|
95
89
|
required={required}
|
|
96
90
|
{...restProps}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// File: src/components/PopupForm/index.tsx
|
|
1
2
|
'use client'
|
|
2
3
|
import React, { forwardRef, useImperativeHandle, useRef } from 'react'
|
|
3
4
|
import { Close } from '@mui/icons-material'
|
|
@@ -20,9 +21,11 @@ export interface PopupFormProps {
|
|
|
20
21
|
/** Function to call when closing the dialog */
|
|
21
22
|
onClose: () => void
|
|
22
23
|
/** ContentSectionProps to render the form content */
|
|
23
|
-
grids
|
|
24
|
+
grids?: ContentSectionProps['grids']
|
|
24
25
|
/** Optional function to handle form submission */
|
|
25
26
|
onSubmit?: () => void
|
|
27
|
+
/** Optional direct content to render */
|
|
28
|
+
content?: React.ReactNode
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -35,7 +38,7 @@ export interface PopupFormProps {
|
|
|
35
38
|
* @returns The rendered popup form.
|
|
36
39
|
*/
|
|
37
40
|
const PopupForm = forwardRef<HTMLFormElement, PopupFormProps>(
|
|
38
|
-
({ title, description, open, onClose, grids, onSubmit }, ref) => {
|
|
41
|
+
({ title, description, open, onClose, grids, onSubmit, content }, ref) => {
|
|
39
42
|
const [submittedData, setSubmittedData] = React.useState<
|
|
40
43
|
Record<string, string>
|
|
41
44
|
>({})
|
|
@@ -84,7 +87,7 @@ const PopupForm = forwardRef<HTMLFormElement, PopupFormProps>(
|
|
|
84
87
|
/** Combine the header grid with the provided content grids */
|
|
85
88
|
const contentSectionGrids: ContentSectionProps['grids'] = [
|
|
86
89
|
headerGrid,
|
|
87
|
-
...grids,
|
|
90
|
+
...(grids || []),
|
|
88
91
|
]
|
|
89
92
|
|
|
90
93
|
/**
|
|
@@ -107,9 +110,24 @@ const PopupForm = forwardRef<HTMLFormElement, PopupFormProps>(
|
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Renders the header typography elements
|
|
115
|
+
*/
|
|
116
|
+
const renderHeaderTypography = () => {
|
|
117
|
+
if (Array.isArray(headerGrid.typography)) {
|
|
118
|
+
return headerGrid.typography.map(
|
|
119
|
+
(typo: ExtendedTypographyProps, index: number) => (
|
|
120
|
+
<Typography key={index} {...typo} />
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
} else if (headerGrid.typography) {
|
|
124
|
+
return <Typography {...headerGrid.typography} />
|
|
125
|
+
}
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
110
129
|
return (
|
|
111
130
|
<Dialog open={open} onClose={onClose} fullWidth maxWidth="sm">
|
|
112
|
-
{/* Close button */}
|
|
113
131
|
<IconButton
|
|
114
132
|
size="small"
|
|
115
133
|
onClick={onClose}
|
|
@@ -122,16 +140,20 @@ const PopupForm = forwardRef<HTMLFormElement, PopupFormProps>(
|
|
|
122
140
|
>
|
|
123
141
|
<Close />
|
|
124
142
|
</IconButton>
|
|
125
|
-
{/* Form container */}
|
|
126
143
|
<Box
|
|
127
144
|
// @ts-ignore
|
|
128
145
|
sx={formContainerStyle}
|
|
129
146
|
>
|
|
130
147
|
<form onSubmit={handleSubmit} ref={internalFormRef}>
|
|
131
|
-
{
|
|
132
|
-
|
|
148
|
+
{content ? (
|
|
149
|
+
<>
|
|
150
|
+
{renderHeaderTypography()}
|
|
151
|
+
{content}
|
|
152
|
+
</>
|
|
153
|
+
) : (
|
|
154
|
+
<ContentSection grids={contentSectionGrids} />
|
|
155
|
+
)}
|
|
133
156
|
</form>
|
|
134
|
-
{/* Display submitted data */}
|
|
135
157
|
{Object.keys(submittedData).length > 0 && (
|
|
136
158
|
<Box mt={2}>
|
|
137
159
|
<Typography fontvariant="merrih6" text="Submitted Data:" />
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { InputAdornment, Button, Box } from '@mui/material'
|
|
3
|
-
import SearchIcon from '../../
|
|
4
|
-
import ShowHideEyeIcon from '../../
|
|
5
|
-
import DownArrowFilledIcon from '../../
|
|
6
|
-
|
|
3
|
+
import SearchIcon from '../../Icons/Search'
|
|
4
|
+
import ShowHideEyeIcon from '../../Icons/ShowHideEye'
|
|
5
|
+
import DownArrowFilledIcon from '../../Icons/DownArrowFilled'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Props interface for the Adornment components
|
|
9
|
+
*/
|
|
10
|
+
export interface AdornmentProps {
|
|
11
|
+
componentvariant: string
|
|
12
|
+
iconcolor?: string
|
|
13
|
+
passwordVisible?: boolean
|
|
14
|
+
marginRight?: number | string
|
|
15
|
+
handleIncrement?: () => void
|
|
16
|
+
handleDecrement?: () => void
|
|
17
|
+
}
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
20
|
* StartAdornment component renders the start adornment for the input component.
|
|
10
21
|
* @param props The props for the StartAdornment component.
|
|
11
22
|
* @returns The start adornment component or null.
|
|
12
23
|
*/
|
|
13
|
-
const StartAdornment: React.FC<AdornmentProps> = props => {
|
|
24
|
+
export const StartAdornment: React.FC<AdornmentProps> = props => {
|
|
14
25
|
const { componentvariant, iconcolor } = props
|
|
15
26
|
// Render the search icon for the search bar variant
|
|
16
27
|
if (componentvariant === 'searchbar') {
|
|
@@ -28,26 +39,22 @@ const StartAdornment: React.FC<AdornmentProps> = props => {
|
|
|
28
39
|
* @param props The props for the EndAdornment component.
|
|
29
40
|
* @returns The end adornment component or null.
|
|
30
41
|
*/
|
|
31
|
-
const EndAdornment: React.FC<AdornmentProps> = props => {
|
|
42
|
+
export const EndAdornment: React.FC<AdornmentProps> = props => {
|
|
32
43
|
const {
|
|
33
44
|
componentvariant,
|
|
34
45
|
passwordVisible,
|
|
35
46
|
handleIncrement,
|
|
36
47
|
handleDecrement,
|
|
37
48
|
} = props
|
|
38
|
-
|
|
39
49
|
const [isPasswordVisible, setIsPasswordVisible] = useState(
|
|
40
50
|
passwordVisible || false
|
|
41
51
|
)
|
|
42
|
-
|
|
43
52
|
const adornmentStyle = {
|
|
44
53
|
cursor: 'pointer',
|
|
45
54
|
}
|
|
46
|
-
|
|
47
55
|
const togglePasswordVisibility = () => {
|
|
48
56
|
setIsPasswordVisible(!isPasswordVisible)
|
|
49
57
|
}
|
|
50
|
-
|
|
51
58
|
// Render the show/hide eye icon for the password variant
|
|
52
59
|
if (componentvariant === 'password') {
|
|
53
60
|
return (
|
|
@@ -118,5 +125,3 @@ const EndAdornment: React.FC<AdornmentProps> = props => {
|
|
|
118
125
|
}
|
|
119
126
|
return null
|
|
120
127
|
}
|
|
121
|
-
|
|
122
|
-
export { StartAdornment, EndAdornment }
|
|
@@ -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 } 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,19 @@ 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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
statusMessage: string
|
|
28
|
-
spreadMessage: string
|
|
29
|
-
spreadMessagePriority: number
|
|
30
|
-
formname: string
|
|
31
|
-
required: boolean
|
|
32
|
-
}
|
|
17
|
+
import {
|
|
18
|
+
useHelperFooterEffect,
|
|
19
|
+
useHasInputEffect,
|
|
20
|
+
usePreventAutocompleteEffect,
|
|
21
|
+
useValidateRequiredEffect,
|
|
22
|
+
useShowErrorEffect,
|
|
23
|
+
} from './useEffects'
|
|
33
24
|
|
|
34
25
|
/**
|
|
35
26
|
* Props interface for the StyledComponent
|
|
@@ -70,26 +61,13 @@ export interface StyledComponentProps {
|
|
|
70
61
|
shrunklabellocation?: 'onnotch' | 'above'
|
|
71
62
|
value?: string
|
|
72
63
|
valuestatus?: boolean
|
|
73
|
-
defaultValue?: string
|
|
74
|
-
inputRef?: RefObject<HTMLInputElement>
|
|
75
64
|
focused?: boolean
|
|
76
65
|
required?: boolean
|
|
77
66
|
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
|
|
67
|
+
'aria-label'?: string
|
|
68
|
+
'aria-required'?: boolean
|
|
69
|
+
'aria-invalid'?: boolean
|
|
70
|
+
'aria-describedby'?: string
|
|
93
71
|
}
|
|
94
72
|
|
|
95
73
|
/**
|
|
@@ -121,7 +99,6 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
121
99
|
const {
|
|
122
100
|
label,
|
|
123
101
|
componentvariant,
|
|
124
|
-
inputRef,
|
|
125
102
|
name,
|
|
126
103
|
backgroundcolor,
|
|
127
104
|
iconcolor,
|
|
@@ -135,7 +112,10 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
135
112
|
required = false,
|
|
136
113
|
formname,
|
|
137
114
|
formSubmitted = false,
|
|
138
|
-
|
|
115
|
+
'aria-label': ariaLabel,
|
|
116
|
+
'aria-required': ariaRequired,
|
|
117
|
+
'aria-invalid': ariaInvalid,
|
|
118
|
+
'aria-describedby': ariaDescribedBy,
|
|
139
119
|
} = props
|
|
140
120
|
|
|
141
121
|
const { validateField } = useHelperFooter()
|
|
@@ -148,50 +128,7 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
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,106 +141,38 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
204
141
|
handleDecrement,
|
|
205
142
|
} = useSplitButton(props)
|
|
206
143
|
|
|
144
|
+
// useEffect hooks
|
|
145
|
+
useHelperFooterEffect(setHelperFooterValue)
|
|
146
|
+
useHasInputEffect(value, valuestatus, setHasInput)
|
|
147
|
+
usePreventAutocompleteEffect(inputRefInternal)
|
|
148
|
+
useValidateRequiredEffect(required, formname, name, label)
|
|
149
|
+
useShowErrorEffect(formSubmitted, hasInput, isFocused, setShowError)
|
|
150
|
+
|
|
151
|
+
const currentHelperFooter = name ? helperFooterValue[name] : undefined
|
|
152
|
+
|
|
207
153
|
/**
|
|
208
154
|
* Handle the change event of the input element.
|
|
209
155
|
* @param e The change event.
|
|
210
156
|
*/
|
|
211
|
-
const handleChange =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (onChange) {
|
|
222
|
-
onChange(e)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
setHasInput(!!e.target.value)
|
|
226
|
-
|
|
227
|
-
const formData = new FormData()
|
|
228
|
-
formData.append(e.target.name, e.target.value)
|
|
229
|
-
|
|
230
|
-
if (name && label && formname) {
|
|
231
|
-
validateField(name, formData, label, required, formname)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Implement validation logic directly
|
|
235
|
-
if (name) {
|
|
236
|
-
let result: HelperFooterMessage | undefined
|
|
237
|
-
|
|
238
|
-
// Basic validation logic (you can expand this based on your requirements)
|
|
239
|
-
if (required && !e.target.value) {
|
|
240
|
-
result = {
|
|
241
|
-
status: 'error',
|
|
242
|
-
statusMessage: `${label} is required.`,
|
|
243
|
-
spreadMessage: `${label} is required.`,
|
|
244
|
-
spreadMessagePriority: 1,
|
|
245
|
-
formname: formname || '',
|
|
246
|
-
required: true,
|
|
247
|
-
}
|
|
248
|
-
} else if (
|
|
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
|
|
262
|
-
|
|
263
|
-
if (result) {
|
|
264
|
-
setHelperFooterValue(prevState => ({
|
|
265
|
-
...prevState,
|
|
266
|
-
[name]: result,
|
|
267
|
-
}))
|
|
268
|
-
} else {
|
|
269
|
-
// Clear any existing error for this field
|
|
270
|
-
setHelperFooterValue(prevState => {
|
|
271
|
-
const newState = { ...prevState }
|
|
272
|
-
delete newState[name]
|
|
273
|
-
return newState
|
|
274
|
-
})
|
|
275
|
-
}
|
|
276
|
-
}
|
|
157
|
+
const handleChange = (
|
|
158
|
+
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
159
|
+
) => {
|
|
160
|
+
if (componentvariant === 'phonenumber') {
|
|
161
|
+
handlePhoneNumberChange(e)
|
|
162
|
+
} else if (componentvariant === 'splitbutton') {
|
|
163
|
+
// Only allow numbers for splitbutton
|
|
164
|
+
const numValue = e.target.value.replace(/[^0-9]/g, '')
|
|
165
|
+
e.target.value = numValue
|
|
166
|
+
}
|
|
277
167
|
|
|
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
|
-
)
|
|
168
|
+
setHasInput(!!e.target.value)
|
|
305
169
|
|
|
306
|
-
|
|
170
|
+
const formData = new FormData()
|
|
171
|
+
formData.append(e.target.name, e.target.value)
|
|
172
|
+
if (name && label && formname) {
|
|
173
|
+
validateField(name, formData, label, required, formname)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
307
176
|
|
|
308
177
|
/**
|
|
309
178
|
* Handle the focus event of the input element.
|
|
@@ -376,13 +245,14 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
376
245
|
focused: shouldShrinkLabel,
|
|
377
246
|
})}
|
|
378
247
|
shrink={shouldShrinkLabel}
|
|
248
|
+
htmlFor={name}
|
|
379
249
|
>
|
|
380
250
|
{label}
|
|
381
251
|
</InputLabel>
|
|
382
252
|
)}
|
|
383
253
|
<Box ref={inputBoxRef} sx={{ width: '100%' }}>
|
|
384
254
|
<NoAutofillOutlinedInput
|
|
385
|
-
ref={
|
|
255
|
+
ref={inputRefInternal}
|
|
386
256
|
style={{
|
|
387
257
|
backgroundColor: backgroundcolor || 'inherit',
|
|
388
258
|
width: '100%',
|
|
@@ -409,6 +279,13 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
409
279
|
: 'text',
|
|
410
280
|
},
|
|
411
281
|
placeholder: placeholder || '',
|
|
282
|
+
'aria-label': ariaLabel,
|
|
283
|
+
'aria-invalid': ariaInvalid,
|
|
284
|
+
'aria-required': ariaRequired,
|
|
285
|
+
'aria-describedby':
|
|
286
|
+
ariaDescribedBy || currentHelperFooter?.statusMessage
|
|
287
|
+
? `${name}-helper-text`
|
|
288
|
+
: undefined,
|
|
412
289
|
}}
|
|
413
290
|
type={
|
|
414
291
|
componentvariant === 'password' && !passwordVisible
|
|
@@ -459,6 +336,7 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
|
|
|
459
336
|
</Box>
|
|
460
337
|
{showError && currentHelperFooter?.statusMessage && (
|
|
461
338
|
<Typography
|
|
339
|
+
id={`${name}-helper-text`}
|
|
462
340
|
fontvariant="merrihelperfooter"
|
|
463
341
|
fontcolor={
|
|
464
342
|
currentHelperFooter?.status === 'error'
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
|
+
import { get, JSONValue } from 'goobs-cache'
|
|
3
|
+
import {
|
|
4
|
+
HelperFooterMessage,
|
|
5
|
+
useHelperFooter,
|
|
6
|
+
} from '../helperfooter/useHelperFooter'
|
|
7
|
+
|
|
8
|
+
export const useHelperFooterEffect = (
|
|
9
|
+
setHelperFooterValue: React.Dispatch<
|
|
10
|
+
React.SetStateAction<Record<string, HelperFooterMessage>>
|
|
11
|
+
>
|
|
12
|
+
) => {
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const fetchHelperFooter = async () => {
|
|
15
|
+
const result = await get('helperFooter', 'client')
|
|
16
|
+
if (result && typeof result === 'object' && 'value' in result) {
|
|
17
|
+
setHelperFooterValue(
|
|
18
|
+
(result as JSONValue).value as Record<string, HelperFooterMessage>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
fetchHelperFooter()
|
|
23
|
+
}, [setHelperFooterValue])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const useHasInputEffect = (
|
|
27
|
+
value: string | undefined,
|
|
28
|
+
valuestatus: boolean | undefined,
|
|
29
|
+
setHasInput: React.Dispatch<React.SetStateAction<boolean>>
|
|
30
|
+
) => {
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
setHasInput(!!value || !!valuestatus)
|
|
33
|
+
}, [value, valuestatus, setHasInput])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const usePreventAutocompleteEffect = (
|
|
37
|
+
inputRefInternal: React.RefObject<HTMLInputElement>
|
|
38
|
+
) => {
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const input = inputRefInternal.current
|
|
41
|
+
if (input) {
|
|
42
|
+
input.setAttribute('autocomplete', 'new-password')
|
|
43
|
+
input.setAttribute('autocorrect', 'off')
|
|
44
|
+
input.setAttribute('autocapitalize', 'none')
|
|
45
|
+
input.setAttribute('spellcheck', 'false')
|
|
46
|
+
}
|
|
47
|
+
}, [inputRefInternal])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const useValidateRequiredEffect = (
|
|
51
|
+
required: boolean | undefined,
|
|
52
|
+
formname: string | undefined,
|
|
53
|
+
name: string | undefined,
|
|
54
|
+
label: string | undefined
|
|
55
|
+
) => {
|
|
56
|
+
const { validateField } = useHelperFooter()
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (required && formname && name && label) {
|
|
60
|
+
const emptyFormData = new FormData()
|
|
61
|
+
emptyFormData.append(name, '')
|
|
62
|
+
validateField(name, emptyFormData, label, required, formname)
|
|
63
|
+
}
|
|
64
|
+
}, [required, formname, name, label, validateField])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const useShowErrorEffect = (
|
|
68
|
+
formSubmitted: boolean,
|
|
69
|
+
hasInput: boolean,
|
|
70
|
+
isFocused: boolean,
|
|
71
|
+
setShowError: React.Dispatch<React.SetStateAction<boolean>>
|
|
72
|
+
) => {
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
setShowError(formSubmitted || (hasInput && !isFocused))
|
|
75
|
+
}, [formSubmitted, hasInput, isFocused, setShowError])
|
|
76
|
+
}
|
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,
|