goobs-frontend 0.7.60 → 0.7.63
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 +2 -2
- package/src/components/Button/index.tsx +103 -65
- package/src/components/ConfirmationCodeInput/index.tsx +2 -2
- package/src/components/Content/index.tsx +13 -7
- package/src/components/Form/Popup/index.tsx +29 -10
- package/src/components/Icons/ShowHideEye.tsx +8 -2
- package/src/components/Nav/HorizontalVariant/index.tsx +92 -91
- package/src/components/Nav/VerticalVariant/index.tsx +41 -0
- package/src/components/Nav/index.tsx +2 -14
- package/src/components/PricingTable/defaultconfig.tsx +35 -115
- package/src/components/PricingTable/index.tsx +7 -11
- package/src/components/StyledComponent/adornment/index.tsx +6 -8
- package/src/components/StyledComponent/helperfooter/useHelperFooter.tsx +383 -340
- package/src/components/StyledComponent/hooks/useDropdown.tsx +60 -15
- package/src/components/StyledComponent/hooks/usePhoneNumber.tsx +53 -27
- package/src/components/StyledComponent/index.tsx +107 -66
- package/src/components/StyledComponent/useEffects/index.tsx +0 -5
- package/src/components/StyledComponent/hooks/usePassword.tsx +0 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goobs-frontend",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.63",
|
|
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",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@mui/icons-material": "^5.16.0",
|
|
29
29
|
"@mui/material": "^5.16.0",
|
|
30
30
|
"@types/lodash": "^4.17.6",
|
|
31
|
-
"goobs-cache": "^1.
|
|
31
|
+
"goobs-cache": "^1.4.0",
|
|
32
32
|
"highlight.js": "^11.10.0",
|
|
33
33
|
"lodash": "^4.17.21",
|
|
34
34
|
"next": "14.2.5"
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import React, { useEffect, useState, useCallback } from 'react'
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react'
|
|
4
2
|
import { Button, Box, ButtonProps } from '@mui/material'
|
|
5
3
|
import StarIcon from '@mui/icons-material/Star'
|
|
6
4
|
import Typography from '../Typography'
|
|
7
|
-
import { get, JSONValue } from 'goobs-cache'
|
|
8
5
|
import { red } from '../../styles/palette'
|
|
6
|
+
import { get } from 'goobs-cache'
|
|
9
7
|
|
|
10
8
|
export type ButtonAlignment = 'left' | 'center' | 'right'
|
|
11
9
|
|
|
@@ -88,75 +86,108 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
|
|
|
88
86
|
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
|
89
87
|
undefined
|
|
90
88
|
)
|
|
91
|
-
const [
|
|
92
|
-
const [
|
|
93
|
-
Record<string, HelperFooterMessage>
|
|
94
|
-
>({})
|
|
95
|
-
|
|
96
|
-
const updateFormValidation = useCallback(() => {
|
|
97
|
-
if (formname && helperFooterValue) {
|
|
98
|
-
const relevantFooters = Object.values(helperFooterValue).filter(
|
|
99
|
-
footer => footer?.formname === formname
|
|
100
|
-
)
|
|
89
|
+
const [, setIsFormValid] = useState<boolean>(true)
|
|
90
|
+
const [hasBeenClicked, setHasBeenClicked] = useState<boolean>(false)
|
|
101
91
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
const fetchHelperFooters = useCallback(async (): Promise<
|
|
93
|
+
HelperFooterMessage[]
|
|
94
|
+
> => {
|
|
95
|
+
if (!formname) {
|
|
96
|
+
console.log('CustomButton: No formname provided, returning empty array')
|
|
97
|
+
return []
|
|
98
|
+
}
|
|
99
|
+
console.log('CustomButton: Fetching helper footers for formname:', formname)
|
|
100
|
+
|
|
101
|
+
// Wait for 2 seconds before fetching to allow time for cache update
|
|
102
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
const helperFooterResult = await get('helperfooter', formname, 'client')
|
|
105
|
+
console.log('CustomButton: Helper footer result:', helperFooterResult)
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
helperFooterResult &&
|
|
109
|
+
typeof helperFooterResult === 'object' &&
|
|
110
|
+
'type' in helperFooterResult &&
|
|
111
|
+
helperFooterResult.type === 'json' &&
|
|
112
|
+
'value' in helperFooterResult &&
|
|
113
|
+
typeof helperFooterResult.value === 'object' &&
|
|
114
|
+
helperFooterResult.value !== null
|
|
115
|
+
) {
|
|
116
|
+
const helperFooters = Object.entries(
|
|
117
|
+
helperFooterResult.value as Record<string, unknown>
|
|
109
118
|
)
|
|
119
|
+
.map(([key, value]): HelperFooterMessage | null => {
|
|
120
|
+
if (
|
|
121
|
+
typeof value === 'object' &&
|
|
122
|
+
value !== null &&
|
|
123
|
+
'status' in value &&
|
|
124
|
+
'statusMessage' in value &&
|
|
125
|
+
'spreadMessage' in value &&
|
|
126
|
+
'spreadMessagePriority' in value &&
|
|
127
|
+
'required' in value
|
|
128
|
+
) {
|
|
129
|
+
return {
|
|
130
|
+
status: value.status as 'error' | 'success',
|
|
131
|
+
statusMessage: String(value.statusMessage),
|
|
132
|
+
spreadMessage: String(value.spreadMessage),
|
|
133
|
+
spreadMessagePriority: Number(value.spreadMessagePriority),
|
|
134
|
+
required: Boolean(value.required),
|
|
135
|
+
formname: key,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null
|
|
139
|
+
})
|
|
140
|
+
.filter((value): value is HelperFooterMessage => value !== null)
|
|
110
141
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
142
|
+
console.log('CustomButton: Valid helper footers:', helperFooters)
|
|
143
|
+
return helperFooters
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log('CustomButton: No valid helper footers found in cache')
|
|
147
|
+
return []
|
|
148
|
+
}, [formname])
|
|
149
|
+
|
|
150
|
+
const updateFormValidation = useCallback(async (): Promise<boolean> => {
|
|
151
|
+
console.log('CustomButton: Starting form validation')
|
|
152
|
+
const helperFooters = await fetchHelperFooters()
|
|
153
|
+
console.log('CustomButton: Fetched helper footers:', helperFooters)
|
|
154
|
+
|
|
155
|
+
if (helperFooters.length === 0) {
|
|
156
|
+
console.log('CustomButton: No helper footers found, form is valid')
|
|
128
157
|
setErrorMessage(undefined)
|
|
129
158
|
setIsFormValid(true)
|
|
159
|
+
return true
|
|
130
160
|
}
|
|
131
|
-
}, [formname, helperFooterValue])
|
|
132
161
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
setHelperFooterValue({})
|
|
149
|
-
}
|
|
162
|
+
const errorFooters = helperFooters.filter(
|
|
163
|
+
footer => footer.status === 'error'
|
|
164
|
+
)
|
|
165
|
+
console.log('CustomButton: Error footers:', errorFooters)
|
|
166
|
+
|
|
167
|
+
if (errorFooters.length > 0) {
|
|
168
|
+
console.log('CustomButton: Found error footers, form is invalid')
|
|
169
|
+
const highestPriorityError = errorFooters.reduce((prev, current) =>
|
|
170
|
+
prev.spreadMessagePriority < current.spreadMessagePriority
|
|
171
|
+
? prev
|
|
172
|
+
: current
|
|
173
|
+
)
|
|
174
|
+
setErrorMessage(highestPriorityError.spreadMessage)
|
|
175
|
+
setIsFormValid(false)
|
|
176
|
+
return false
|
|
150
177
|
}
|
|
151
178
|
|
|
152
|
-
|
|
153
|
-
|
|
179
|
+
console.log('CustomButton: No error footers found, form is valid')
|
|
180
|
+
setErrorMessage(undefined)
|
|
181
|
+
setIsFormValid(true)
|
|
182
|
+
return true
|
|
183
|
+
}, [fetchHelperFooters])
|
|
154
184
|
|
|
155
185
|
useEffect(() => {
|
|
156
|
-
|
|
186
|
+
console.log('CustomButton: Running effect to update form validation')
|
|
187
|
+
void updateFormValidation()
|
|
157
188
|
}, [updateFormValidation])
|
|
158
189
|
|
|
159
|
-
const renderIcon = () => {
|
|
190
|
+
const renderIcon = (): React.ReactNode => {
|
|
160
191
|
if (icon === false) {
|
|
161
192
|
return null
|
|
162
193
|
}
|
|
@@ -168,13 +199,20 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
|
|
|
168
199
|
return <StarIcon style={{ fontSize: iconsize }} />
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
const handleButtonClick = async (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
202
|
+
const handleButtonClick = async (
|
|
203
|
+
event: React.MouseEvent<HTMLButtonElement>
|
|
204
|
+
): Promise<void> => {
|
|
205
|
+
console.log('CustomButton: Button clicked')
|
|
206
|
+
event.preventDefault()
|
|
207
|
+
setHasBeenClicked(true)
|
|
175
208
|
|
|
176
|
-
|
|
209
|
+
const isValid = await updateFormValidation()
|
|
210
|
+
console.log('CustomButton: Form validation result:', isValid)
|
|
211
|
+
if (isValid && onClick) {
|
|
212
|
+
console.log('CustomButton: Form is valid, calling onClick')
|
|
177
213
|
onClick()
|
|
214
|
+
} else {
|
|
215
|
+
console.log('CustomButton: Form is invalid or no onClick provided')
|
|
178
216
|
}
|
|
179
217
|
}
|
|
180
218
|
|
|
@@ -216,7 +254,7 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
|
|
|
216
254
|
{iconlocation === 'right' && renderIcon()}
|
|
217
255
|
</Box>
|
|
218
256
|
</Button>
|
|
219
|
-
{errorMessage && (
|
|
257
|
+
{hasBeenClicked && errorMessage && (
|
|
220
258
|
<Typography
|
|
221
259
|
fontvariant="merrihelperfooter"
|
|
222
260
|
fontcolor={red.main}
|
|
@@ -65,16 +65,16 @@ export interface ContentSectionProps {
|
|
|
65
65
|
card?: ExtendedCardProps | ExtendedCardProps[]
|
|
66
66
|
codecopy?: ExtendedCodeCopyProps | ExtendedCodeCopyProps[]
|
|
67
67
|
}>
|
|
68
|
+
width?: number
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
72
|
* RenderContent component handles the rendering of various content elements
|
|
72
73
|
* based on the provided configuration.
|
|
73
74
|
*/
|
|
74
|
-
const RenderContent: React.FC<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}) => {
|
|
75
|
+
const RenderContent: React.FC<
|
|
76
|
+
ContentSectionProps['grids'][0] & { width?: number }
|
|
77
|
+
> = ({ grid, width, ...props }) => {
|
|
78
78
|
let columnConfigs: columnconfig[] = []
|
|
79
79
|
|
|
80
80
|
// Helper function to add configurations to columnConfigs
|
|
@@ -104,20 +104,26 @@ const RenderContent: React.FC<ContentSectionProps['grids'][0]> = ({
|
|
|
104
104
|
addToColumnConfigs(useCard(props))
|
|
105
105
|
addToColumnConfigs(useCodeCopy(props))
|
|
106
106
|
|
|
107
|
+
const updatedGridConfig: gridconfig = {
|
|
108
|
+
...grid.gridconfig,
|
|
109
|
+
gridwidth: width ? `${width}px` : grid.gridconfig?.gridwidth,
|
|
110
|
+
}
|
|
111
|
+
|
|
107
112
|
return (
|
|
108
|
-
<CustomGrid gridconfig={
|
|
113
|
+
<CustomGrid gridconfig={updatedGridConfig} columnconfig={columnConfigs} />
|
|
109
114
|
)
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
/**
|
|
113
118
|
* ContentSection component renders multiple grids based on the provided configuration.
|
|
114
119
|
* @param grids An array of ContentSectionProps, each representing a grid to be rendered.
|
|
120
|
+
* @param width Optional width for the content section, defaults to 450px if not provided.
|
|
115
121
|
*/
|
|
116
|
-
export default function ContentSection({ grids }: ContentSectionProps) {
|
|
122
|
+
export default function ContentSection({ grids, width }: ContentSectionProps) {
|
|
117
123
|
return (
|
|
118
124
|
<>
|
|
119
125
|
{grids.map((gridProps, index) => (
|
|
120
|
-
<RenderContent key={index} {...gridProps} />
|
|
126
|
+
<RenderContent key={index} {...gridProps} width={width} />
|
|
121
127
|
))}
|
|
122
128
|
</>
|
|
123
129
|
)
|
|
@@ -8,7 +8,7 @@ import React, {
|
|
|
8
8
|
useCallback,
|
|
9
9
|
} from 'react'
|
|
10
10
|
import { Close } from '@mui/icons-material'
|
|
11
|
-
import { Dialog, IconButton, Box } from '@mui/material'
|
|
11
|
+
import { Dialog, IconButton, Box, DialogProps } from '@mui/material'
|
|
12
12
|
import ContentSection, { ContentSectionProps } from '../../Content'
|
|
13
13
|
import { formContainerStyle } from './../../../styles/Form'
|
|
14
14
|
import { ExtendedTypographyProps } from '../../Content/Structure/typography/useGridTypography'
|
|
@@ -34,6 +34,8 @@ export interface PopupFormProps {
|
|
|
34
34
|
open?: boolean
|
|
35
35
|
/** Callback function to handle closing the popup (only applicable for 'dialog' type) */
|
|
36
36
|
onClose?: () => void
|
|
37
|
+
/** The width of the popup form in pixels */
|
|
38
|
+
width?: number
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/**
|
|
@@ -52,11 +54,22 @@ export interface PopupFormProps {
|
|
|
52
54
|
* onClose={handleClose}
|
|
53
55
|
* onSubmit={handleSubmit}
|
|
54
56
|
* content={<LoginForm />}
|
|
57
|
+
* width={400}
|
|
55
58
|
* />
|
|
56
59
|
*/
|
|
57
60
|
const PopupForm = forwardRef<HTMLFormElement, PopupFormProps>(
|
|
58
61
|
(
|
|
59
|
-
{
|
|
62
|
+
{
|
|
63
|
+
title,
|
|
64
|
+
description,
|
|
65
|
+
grids,
|
|
66
|
+
onSubmit,
|
|
67
|
+
content,
|
|
68
|
+
popupType,
|
|
69
|
+
open,
|
|
70
|
+
onClose,
|
|
71
|
+
width = 450,
|
|
72
|
+
},
|
|
60
73
|
ref
|
|
61
74
|
) => {
|
|
62
75
|
const internalFormRef = useRef<HTMLFormElement>(null)
|
|
@@ -151,22 +164,28 @@ const PopupForm = forwardRef<HTMLFormElement, PopupFormProps>(
|
|
|
151
164
|
[renderHeader, handleSubmit, content, grids]
|
|
152
165
|
)
|
|
153
166
|
|
|
167
|
+
const dialogProps: DialogProps = {
|
|
168
|
+
open: popupType === 'modal' ? true : open || false,
|
|
169
|
+
onClose: popupType === 'modal' ? undefined : onClose,
|
|
170
|
+
fullWidth: true,
|
|
171
|
+
maxWidth: false,
|
|
172
|
+
PaperProps: {
|
|
173
|
+
style: {
|
|
174
|
+
width: `${width}px`,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
154
179
|
if (popupType === 'modal') {
|
|
155
180
|
return (
|
|
156
|
-
<Dialog
|
|
157
|
-
open={true}
|
|
158
|
-
fullWidth
|
|
159
|
-
maxWidth="sm"
|
|
160
|
-
disableEscapeKeyDown
|
|
161
|
-
hideBackdrop
|
|
162
|
-
>
|
|
181
|
+
<Dialog {...dialogProps} disableEscapeKeyDown hideBackdrop>
|
|
163
182
|
{renderContent}
|
|
164
183
|
</Dialog>
|
|
165
184
|
)
|
|
166
185
|
}
|
|
167
186
|
|
|
168
187
|
return (
|
|
169
|
-
<Dialog
|
|
188
|
+
<Dialog {...dialogProps}>
|
|
170
189
|
{onClose && (
|
|
171
190
|
<IconButton
|
|
172
191
|
size="small"
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
'use client'
|
|
2
2
|
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import VisibilityIcon from '@mui/icons-material/Visibility'
|
|
5
5
|
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
interface ShowHideEyeIconProps {
|
|
8
|
+
visible?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ShowHideEyeIcon: React.FC<ShowHideEyeIconProps> = ({
|
|
12
|
+
visible = false,
|
|
13
|
+
}) => {
|
|
8
14
|
const iconStyle = { color: 'black' }
|
|
9
15
|
return visible ? (
|
|
10
16
|
<VisibilityIcon style={iconStyle} />
|
|
@@ -1,89 +1,42 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import React, { useState, useEffect } from 'react'
|
|
3
|
-
import { Box, Tabs,
|
|
3
|
+
import { Box, Tabs, Tab } from '@mui/material'
|
|
4
4
|
import { get, set, JSONValue } from 'goobs-cache'
|
|
5
5
|
import { NavProps, SubNav, View } from '../index'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Represents the possible alignment options for the horizontal navigation.
|
|
9
|
+
*/
|
|
8
10
|
type Alignment = 'left' | 'center' | 'right' | 'inherit' | 'justify'
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
13
|
+
* Represents the structure of an active tab value.
|
|
12
14
|
*/
|
|
13
15
|
export interface ActiveTabValue {
|
|
16
|
+
/** The unique identifier of the active tab. */
|
|
14
17
|
tabId: string
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
21
|
+
* Props for the HorizontalVariant component.
|
|
19
22
|
*/
|
|
20
23
|
export interface HorizontalVariantProps {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
navname?: string // Optional name for the navigation
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Styled component for the Tab, extending MUI's Tab with custom props
|
|
29
|
-
*/
|
|
30
|
-
const StyledTab = styled(Tab, {
|
|
31
|
-
shouldForwardProp: prop =>
|
|
32
|
-
prop !== 'height' && prop !== 'hasleftborder' && prop !== 'hasrightborder',
|
|
33
|
-
})<NavProps & { height?: string }>(
|
|
34
|
-
({ height = '80px', hasleftborder = 'false', hasrightborder = 'false' }) => ({
|
|
35
|
-
minHeight: 0,
|
|
36
|
-
textTransform: 'none',
|
|
37
|
-
border: 'none',
|
|
38
|
-
boxSizing: 'border-box',
|
|
39
|
-
backgroundColor: 'black',
|
|
40
|
-
color: '#fff',
|
|
41
|
-
fontWeight: 500,
|
|
42
|
-
fontFamily: 'Merriweather',
|
|
43
|
-
fontSize: 16,
|
|
44
|
-
height: height,
|
|
45
|
-
'&:hover': {
|
|
46
|
-
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
47
|
-
},
|
|
48
|
-
'& .MuiTouchRipple-root': {
|
|
49
|
-
color: '#fff',
|
|
50
|
-
},
|
|
51
|
-
'&.Mui-selected': {
|
|
52
|
-
color: '#fff',
|
|
53
|
-
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
|
54
|
-
},
|
|
55
|
-
'& .MuiSvgIcon-root': {
|
|
56
|
-
color: '#fff',
|
|
57
|
-
},
|
|
58
|
-
...(hasleftborder === 'true' && {
|
|
59
|
-
borderLeft: '1px solid white',
|
|
60
|
-
}),
|
|
61
|
-
...(hasrightborder === 'true' && {
|
|
62
|
-
borderRight: '1px solid white',
|
|
63
|
-
}),
|
|
64
|
-
})
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Styled component for the horizontal navigation container
|
|
69
|
-
*/
|
|
70
|
-
const HorizontalNavContainer = styled(Box)<{
|
|
24
|
+
/** An array of navigation items, sub-navigation items, or views. */
|
|
25
|
+
items: (NavProps | SubNav | View)[]
|
|
26
|
+
/** The height of the navigation bar. Defaults to '80px'. */
|
|
71
27
|
height?: string
|
|
28
|
+
/** The alignment of the navigation items. Defaults to 'left'. */
|
|
72
29
|
alignment?: Alignment
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
display: 'flex',
|
|
77
|
-
height: height,
|
|
78
|
-
justifyContent: alignment,
|
|
79
|
-
paddingLeft: '5px',
|
|
80
|
-
paddingRight: '5px',
|
|
81
|
-
}))
|
|
30
|
+
/** A unique name for this navigation component. Used for state management. */
|
|
31
|
+
navname?: string
|
|
32
|
+
}
|
|
82
33
|
|
|
83
34
|
/**
|
|
84
|
-
* HorizontalVariant component
|
|
85
|
-
*
|
|
86
|
-
*
|
|
35
|
+
* HorizontalVariant component that renders a horizontal navigation bar.
|
|
36
|
+
* It supports dynamic tab management, routing, and custom click handlers.
|
|
37
|
+
*
|
|
38
|
+
* @param {HorizontalVariantProps} props - The props for the HorizontalVariant component.
|
|
39
|
+
* @returns {JSX.Element} The rendered HorizontalVariant component.
|
|
87
40
|
*/
|
|
88
41
|
function HorizontalVariant({
|
|
89
42
|
items,
|
|
@@ -91,18 +44,32 @@ function HorizontalVariant({
|
|
|
91
44
|
alignment = 'left',
|
|
92
45
|
navname,
|
|
93
46
|
}: HorizontalVariantProps) {
|
|
94
|
-
|
|
47
|
+
/**
|
|
48
|
+
* State to keep track of active tab values for different navigation components.
|
|
49
|
+
*/
|
|
95
50
|
const [activeTabValues, setActiveTabValues] = useState<
|
|
96
51
|
Record<string, ActiveTabValue | null>
|
|
97
52
|
>({})
|
|
98
53
|
|
|
99
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Effect hook to fetch and set the active tab values when the component mounts.
|
|
56
|
+
*/
|
|
100
57
|
useEffect(() => {
|
|
58
|
+
/**
|
|
59
|
+
* Asynchronously fetches the active tab values from the cache.
|
|
60
|
+
*/
|
|
101
61
|
const fetchActiveTabValues = async () => {
|
|
102
|
-
const result = await get('activeTabValues', 'client')
|
|
103
|
-
if (
|
|
62
|
+
const result = await get('activeTabValues', 'tabStore', 'client')
|
|
63
|
+
if (
|
|
64
|
+
result &&
|
|
65
|
+
typeof result === 'object' &&
|
|
66
|
+
'type' in result &&
|
|
67
|
+
result.type === 'json' &&
|
|
68
|
+
'value' in result &&
|
|
69
|
+
typeof result.value === 'object'
|
|
70
|
+
) {
|
|
104
71
|
setActiveTabValues(
|
|
105
|
-
|
|
72
|
+
result.value as Record<string, ActiveTabValue | null>
|
|
106
73
|
)
|
|
107
74
|
}
|
|
108
75
|
}
|
|
@@ -111,9 +78,11 @@ function HorizontalVariant({
|
|
|
111
78
|
}, [])
|
|
112
79
|
|
|
113
80
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
81
|
+
* Handles tab change events.
|
|
82
|
+
* Updates the active tab values in the state and cache.
|
|
83
|
+
*
|
|
84
|
+
* @param {React.SyntheticEvent} event - The event object.
|
|
85
|
+
* @param {string} newValue - The new value of the selected tab.
|
|
117
86
|
*/
|
|
118
87
|
const handleTabChange = async (
|
|
119
88
|
event: React.SyntheticEvent,
|
|
@@ -124,18 +93,19 @@ function HorizontalVariant({
|
|
|
124
93
|
[navname ?? '']: { tabId: newValue },
|
|
125
94
|
}
|
|
126
95
|
setActiveTabValues(updatedActiveTabValues)
|
|
127
|
-
// Store updated values in cache with 30 minutes expiration
|
|
128
96
|
await set(
|
|
129
97
|
'activeTabValues',
|
|
98
|
+
'tabStore',
|
|
130
99
|
{ type: 'json', value: updatedActiveTabValues } as JSONValue,
|
|
131
|
-
new Date(Date.now() + 30 * 60 * 1000),
|
|
132
100
|
'client'
|
|
133
101
|
)
|
|
134
102
|
}
|
|
135
103
|
|
|
136
104
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
105
|
+
* Handles click events on individual tabs.
|
|
106
|
+
* Supports different trigger types: route, onClick, and routeonhorizontal.
|
|
107
|
+
*
|
|
108
|
+
* @param {NavProps} tab - The tab object that was clicked.
|
|
139
109
|
*/
|
|
140
110
|
const handleTabClick = (tab: NavProps) => {
|
|
141
111
|
if (tab.trigger === 'route') {
|
|
@@ -154,9 +124,19 @@ function HorizontalVariant({
|
|
|
154
124
|
}
|
|
155
125
|
|
|
156
126
|
return (
|
|
157
|
-
<
|
|
127
|
+
<Box
|
|
128
|
+
sx={{
|
|
129
|
+
flexGrow: 1,
|
|
130
|
+
bgcolor: 'black',
|
|
131
|
+
display: 'flex',
|
|
132
|
+
height: height,
|
|
133
|
+
justifyContent: alignment,
|
|
134
|
+
paddingLeft: '5px',
|
|
135
|
+
paddingRight: '5px',
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
158
138
|
<Tabs
|
|
159
|
-
value={activeTabValues[navname ?? '']?.tabId || false}
|
|
139
|
+
value={activeTabValues?.[navname ?? '']?.tabId || false}
|
|
160
140
|
onChange={handleTabChange}
|
|
161
141
|
aria-label="nav tabs"
|
|
162
142
|
sx={{
|
|
@@ -171,35 +151,56 @@ function HorizontalVariant({
|
|
|
171
151
|
}}
|
|
172
152
|
>
|
|
173
153
|
{items.map((item: NavProps | SubNav | View) => {
|
|
174
|
-
// Only render NavProps items as tabs
|
|
175
154
|
if ('orientation' in item) {
|
|
176
155
|
const tab = item as NavProps
|
|
177
156
|
return (
|
|
178
|
-
<
|
|
157
|
+
<Tab
|
|
179
158
|
key={tab.title}
|
|
180
159
|
value={tab.title}
|
|
181
160
|
label={tab.title}
|
|
182
|
-
|
|
183
|
-
hasrightborder={tab.hasrightborder}
|
|
184
|
-
height={height}
|
|
185
|
-
orientation={tab.orientation}
|
|
161
|
+
onClick={() => handleTabClick(tab)}
|
|
186
162
|
sx={{
|
|
163
|
+
minHeight: 0,
|
|
164
|
+
textTransform: 'none',
|
|
165
|
+
border: 'none',
|
|
166
|
+
boxSizing: 'border-box',
|
|
167
|
+
backgroundColor: 'black',
|
|
168
|
+
color: '#fff',
|
|
169
|
+
fontWeight: 500,
|
|
170
|
+
fontFamily: 'Merriweather',
|
|
171
|
+
fontSize: 16,
|
|
172
|
+
height: height,
|
|
173
|
+
'&:hover': {
|
|
174
|
+
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
175
|
+
},
|
|
176
|
+
'& .MuiTouchRipple-root': {
|
|
177
|
+
color: '#fff',
|
|
178
|
+
},
|
|
179
|
+
'&.Mui-selected': {
|
|
180
|
+
color: '#fff',
|
|
181
|
+
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
|
182
|
+
},
|
|
183
|
+
'& .MuiSvgIcon-root': {
|
|
184
|
+
color: '#fff',
|
|
185
|
+
},
|
|
186
|
+
...(tab.hasleftborder === 'true' && {
|
|
187
|
+
borderLeft: '1px solid white',
|
|
188
|
+
}),
|
|
189
|
+
...(tab.hasrightborder === 'true' && {
|
|
190
|
+
borderRight: '1px solid white',
|
|
191
|
+
}),
|
|
187
192
|
width: 'auto',
|
|
188
193
|
justifyContent: 'center',
|
|
189
194
|
alignItems: 'center',
|
|
190
195
|
px: 4,
|
|
191
196
|
}}
|
|
192
|
-
onClick={() => handleTabClick(tab)}
|
|
193
|
-
title={tab.title}
|
|
194
|
-
route={tab.route}
|
|
195
|
-
navname={tab.navname}
|
|
196
197
|
/>
|
|
197
198
|
)
|
|
198
199
|
}
|
|
199
200
|
return null
|
|
200
201
|
})}
|
|
201
202
|
</Tabs>
|
|
202
|
-
</
|
|
203
|
+
</Box>
|
|
203
204
|
)
|
|
204
205
|
}
|
|
205
206
|
|