goobs-frontend 0.7.53

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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/package.json +122 -0
  4. package/src/app/_app.tsx +8 -0
  5. package/src/atoms/helperfooter.ts +21 -0
  6. package/src/components/Button/index.tsx +241 -0
  7. package/src/components/ConfirmationCodeInput/index.tsx +108 -0
  8. package/src/components/ConfirmationCodeInput/utils/useCodeConfirmation.tsx +129 -0
  9. package/src/components/Content/Structure/animations.tsx +106 -0
  10. package/src/components/Content/Structure/button/useButton.tsx +80 -0
  11. package/src/components/Content/Structure/confirmationinput/useConfirmationInput.tsx +56 -0
  12. package/src/components/Content/Structure/image/useImage.tsx +62 -0
  13. package/src/components/Content/Structure/link/useLink.tsx +60 -0
  14. package/src/components/Content/Structure/radiogroup/useGridRadioGroup.tsx +62 -0
  15. package/src/components/Content/Structure/styledcomponent/useStyledComponent.tsx +96 -0
  16. package/src/components/Content/Structure/typography/useGridTypography.tsx +53 -0
  17. package/src/components/Content/index.tsx +147 -0
  18. package/src/components/Form/Popup/index.tsx +121 -0
  19. package/src/components/Grid/defaultconfig.tsx +131 -0
  20. package/src/components/Grid/index.tsx +265 -0
  21. package/src/components/Icons/Calendar.tsx +21 -0
  22. package/src/components/Icons/DownArrowFilled.tsx +9 -0
  23. package/src/components/Icons/Drag.tsx +9 -0
  24. package/src/components/Icons/FavoriteIcon.tsx +24 -0
  25. package/src/components/Icons/Info.tsx +12 -0
  26. package/src/components/Icons/Search.tsx +29 -0
  27. package/src/components/Icons/ShowHideEye.tsx +16 -0
  28. package/src/components/RadioGroup/index.tsx +96 -0
  29. package/src/components/StyledComponent/adornments.tsx +118 -0
  30. package/src/components/StyledComponent/helperfooter/useHelperFooter.tsx +411 -0
  31. package/src/components/StyledComponent/hooks/useDropdown.tsx +144 -0
  32. package/src/components/StyledComponent/hooks/usePassword.tsx +23 -0
  33. package/src/components/StyledComponent/hooks/usePhoneNumber.tsx +75 -0
  34. package/src/components/StyledComponent/hooks/useSearchbar.tsx +44 -0
  35. package/src/components/StyledComponent/hooks/useSplitButton.tsx +66 -0
  36. package/src/components/StyledComponent/index.tsx +404 -0
  37. package/src/components/Typography/index.tsx +268 -0
  38. package/src/index.ts +210 -0
  39. package/src/main.tsx +7 -0
  40. package/src/styles/Form/index.ts +7 -0
  41. package/src/styles/StyledComponent/Label/index.ts +76 -0
  42. package/src/styles/palette.ts +143 -0
  43. package/src/styles/typography.ts +184 -0
  44. package/src/types/async-lock.d.ts +35 -0
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, useEffect } from 'react'
4
+ import { StyledComponentProps } from '../index'
5
+
6
+ /**
7
+ * useSplitButton hook handles the state and logic for a split button component.
8
+ * @param props The props for the split button component.
9
+ * @returns An object containing the value and handlers for the split button.
10
+ */
11
+ export const useSplitButton = (props: StyledComponentProps) => {
12
+ const [value, setValue] = useState('0')
13
+
14
+ /**
15
+ * Update the value state when the value prop changes.
16
+ */
17
+ useEffect(() => {
18
+ if (props.value !== undefined) {
19
+ setValue(props.value)
20
+ }
21
+ }, [props.value])
22
+
23
+ /**
24
+ * Increment the value by 1.
25
+ */
26
+ const handleIncrement = useCallback(() => {
27
+ setValue(prev => {
28
+ const num = parseInt(prev)
29
+ if (isNaN(num)) {
30
+ return '0'
31
+ }
32
+ const newValue = num + 1
33
+ return newValue > 0 ? newValue.toString() : '0'
34
+ })
35
+ }, [])
36
+
37
+ /**
38
+ * Decrement the value by 1.
39
+ */
40
+ const handleDecrement = useCallback(() => {
41
+ setValue(prev => {
42
+ const num = parseInt(prev)
43
+ if (isNaN(num)) {
44
+ return '0'
45
+ }
46
+ const newValue = num - 1
47
+ return newValue >= 0 ? newValue.toString() : '0'
48
+ })
49
+ }, [])
50
+
51
+ /**
52
+ * Update the value when the input value changes.
53
+ * @param newValue The new value from the input.
54
+ */
55
+ const handleChange = useCallback((newValue: string) => {
56
+ const numValue = newValue.replace(/[^0-9]/g, '')
57
+ setValue(numValue === '' ? '0' : numValue)
58
+ }, [])
59
+
60
+ return {
61
+ value,
62
+ handleIncrement,
63
+ handleDecrement,
64
+ handleChange,
65
+ }
66
+ }
@@ -0,0 +1,404 @@
1
+ 'use client'
2
+
3
+ import React, {
4
+ useCallback,
5
+ useState,
6
+ useRef,
7
+ useEffect,
8
+ RefObject,
9
+ } from 'react'
10
+ import { Box, InputLabel, OutlinedInput, styled } from '@mui/material'
11
+ import { useDropdown } from './hooks/useDropdown'
12
+ import { usePhoneNumber } from './hooks/usePhoneNumber'
13
+ import { usePassword } from './hooks/usePassword'
14
+ import { useSplitButton } from './hooks/useSplitButton'
15
+ import { Typography } from './../Typography'
16
+ import { red, green } from '../../styles/palette'
17
+ import { StartAdornment, EndAdornment } from './adornments'
18
+ import { useHelperFooter } from './helperfooter/useHelperFooter'
19
+ import labelStyles from '../../styles/StyledComponent/Label'
20
+ import { HelperFooterMessage } from '../../atoms/helperfooter'
21
+
22
+ export interface StyledComponentProps {
23
+ name?: string
24
+ outlinecolor?: string
25
+ iconcolor?: string
26
+ backgroundcolor?: string
27
+ notched?: boolean
28
+ combinedfontcolor?: string
29
+ unshrunkfontcolor?: string
30
+ shrunkfontcolor?: string
31
+ autoComplete?: string
32
+ componentvariant?:
33
+ | 'multilinetextfield'
34
+ | 'dropdown'
35
+ | 'searchbar'
36
+ | 'textfield'
37
+ | 'phonenumber'
38
+ | 'password'
39
+ | 'ip-address'
40
+ | 'email'
41
+ | 'url'
42
+ | 'credit-card'
43
+ | 'number'
44
+ | 'hostname'
45
+ | 'domain'
46
+ | 'time'
47
+ | 'date'
48
+ | 'splitbutton'
49
+ options?: readonly string[]
50
+ helperfooter?: HelperFooterMessage
51
+ placeholder?: string
52
+ minRows?: number
53
+ formname?: string
54
+ label?: string
55
+ shrunklabellocation?: 'onnotch' | 'above'
56
+ value?: string
57
+ valuestatus?: boolean
58
+ onChange?: (
59
+ event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
60
+ ) => void
61
+ defaultValue?: string
62
+ inputRef?: RefObject<HTMLInputElement>
63
+ serverActionValidation?: (
64
+ formData: FormData
65
+ ) => Promise<HelperFooterMessage | undefined>
66
+ focused?: boolean
67
+ required?: boolean
68
+ formSubmitted?: boolean
69
+ }
70
+
71
+ export interface AdornmentProps {
72
+ componentvariant: string
73
+ iconcolor?: string
74
+ passwordVisible?: boolean
75
+ togglePasswordVisibility?: (event: React.MouseEvent<HTMLDivElement>) => void
76
+ marginRight?: number | string
77
+ handleIncrement?: () => void
78
+ handleDecrement?: () => void
79
+ }
80
+
81
+ const NoAutofillOutlinedInput = styled(OutlinedInput)(() => ({
82
+ '& .MuiInputBase-input': {
83
+ '&:-webkit-autofill': {
84
+ transition: 'background-color 600000s 0s, color 600000s 0s',
85
+ },
86
+ '&:-webkit-autofill:hover': {
87
+ transition: 'background-color 600000s 0s, color 600000s 0s',
88
+ },
89
+ '&:-webkit-autofill:focus': {
90
+ transition: 'background-color 600000s 0s, color 600000s 0s',
91
+ },
92
+ '&:-webkit-autofill:active': {
93
+ transition: 'background-color 600000s 0s, color 600000s 0s',
94
+ },
95
+ },
96
+ }))
97
+
98
+ /**
99
+ * StyledComponent is a customizable input component with various variants and features.
100
+ * @param props The props for the StyledComponent.
101
+ * @returns The rendered StyledComponent.
102
+ */
103
+ const StyledComponent: React.FC<StyledComponentProps> = props => {
104
+ const {
105
+ label,
106
+ componentvariant,
107
+ inputRef,
108
+ name,
109
+ serverActionValidation,
110
+ onChange,
111
+ backgroundcolor,
112
+ iconcolor,
113
+ unshrunkfontcolor,
114
+ combinedfontcolor,
115
+ shrunkfontcolor,
116
+ shrunklabellocation,
117
+ value,
118
+ valuestatus,
119
+ placeholder,
120
+ required = false,
121
+ formname,
122
+ formSubmitted = false,
123
+ } = props
124
+
125
+ const { validateField, helperFooterAtomValue } = useHelperFooter()
126
+ const [isFocused, setIsFocused] = useState(false)
127
+ const [hasInput, setHasInput] = useState(false)
128
+ const [showError, setShowError] = useState(false)
129
+ const inputRefInternal = useRef<HTMLInputElement>(null)
130
+ const inputBoxRef = useRef<HTMLDivElement>(null)
131
+
132
+ /**
133
+ * Update the hasInput state when the value or valuestatus props change.
134
+ */
135
+ useEffect(() => {
136
+ setHasInput(!!value || !!valuestatus)
137
+ }, [value, valuestatus])
138
+
139
+ /**
140
+ * Set autocomplete, autocorrect, autocapitalize, and spellcheck attributes on the input element.
141
+ */
142
+ useEffect(() => {
143
+ const input = inputRefInternal.current || inputRef?.current
144
+ if (input) {
145
+ input.setAttribute('autocomplete', 'new-password')
146
+ input.setAttribute('autocorrect', 'off')
147
+ input.setAttribute('autocapitalize', 'none')
148
+ input.setAttribute('spellcheck', 'false')
149
+ }
150
+ }, [inputRef])
151
+
152
+ /**
153
+ * Validate the field if it is required and the form name, name, and label are provided.
154
+ */
155
+ useEffect(() => {
156
+ if (required && formname && name && label) {
157
+ const emptyFormData = new FormData()
158
+ emptyFormData.append(name, '')
159
+ validateField(name, emptyFormData, label, required, formname)
160
+ }
161
+ }, [required, formname, name, label, validateField])
162
+
163
+ /**
164
+ * Update the showError state based on the formSubmitted, hasInput, and isFocused states.
165
+ */
166
+ useEffect(() => {
167
+ setShowError(formSubmitted || (hasInput && !isFocused))
168
+ }, [formSubmitted, hasInput, isFocused])
169
+
170
+ const { handleDropdownClick, renderMenu, selectedOption, isDropdownOpen } =
171
+ useDropdown(props, inputBoxRef)
172
+
173
+ const { handlePhoneNumberChange } = usePhoneNumber({ ...props, onChange })
174
+
175
+ const { passwordVisible, togglePasswordVisibility } = usePassword()
176
+
177
+ const {
178
+ value: splitButtonValue,
179
+ handleIncrement,
180
+ handleDecrement,
181
+ } = useSplitButton(props)
182
+
183
+ /**
184
+ * Handle the change event of the input element.
185
+ * @param e The change event.
186
+ */
187
+ const handleChange = useCallback(
188
+ (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
189
+ if (componentvariant === 'phonenumber') {
190
+ handlePhoneNumberChange(e)
191
+ } else if (componentvariant === 'splitbutton') {
192
+ // Only allow numbers for splitbutton
193
+ const numValue = e.target.value.replace(/[^0-9]/g, '')
194
+ if (onChange) {
195
+ onChange({ ...e, target: { ...e.target, value: numValue } })
196
+ }
197
+ } else {
198
+ if (onChange) {
199
+ onChange(e)
200
+ }
201
+ }
202
+
203
+ setHasInput(!!e.target.value)
204
+
205
+ const formData = new FormData()
206
+ formData.append(e.target.name, e.target.value)
207
+
208
+ if (name && label && formname) {
209
+ validateField(name, formData, label, required, formname)
210
+ }
211
+
212
+ if (serverActionValidation && name) {
213
+ serverActionValidation(formData)
214
+ }
215
+ },
216
+ [
217
+ componentvariant,
218
+ onChange,
219
+ handlePhoneNumberChange,
220
+ validateField,
221
+ name,
222
+ label,
223
+ required,
224
+ formname,
225
+ serverActionValidation,
226
+ ]
227
+ )
228
+
229
+ const currentHelperFooter = name ? helperFooterAtomValue[name] : undefined
230
+
231
+ /**
232
+ * Handle the focus event of the input element.
233
+ */
234
+ const handleFocus = () => {
235
+ setIsFocused(true)
236
+ }
237
+
238
+ /**
239
+ * Handle the blur event of the input element.
240
+ */
241
+ const handleBlur = () => {
242
+ setIsFocused(false)
243
+ if (name && label && !hasInput && formname) {
244
+ const formData = new FormData()
245
+ formData.append(name, '')
246
+ validateField(name, formData, label, required, formname)
247
+ }
248
+ }
249
+
250
+ const isDropdownVariant = componentvariant === 'dropdown'
251
+ const isSplitButtonVariant = componentvariant === 'splitbutton'
252
+ const isNotchedVariant =
253
+ !isDropdownVariant &&
254
+ !isSplitButtonVariant &&
255
+ shrunklabellocation !== 'above' &&
256
+ !!label
257
+ const hasPlaceholder = !!placeholder
258
+
259
+ /**
260
+ * Determine if the label should be shrunk based on various conditions.
261
+ */
262
+ const shouldShrinkLabel =
263
+ isFocused ||
264
+ isDropdownVariant ||
265
+ isSplitButtonVariant ||
266
+ hasPlaceholder ||
267
+ hasInput ||
268
+ componentvariant === 'phonenumber'
269
+
270
+ return (
271
+ <Box
272
+ sx={{
273
+ boxSizing: 'border-box',
274
+ display: 'flex',
275
+ flexDirection: 'column',
276
+ alignItems: 'flex-start',
277
+ width: '100%',
278
+ position: 'relative',
279
+ margin: 0,
280
+ padding: 0,
281
+ overflow: 'hidden',
282
+ }}
283
+ >
284
+ <Box
285
+ sx={{
286
+ position: 'relative',
287
+ width: '100%',
288
+ paddingTop: shrunklabellocation === 'above' ? '20px' : '5px',
289
+ }}
290
+ >
291
+ {label && (
292
+ <InputLabel
293
+ style={labelStyles({
294
+ componentvariant,
295
+ unshrunkfontcolor,
296
+ shrunkfontcolor,
297
+ shrunklabellocation,
298
+ combinedfontcolor,
299
+ focused: shouldShrinkLabel,
300
+ })}
301
+ shrink={shouldShrinkLabel}
302
+ >
303
+ {label}
304
+ </InputLabel>
305
+ )}
306
+ <Box ref={inputBoxRef} sx={{ width: '100%' }}>
307
+ <NoAutofillOutlinedInput
308
+ ref={inputRef || inputRefInternal}
309
+ style={{
310
+ backgroundColor: backgroundcolor || 'inherit',
311
+ width: '100%',
312
+ height: 40,
313
+ cursor: isSplitButtonVariant
314
+ ? 'default'
315
+ : isDropdownVariant
316
+ ? 'pointer'
317
+ : 'text',
318
+ boxSizing: 'border-box',
319
+ borderRadius: 5,
320
+ marginTop: 'auto',
321
+ paddingRight: 6,
322
+ }}
323
+ inputProps={{
324
+ style: {
325
+ width: '100%',
326
+ color: combinedfontcolor || unshrunkfontcolor || 'inherit',
327
+ height: '100%',
328
+ cursor: isSplitButtonVariant
329
+ ? 'default'
330
+ : isDropdownVariant
331
+ ? 'pointer'
332
+ : 'text',
333
+ },
334
+ placeholder: placeholder || '',
335
+ }}
336
+ type={
337
+ componentvariant === 'password' && !passwordVisible
338
+ ? 'password'
339
+ : 'text'
340
+ }
341
+ startAdornment={
342
+ <StartAdornment
343
+ componentvariant={componentvariant || ''}
344
+ iconcolor={iconcolor}
345
+ />
346
+ }
347
+ endAdornment={
348
+ <EndAdornment
349
+ componentvariant={componentvariant || ''}
350
+ passwordVisible={passwordVisible}
351
+ togglePasswordVisibility={togglePasswordVisibility}
352
+ iconcolor={iconcolor}
353
+ handleIncrement={handleIncrement}
354
+ handleDecrement={handleDecrement}
355
+ />
356
+ }
357
+ onChange={handleChange}
358
+ onFocus={handleFocus}
359
+ onBlur={handleBlur}
360
+ fullWidth
361
+ multiline={componentvariant === 'multilinetextfield'}
362
+ label={label}
363
+ autoComplete="off"
364
+ name={name}
365
+ value={
366
+ isDropdownVariant
367
+ ? selectedOption
368
+ : isSplitButtonVariant
369
+ ? splitButtonValue
370
+ : value
371
+ }
372
+ readOnly={isDropdownVariant}
373
+ notched={
374
+ (isNotchedVariant && shouldShrinkLabel) ||
375
+ ((isDropdownVariant || isSplitButtonVariant) &&
376
+ shrunklabellocation !== 'above') ||
377
+ hasPlaceholder ||
378
+ componentvariant === 'phonenumber'
379
+ }
380
+ />
381
+ {isDropdownVariant && isDropdownOpen && renderMenu}
382
+ </Box>
383
+ </Box>
384
+ {showError && currentHelperFooter?.statusMessage && (
385
+ <Typography
386
+ fontvariant="merrihelperfooter"
387
+ fontcolor={
388
+ currentHelperFooter?.status === 'error'
389
+ ? red.main
390
+ : currentHelperFooter?.status === 'success'
391
+ ? green.dark
392
+ : undefined
393
+ }
394
+ marginTop={0.5}
395
+ marginBottom={0}
396
+ align="left"
397
+ text={currentHelperFooter?.statusMessage}
398
+ />
399
+ )}
400
+ </Box>
401
+ )
402
+ }
403
+
404
+ export default StyledComponent