goobs-frontend 0.7.67 → 0.7.69
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 -16
- package/src/app/_app.tsx +8 -8
- package/src/components/Button/index.tsx +95 -203
- package/src/components/ConfirmationCodeInput/index.tsx +4 -6
- package/src/components/Content/Structure/button/useButton.tsx +1 -35
- package/src/components/Content/Structure/datefield/useDateField.tsx +55 -0
- package/src/components/Content/Structure/dropdown/useDropdown.tsx +55 -0
- package/src/components/Content/Structure/incremementNumberField/useIncremementNumberField.tsx +65 -0
- package/src/components/Content/Structure/numberField/useNumberField.tsx +55 -0
- package/src/components/Content/Structure/passwordField/usePasswordField.tsx +57 -0
- package/src/components/Content/Structure/phoneNumber/usePhoneNumber.tsx +0 -0
- package/src/components/Content/Structure/qrcode/useQRCode.tsx +69 -0
- package/src/components/Content/Structure/searchbar/useSearchbar.tsx +55 -0
- package/src/components/Content/Structure/textfield/useTextField.tsx +84 -0
- package/src/components/Content/index.tsx +44 -7
- package/src/components/DateField/index.tsx +112 -0
- package/src/components/Dropdown/index.tsx +91 -0
- package/src/components/Form/Popup/index.tsx +1 -1
- package/src/components/IncrementNumberField/index.tsx +123 -0
- package/src/components/Nav/HorizontalVariant/index.tsx +18 -12
- package/src/components/Nav/VerticalVariant/index.tsx +14 -10
- package/src/components/NumberField/index.tsx +95 -0
- package/src/components/PasswordField/index.tsx +105 -0
- package/src/components/PhoneNumberField/index.tsx +102 -0
- package/src/components/PricingTable/index.tsx +10 -8
- package/src/components/QRCode/index.tsx +105 -0
- package/src/components/Searchbar/index.tsx +77 -0
- package/src/components/TextField/index.tsx +130 -0
- package/src/components/Toolbar/index.tsx +11 -10
- package/src/index.ts +54 -9
- package/src/components/Button/hook/useHelperFooter.tsx +0 -214
- package/src/components/Content/Structure/styledcomponent/useStyledComponent.tsx +0 -104
- package/src/components/StyledComponent/adornment/index.tsx +0 -125
- package/src/components/StyledComponent/hooks/useDropdown.tsx +0 -150
- package/src/components/StyledComponent/hooks/useInputHelperFooter.tsx +0 -524
- package/src/components/StyledComponent/hooks/usePhoneNumber.tsx +0 -99
- package/src/components/StyledComponent/hooks/useRequiredFieldsValidator.tsx +0 -190
- package/src/components/StyledComponent/hooks/useSearchbar.tsx +0 -46
- package/src/components/StyledComponent/hooks/useSplitButton.tsx +0 -70
- package/src/components/StyledComponent/index.tsx +0 -473
- package/src/components/StyledComponent/useCallbacks/index.tsx +0 -46
- package/src/styles/StyledComponent/Label/index.ts +0 -76
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useState, useCallback } from 'react'
|
|
3
|
+
import { TextField, Button, Box, TextFieldProps } from '@mui/material'
|
|
4
|
+
import { styled } from '@mui/material/styles'
|
|
5
|
+
|
|
6
|
+
export interface IncrementNumberFieldProps
|
|
7
|
+
extends Omit<TextFieldProps, 'onChange'> {
|
|
8
|
+
initialValue?: string
|
|
9
|
+
onChange?: () => void
|
|
10
|
+
backgroundcolor?: string
|
|
11
|
+
outlinecolor?: string
|
|
12
|
+
fontcolor?: string
|
|
13
|
+
label?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const StyledTextField = styled(TextField)<{
|
|
17
|
+
backgroundcolor?: string
|
|
18
|
+
outlinecolor?: string
|
|
19
|
+
fontcolor?: string
|
|
20
|
+
}>(({ theme, backgroundcolor, outlinecolor, fontcolor }) => ({
|
|
21
|
+
'& .MuiOutlinedInput-root': {
|
|
22
|
+
backgroundColor: backgroundcolor || theme.palette.background.paper,
|
|
23
|
+
'& fieldset': {
|
|
24
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
25
|
+
},
|
|
26
|
+
'&:hover fieldset': {
|
|
27
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
28
|
+
},
|
|
29
|
+
'&.Mui-focused fieldset': {
|
|
30
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
'& .MuiInputLabel-root': {
|
|
34
|
+
color: fontcolor || theme.palette.text.primary,
|
|
35
|
+
'&.Mui-focused': {
|
|
36
|
+
color: fontcolor || theme.palette.primary.main,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
'& .MuiInputBase-input': {
|
|
40
|
+
color: fontcolor || theme.palette.text.primary,
|
|
41
|
+
textAlign: 'center',
|
|
42
|
+
padding: '8px 0',
|
|
43
|
+
},
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
const StyledButton = styled(Button)(({ theme }) => ({
|
|
47
|
+
minWidth: '36px',
|
|
48
|
+
padding: 0,
|
|
49
|
+
height: '100%',
|
|
50
|
+
borderRadius: '4px',
|
|
51
|
+
backgroundColor: theme.palette.grey[300],
|
|
52
|
+
color: theme.palette.text.primary,
|
|
53
|
+
'&:hover': {
|
|
54
|
+
backgroundColor: theme.palette.grey[400],
|
|
55
|
+
},
|
|
56
|
+
}))
|
|
57
|
+
|
|
58
|
+
const IncrementNumberField: React.FC<IncrementNumberFieldProps> = ({
|
|
59
|
+
initialValue = '0',
|
|
60
|
+
onChange,
|
|
61
|
+
backgroundcolor,
|
|
62
|
+
outlinecolor,
|
|
63
|
+
fontcolor,
|
|
64
|
+
label,
|
|
65
|
+
...rest
|
|
66
|
+
}) => {
|
|
67
|
+
const [value, setValue] = useState(initialValue)
|
|
68
|
+
|
|
69
|
+
const handleIncrement = useCallback(() => {
|
|
70
|
+
setValue(prev => {
|
|
71
|
+
const num = parseInt(prev)
|
|
72
|
+
if (isNaN(num)) {
|
|
73
|
+
return '0'
|
|
74
|
+
}
|
|
75
|
+
const newValue = (num + 1).toString()
|
|
76
|
+
onChange?.()
|
|
77
|
+
return newValue
|
|
78
|
+
})
|
|
79
|
+
}, [onChange])
|
|
80
|
+
|
|
81
|
+
const handleDecrement = useCallback(() => {
|
|
82
|
+
setValue(prev => {
|
|
83
|
+
const num = parseInt(prev)
|
|
84
|
+
if (isNaN(num)) {
|
|
85
|
+
return '0'
|
|
86
|
+
}
|
|
87
|
+
const newValue = Math.max(0, num - 1).toString()
|
|
88
|
+
onChange?.()
|
|
89
|
+
return newValue
|
|
90
|
+
})
|
|
91
|
+
}, [onChange])
|
|
92
|
+
|
|
93
|
+
const handleChange = useCallback(
|
|
94
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
95
|
+
const numValue = event.target.value.replace(/[^0-9]/g, '')
|
|
96
|
+
const newValue = numValue === '' ? '0' : numValue
|
|
97
|
+
setValue(newValue)
|
|
98
|
+
onChange?.()
|
|
99
|
+
},
|
|
100
|
+
[onChange]
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Box display="flex" alignItems="center">
|
|
105
|
+
<StyledButton onClick={handleDecrement}>-</StyledButton>
|
|
106
|
+
<StyledTextField
|
|
107
|
+
value={value}
|
|
108
|
+
onChange={handleChange}
|
|
109
|
+
backgroundcolor={backgroundcolor}
|
|
110
|
+
outlinecolor={outlinecolor}
|
|
111
|
+
fontcolor={fontcolor}
|
|
112
|
+
label={label}
|
|
113
|
+
variant="outlined"
|
|
114
|
+
size="small"
|
|
115
|
+
inputProps={{ style: { width: '40px' } }}
|
|
116
|
+
{...rest}
|
|
117
|
+
/>
|
|
118
|
+
<StyledButton onClick={handleIncrement}>+</StyledButton>
|
|
119
|
+
</Box>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default IncrementNumberField
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React, { useEffect } from 'react'
|
|
2
|
+
import React, { useState, useEffect } from 'react'
|
|
3
3
|
import { Box, Tabs, Tab } from '@mui/material'
|
|
4
|
-
import { session } from 'goobs-cache'
|
|
5
4
|
import { NavProps, SubNav, View } from '../index'
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -42,24 +41,31 @@ function HorizontalVariant({
|
|
|
42
41
|
items,
|
|
43
42
|
height = '80px',
|
|
44
43
|
alignment = 'left',
|
|
45
|
-
navname,
|
|
44
|
+
navname = '',
|
|
46
45
|
}: HorizontalVariantProps) {
|
|
47
46
|
/**
|
|
48
47
|
* State to keep track of active tab values for different navigation components.
|
|
49
48
|
*/
|
|
50
|
-
const
|
|
51
|
-
Record<string, ActiveTabValue
|
|
49
|
+
const [activeTabValues, setActiveTabValues] = useState<
|
|
50
|
+
Record<string, ActiveTabValue>
|
|
52
51
|
>({})
|
|
53
|
-
const [activeTabValues, setActiveTabValues] =
|
|
54
|
-
session.useAtom(activeTabValuesAtom)
|
|
55
52
|
|
|
56
53
|
/**
|
|
57
54
|
* Effect hook to initialize the active tab values when the component mounts.
|
|
58
55
|
*/
|
|
59
56
|
useEffect(() => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
if (!activeTabValues[navname]) {
|
|
58
|
+
const firstTab = items.find(item => 'orientation' in item) as
|
|
59
|
+
| NavProps
|
|
60
|
+
| undefined
|
|
61
|
+
if (firstTab && firstTab.title) {
|
|
62
|
+
setActiveTabValues(prev => ({
|
|
63
|
+
...prev,
|
|
64
|
+
[navname]: { tabId: firstTab.title as string },
|
|
65
|
+
}))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}, [items, navname, activeTabValues])
|
|
63
69
|
|
|
64
70
|
/**
|
|
65
71
|
* Handles tab change events.
|
|
@@ -71,7 +77,7 @@ function HorizontalVariant({
|
|
|
71
77
|
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
|
|
72
78
|
setActiveTabValues(prev => ({
|
|
73
79
|
...prev,
|
|
74
|
-
[navname
|
|
80
|
+
[navname]: { tabId: newValue },
|
|
75
81
|
}))
|
|
76
82
|
}
|
|
77
83
|
|
|
@@ -110,7 +116,7 @@ function HorizontalVariant({
|
|
|
110
116
|
}}
|
|
111
117
|
>
|
|
112
118
|
<Tabs
|
|
113
|
-
value={activeTabValues
|
|
119
|
+
value={activeTabValues[navname]?.tabId || ''}
|
|
114
120
|
onChange={handleTabChange}
|
|
115
121
|
aria-label="nav tabs"
|
|
116
122
|
sx={{
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
AccordionDetails,
|
|
13
13
|
List,
|
|
14
14
|
} from '@mui/material'
|
|
15
|
-
import
|
|
15
|
+
import Dropdown from '../../Dropdown'
|
|
16
|
+
import Searchbar from '../../Searchbar'
|
|
16
17
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
|
17
18
|
import Link from 'next/link'
|
|
18
19
|
import { useRouter } from 'next/navigation'
|
|
@@ -390,26 +391,29 @@ function VerticalVariant({
|
|
|
390
391
|
{(showDropdown || showSearchbar) && (
|
|
391
392
|
<Stack mt={1} spacing={1}>
|
|
392
393
|
{showDropdown && (
|
|
393
|
-
<
|
|
394
|
+
<Dropdown
|
|
394
395
|
label={dropdownLabel}
|
|
395
|
-
componentvariant="dropdown"
|
|
396
|
-
outlinecolor="none"
|
|
397
396
|
options={navOptions}
|
|
398
397
|
backgroundcolor={white.main}
|
|
399
|
-
|
|
398
|
+
outlinecolor="none"
|
|
399
|
+
fontcolor={black.main}
|
|
400
400
|
shrunkfontcolor={white.main}
|
|
401
|
-
|
|
401
|
+
onChange={() => {
|
|
402
|
+
console.log('Dropdown selection changed')
|
|
403
|
+
}}
|
|
402
404
|
/>
|
|
403
405
|
)}
|
|
404
406
|
{showSearchbar && (
|
|
405
|
-
<
|
|
406
|
-
componentvariant="searchbar"
|
|
407
|
+
<Searchbar
|
|
407
408
|
label={searchbarLabel}
|
|
408
409
|
backgroundcolor={semiTransparentWhite.main}
|
|
409
410
|
iconcolor={white.main}
|
|
410
411
|
outlinecolor="none"
|
|
411
|
-
|
|
412
|
-
|
|
412
|
+
fontcolor={white.main}
|
|
413
|
+
placeholder="Search..."
|
|
414
|
+
onChange={() => {
|
|
415
|
+
console.log('Search value changed')
|
|
416
|
+
}}
|
|
413
417
|
/>
|
|
414
418
|
)}
|
|
415
419
|
</Stack>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useState, useCallback } from 'react'
|
|
3
|
+
import { TextField, TextFieldProps } from '@mui/material'
|
|
4
|
+
import { styled } from '@mui/material/styles'
|
|
5
|
+
|
|
6
|
+
export interface NumberFieldProps extends Omit<TextFieldProps, 'onChange'> {
|
|
7
|
+
initialValue?: string
|
|
8
|
+
onChange?: () => void
|
|
9
|
+
backgroundcolor?: string
|
|
10
|
+
outlinecolor?: string
|
|
11
|
+
fontcolor?: string
|
|
12
|
+
label?: string
|
|
13
|
+
min?: number
|
|
14
|
+
max?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const StyledTextField = styled(TextField)<{
|
|
18
|
+
backgroundcolor?: string
|
|
19
|
+
outlinecolor?: string
|
|
20
|
+
fontcolor?: string
|
|
21
|
+
}>(({ theme, backgroundcolor, outlinecolor, fontcolor }) => ({
|
|
22
|
+
'& .MuiOutlinedInput-root': {
|
|
23
|
+
backgroundColor: backgroundcolor || theme.palette.background.paper,
|
|
24
|
+
'& fieldset': {
|
|
25
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
26
|
+
},
|
|
27
|
+
'&:hover fieldset': {
|
|
28
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
29
|
+
},
|
|
30
|
+
'&.Mui-focused fieldset': {
|
|
31
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
'& .MuiInputLabel-root': {
|
|
35
|
+
color: fontcolor || theme.palette.text.primary,
|
|
36
|
+
'&.Mui-focused': {
|
|
37
|
+
color: fontcolor || theme.palette.primary.main,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
'& .MuiInputBase-input': {
|
|
41
|
+
color: fontcolor || theme.palette.text.primary,
|
|
42
|
+
},
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
const NumberField: React.FC<NumberFieldProps> = ({
|
|
46
|
+
initialValue = '',
|
|
47
|
+
onChange,
|
|
48
|
+
backgroundcolor,
|
|
49
|
+
outlinecolor,
|
|
50
|
+
fontcolor,
|
|
51
|
+
label,
|
|
52
|
+
min,
|
|
53
|
+
max,
|
|
54
|
+
...rest
|
|
55
|
+
}) => {
|
|
56
|
+
const [value, setValue] = useState(initialValue)
|
|
57
|
+
|
|
58
|
+
const handleChange = useCallback(
|
|
59
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
60
|
+
const newValue = event.target.value.replace(/[^0-9]/g, '')
|
|
61
|
+
if (newValue === '') {
|
|
62
|
+
setValue('')
|
|
63
|
+
onChange?.()
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
const numValue = parseInt(newValue, 10)
|
|
67
|
+
if (min !== undefined && numValue < min) {
|
|
68
|
+
setValue(min.toString())
|
|
69
|
+
} else if (max !== undefined && numValue > max) {
|
|
70
|
+
setValue(max.toString())
|
|
71
|
+
} else {
|
|
72
|
+
setValue(newValue)
|
|
73
|
+
}
|
|
74
|
+
onChange?.()
|
|
75
|
+
},
|
|
76
|
+
[onChange, min, max]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<StyledTextField
|
|
81
|
+
value={value}
|
|
82
|
+
onChange={handleChange}
|
|
83
|
+
backgroundcolor={backgroundcolor}
|
|
84
|
+
outlinecolor={outlinecolor}
|
|
85
|
+
fontcolor={fontcolor}
|
|
86
|
+
label={label}
|
|
87
|
+
variant="outlined"
|
|
88
|
+
type="text"
|
|
89
|
+
inputMode="numeric"
|
|
90
|
+
{...rest}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default NumberField
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useState, useCallback } from 'react'
|
|
3
|
+
import { TextField, TextFieldProps, InputAdornment } from '@mui/material'
|
|
4
|
+
import { styled } from '@mui/material/styles'
|
|
5
|
+
import ShowHideEyeIcon from '../Icons/ShowHideEye'
|
|
6
|
+
|
|
7
|
+
export interface PasswordFieldProps extends Omit<TextFieldProps, 'type'> {
|
|
8
|
+
backgroundcolor?: string
|
|
9
|
+
outlinecolor?: string
|
|
10
|
+
fontcolor?: string
|
|
11
|
+
label?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const StyledTextField = styled(TextField)<{
|
|
15
|
+
backgroundcolor?: string
|
|
16
|
+
outlinecolor?: string
|
|
17
|
+
fontcolor?: string
|
|
18
|
+
}>(({ theme, backgroundcolor, outlinecolor, fontcolor }) => ({
|
|
19
|
+
'& .MuiOutlinedInput-root': {
|
|
20
|
+
backgroundColor: backgroundcolor || theme.palette.background.paper,
|
|
21
|
+
'& fieldset': {
|
|
22
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
23
|
+
},
|
|
24
|
+
'&:hover fieldset': {
|
|
25
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
26
|
+
},
|
|
27
|
+
'&.Mui-focused fieldset': {
|
|
28
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
'& .MuiInputLabel-root': {
|
|
32
|
+
color: fontcolor || theme.palette.text.primary,
|
|
33
|
+
'&.Mui-focused': {
|
|
34
|
+
color: fontcolor || theme.palette.primary.main,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
'& .MuiInputBase-input': {
|
|
38
|
+
color: fontcolor || theme.palette.text.primary,
|
|
39
|
+
},
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
interface AdornmentProps {
|
|
43
|
+
componentvariant: string
|
|
44
|
+
passwordVisible?: boolean
|
|
45
|
+
togglePasswordVisibility?: () => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const EndAdornment: React.FC<AdornmentProps> = ({
|
|
49
|
+
componentvariant,
|
|
50
|
+
passwordVisible,
|
|
51
|
+
togglePasswordVisibility,
|
|
52
|
+
}) => {
|
|
53
|
+
if (componentvariant === 'password') {
|
|
54
|
+
return (
|
|
55
|
+
<InputAdornment
|
|
56
|
+
position="end"
|
|
57
|
+
onClick={togglePasswordVisibility}
|
|
58
|
+
style={{ cursor: 'pointer' }}
|
|
59
|
+
>
|
|
60
|
+
<ShowHideEyeIcon visible={passwordVisible} />
|
|
61
|
+
</InputAdornment>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const PasswordField: React.FC<PasswordFieldProps> = ({
|
|
68
|
+
backgroundcolor,
|
|
69
|
+
outlinecolor,
|
|
70
|
+
fontcolor,
|
|
71
|
+
label = 'Password',
|
|
72
|
+
...rest
|
|
73
|
+
}) => {
|
|
74
|
+
const [passwordVisible, setPasswordVisible] = useState(false)
|
|
75
|
+
|
|
76
|
+
const togglePasswordVisibility = useCallback(() => {
|
|
77
|
+
setPasswordVisible(prev => {
|
|
78
|
+
console.log('togglePasswordVisibility', { passwordVisible: !prev })
|
|
79
|
+
return !prev
|
|
80
|
+
})
|
|
81
|
+
}, [])
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<StyledTextField
|
|
85
|
+
type={passwordVisible ? 'text' : 'password'}
|
|
86
|
+
label={label}
|
|
87
|
+
backgroundcolor={backgroundcolor}
|
|
88
|
+
outlinecolor={outlinecolor}
|
|
89
|
+
fontcolor={fontcolor}
|
|
90
|
+
fullWidth
|
|
91
|
+
InputProps={{
|
|
92
|
+
endAdornment: (
|
|
93
|
+
<EndAdornment
|
|
94
|
+
componentvariant="password"
|
|
95
|
+
passwordVisible={passwordVisible}
|
|
96
|
+
togglePasswordVisibility={togglePasswordVisibility}
|
|
97
|
+
/>
|
|
98
|
+
),
|
|
99
|
+
}}
|
|
100
|
+
{...rest}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export default PasswordField
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useState, useCallback } from 'react'
|
|
3
|
+
import { TextField, TextFieldProps } from '@mui/material'
|
|
4
|
+
import { styled } from '@mui/material/styles'
|
|
5
|
+
|
|
6
|
+
interface PhoneNumberFieldProps extends Omit<TextFieldProps, 'onChange'> {
|
|
7
|
+
initialValue?: string
|
|
8
|
+
onChange?: () => void
|
|
9
|
+
backgroundcolor?: string
|
|
10
|
+
outlinecolor?: string
|
|
11
|
+
fontcolor?: string
|
|
12
|
+
label?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const StyledTextField = styled(TextField)<{
|
|
16
|
+
backgroundcolor?: string
|
|
17
|
+
outlinecolor?: string
|
|
18
|
+
fontcolor?: string
|
|
19
|
+
}>(({ theme, backgroundcolor, outlinecolor, fontcolor }) => ({
|
|
20
|
+
'& .MuiOutlinedInput-root': {
|
|
21
|
+
backgroundColor: backgroundcolor || theme.palette.background.paper,
|
|
22
|
+
'& fieldset': {
|
|
23
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
24
|
+
},
|
|
25
|
+
'&:hover fieldset': {
|
|
26
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
27
|
+
},
|
|
28
|
+
'&.Mui-focused fieldset': {
|
|
29
|
+
borderColor: outlinecolor || theme.palette.primary.main,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
'& .MuiInputLabel-root': {
|
|
33
|
+
color: fontcolor || theme.palette.text.primary,
|
|
34
|
+
'&.Mui-focused': {
|
|
35
|
+
color: fontcolor || theme.palette.primary.main,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
'& .MuiInputBase-input': {
|
|
39
|
+
color: fontcolor || theme.palette.text.primary,
|
|
40
|
+
},
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
const formatPhoneNumber = (value: string): string => {
|
|
44
|
+
const digits = value.replace(/\D/g, '')
|
|
45
|
+
const limitedDigits = digits.slice(0, 10)
|
|
46
|
+
let formattedNumber = '+1 '
|
|
47
|
+
if (limitedDigits.length > 0) {
|
|
48
|
+
formattedNumber += limitedDigits.slice(0, 3)
|
|
49
|
+
if (limitedDigits.length > 3) {
|
|
50
|
+
formattedNumber += '-' + limitedDigits.slice(3, 6)
|
|
51
|
+
if (limitedDigits.length > 6) {
|
|
52
|
+
formattedNumber += '-' + limitedDigits.slice(6)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return formattedNumber.trim()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const PhoneNumberField: React.FC<PhoneNumberFieldProps> = ({
|
|
60
|
+
initialValue = '',
|
|
61
|
+
onChange,
|
|
62
|
+
backgroundcolor,
|
|
63
|
+
outlinecolor,
|
|
64
|
+
fontcolor,
|
|
65
|
+
label = 'Phone Number',
|
|
66
|
+
...rest
|
|
67
|
+
}) => {
|
|
68
|
+
const [phoneNumber, setPhoneNumber] = useState(
|
|
69
|
+
formatPhoneNumber(initialValue)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const handlePhoneNumberChange = useCallback(
|
|
73
|
+
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
74
|
+
const input = e.target.value
|
|
75
|
+
let strippedInput = input.replace(/^\+1\s?/, '').replace(/\D/g, '')
|
|
76
|
+
strippedInput = strippedInput.slice(0, 10)
|
|
77
|
+
const formattedValue =
|
|
78
|
+
strippedInput.length > 0 ? formatPhoneNumber(strippedInput) : '+1 '
|
|
79
|
+
setPhoneNumber(formattedValue)
|
|
80
|
+
onChange?.()
|
|
81
|
+
},
|
|
82
|
+
[onChange]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<StyledTextField
|
|
87
|
+
label={label}
|
|
88
|
+
value={phoneNumber}
|
|
89
|
+
onChange={handlePhoneNumberChange}
|
|
90
|
+
backgroundcolor={backgroundcolor}
|
|
91
|
+
outlinecolor={outlinecolor}
|
|
92
|
+
fontcolor={fontcolor}
|
|
93
|
+
fullWidth
|
|
94
|
+
inputProps={{
|
|
95
|
+
maxLength: 16,
|
|
96
|
+
}}
|
|
97
|
+
{...rest}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default PhoneNumberField
|
|
@@ -6,7 +6,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle'
|
|
|
6
6
|
import { Typography } from '../Typography'
|
|
7
7
|
import StyledTooltip from '../Tooltip'
|
|
8
8
|
import CustomButton from '../Button'
|
|
9
|
-
import
|
|
9
|
+
import Dropdown from '../Dropdown'
|
|
10
10
|
import CustomGrid from './../../components/Grid'
|
|
11
11
|
import { columnconfig, gridconfig } from './../../components/Grid/'
|
|
12
12
|
import defaultConfig from './defaultconfig'
|
|
@@ -117,15 +117,17 @@ const PricingTable: React.FC<PricingProps> = props => {
|
|
|
117
117
|
headerColumnConfigs.push({
|
|
118
118
|
...config.packagecolumns.columnconfig,
|
|
119
119
|
component: (
|
|
120
|
-
<
|
|
120
|
+
<Dropdown
|
|
121
121
|
label="Packages"
|
|
122
|
-
shrunklabellocation="above"
|
|
123
|
-
componentvariant="dropdown"
|
|
124
|
-
shrunkfontcolor={black.main}
|
|
125
|
-
outlinecolor={black.main}
|
|
126
|
-
backgroundcolor={semiTransparentBlack.main}
|
|
127
|
-
defaultOption="ThothOS"
|
|
128
122
|
options={['ThothOS', 'ThothOS Pro', 'ThothOS Enterprise']}
|
|
123
|
+
defaultOption="ThothOS"
|
|
124
|
+
backgroundcolor={semiTransparentBlack.main}
|
|
125
|
+
outlinecolor={black.main}
|
|
126
|
+
fontcolor={black.main}
|
|
127
|
+
shrunkfontcolor={black.main}
|
|
128
|
+
onChange={() => {
|
|
129
|
+
console.log('Package selection changed')
|
|
130
|
+
}}
|
|
129
131
|
/>
|
|
130
132
|
),
|
|
131
133
|
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import QRCode from 'react-qr-code'
|
|
3
|
+
import { Box, Typography, Paper, Theme, CircularProgress } from '@mui/material'
|
|
4
|
+
import { SxProps } from '@mui/system'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props for the QRCodeComponent
|
|
8
|
+
* @typedef {Object} QRCodeProps
|
|
9
|
+
* @property {string} value - The value to be encoded in the QR code
|
|
10
|
+
* @property {number} [size] - The size of the QR code in pixels
|
|
11
|
+
* @property {string} [title] - An optional title to display above the QR code
|
|
12
|
+
* @property {SxProps<Theme>} [sx] - Custom styles to apply to the component
|
|
13
|
+
*/
|
|
14
|
+
export interface QRCodeProps {
|
|
15
|
+
value: string
|
|
16
|
+
size?: number
|
|
17
|
+
title?: string
|
|
18
|
+
sx?: SxProps<Theme>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A component that displays a QR code with Material-UI styling
|
|
23
|
+
* @param {QRCodeProps} props - The props for the component
|
|
24
|
+
* @returns {React.ReactElement} The rendered QR code component
|
|
25
|
+
*/
|
|
26
|
+
const QRCodeComponent: React.FC<QRCodeProps> = React.memo(
|
|
27
|
+
({ value, size = 256, title, sx }) => {
|
|
28
|
+
// Validate the QR code value
|
|
29
|
+
const isValidValue = useMemo(() => {
|
|
30
|
+
if (!value) return false
|
|
31
|
+
try {
|
|
32
|
+
// Check if the value is a valid URL
|
|
33
|
+
new URL(value)
|
|
34
|
+
return true
|
|
35
|
+
} catch {
|
|
36
|
+
// If not a URL, check if it's a non-empty string
|
|
37
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
38
|
+
}
|
|
39
|
+
}, [value])
|
|
40
|
+
|
|
41
|
+
// Calculate responsive size
|
|
42
|
+
const responsiveSize = useMemo(() => {
|
|
43
|
+
return Math.min(size, window.innerWidth - 32) // 32px for padding
|
|
44
|
+
}, [size])
|
|
45
|
+
|
|
46
|
+
if (!isValidValue) {
|
|
47
|
+
return (
|
|
48
|
+
<Box sx={{ ...sx, p: 2 }} role="alert">
|
|
49
|
+
<Typography color="error">
|
|
50
|
+
Error: Invalid or empty QR code value
|
|
51
|
+
</Typography>
|
|
52
|
+
</Box>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Paper
|
|
58
|
+
elevation={3}
|
|
59
|
+
sx={{
|
|
60
|
+
...sx,
|
|
61
|
+
p: 3,
|
|
62
|
+
display: 'inline-block',
|
|
63
|
+
maxWidth: '100%',
|
|
64
|
+
boxSizing: 'border-box',
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{title && (
|
|
68
|
+
<Typography variant="h6" gutterBottom align="center">
|
|
69
|
+
{title}
|
|
70
|
+
</Typography>
|
|
71
|
+
)}
|
|
72
|
+
<Box
|
|
73
|
+
sx={{
|
|
74
|
+
display: 'flex',
|
|
75
|
+
justifyContent: 'center',
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
width: responsiveSize,
|
|
78
|
+
height: responsiveSize,
|
|
79
|
+
margin: 'auto',
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<React.Suspense
|
|
83
|
+
fallback={
|
|
84
|
+
<CircularProgress
|
|
85
|
+
size={responsiveSize / 4}
|
|
86
|
+
aria-label="Loading QR Code"
|
|
87
|
+
/>
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
<QRCode
|
|
91
|
+
value={value}
|
|
92
|
+
size={responsiveSize}
|
|
93
|
+
style={{ height: 'auto', maxWidth: '100%', width: '100%' }}
|
|
94
|
+
aria-label={`QR Code for ${title || value}`}
|
|
95
|
+
/>
|
|
96
|
+
</React.Suspense>
|
|
97
|
+
</Box>
|
|
98
|
+
</Paper>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
QRCodeComponent.displayName = 'QRCodeComponent'
|
|
104
|
+
|
|
105
|
+
export default QRCodeComponent
|