goobs-frontend 0.7.61 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goobs-frontend",
3
- "version": "0.7.61",
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.3.2",
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
- 'use client'
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 [isFormValid, setIsFormValid] = useState<boolean>(true)
92
- const [helperFooterValue, setHelperFooterValue] = useState<
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
- const errorFooters = relevantFooters.filter(
103
- footer => footer?.status === 'error'
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
- const emptyRequiredFields = relevantFooters.filter(
107
- footer =>
108
- footer?.required && (!footer.status || footer.status === 'error')
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
- if (errorFooters.length > 0) {
112
- const highestPriorityError = errorFooters.reduce((prev, current) =>
113
- (prev.spreadMessagePriority || Infinity) <
114
- (current.spreadMessagePriority || Infinity)
115
- ? prev
116
- : current
117
- )
118
- setErrorMessage(highestPriorityError.spreadMessage)
119
- setIsFormValid(false)
120
- } else if (emptyRequiredFields.length > 0) {
121
- setErrorMessage('Please fill in all required fields.')
122
- setIsFormValid(false)
123
- } else {
124
- setErrorMessage(undefined)
125
- setIsFormValid(true)
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
- useEffect(() => {
134
- const fetchHelperFooter = async () => {
135
- const helperFooterResult = await get('helperFooter', 'client')
136
- if (
137
- helperFooterResult &&
138
- typeof helperFooterResult === 'object' &&
139
- 'value' in helperFooterResult
140
- ) {
141
- setHelperFooterValue(
142
- (helperFooterResult as JSONValue).value as Record<
143
- string,
144
- HelperFooterMessage
145
- >
146
- )
147
- } else {
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
- fetchHelperFooter()
153
- }, [formname])
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
- updateFormValidation()
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
- if (!isFormValid) {
173
- return
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
- if (onClick) {
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}
@@ -80,8 +80,8 @@ const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
80
80
  if (isValid) {
81
81
  set(
82
82
  'verificationCode',
83
- combinedCode,
84
- new Date(Date.now() + 3600000),
83
+ 'codeStore',
84
+ { type: 'string', value: combinedCode },
85
85
  'client'
86
86
  )
87
87
  }
@@ -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<ContentSectionProps['grids'][0]> = ({
75
- grid,
76
- ...props
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={grid.gridconfig} columnconfig={columnConfigs} />
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
- { title, description, grids, onSubmit, content, popupType, open, onClose },
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 open={open || false} onClose={onClose} fullWidth maxWidth="sm">
188
+ <Dialog {...dialogProps}>
170
189
  {onClose && (
171
190
  <IconButton
172
191
  size="small"
@@ -1,10 +1,16 @@
1
- // src/components/Icons/ShowHideEyeIcon.tsx
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
- const ShowHideEyeIcon = ({ visible }: { visible: boolean }) => {
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} />
@@ -59,10 +59,17 @@ function HorizontalVariant({
59
59
  * Asynchronously fetches the active tab values from the cache.
60
60
  */
61
61
  const fetchActiveTabValues = async () => {
62
- const result = await get('activeTabValues', 'client')
63
- if (result && typeof result === 'object' && 'value' in result) {
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
+ ) {
64
71
  setActiveTabValues(
65
- (result as JSONValue).value as Record<string, ActiveTabValue | null>
72
+ result.value as Record<string, ActiveTabValue | null>
66
73
  )
67
74
  }
68
75
  }
@@ -88,8 +95,8 @@ function HorizontalVariant({
88
95
  setActiveTabValues(updatedActiveTabValues)
89
96
  await set(
90
97
  'activeTabValues',
98
+ 'tabStore',
91
99
  { type: 'json', value: updatedActiveTabValues } as JSONValue,
92
- new Date(Date.now() + 30 * 60 * 1000),
93
100
  'client'
94
101
  )
95
102
  }
@@ -1,9 +1,8 @@
1
1
  'use client'
2
- import React, { useState, useEffect, useMemo } from 'react'
2
+ import React, { useState, useMemo } from 'react'
3
3
  import type { JSX } from 'react'
4
4
  import HorizontalVariant from './HorizontalVariant'
5
5
  import VerticalVariant from './VerticalVariant'
6
- import { get, JSONValue } from 'goobs-cache'
7
6
 
8
7
  // Type definition for alignment options
9
8
  type Alignment = 'left' | 'center' | 'right' | 'inherit' | 'justify'
@@ -78,7 +77,7 @@ function Nav({
78
77
  // State for expanded navigation items
79
78
  const [expandedNavs, setExpandedNavs] = useState<string[]>([])
80
79
  const [expandedSubnavs, setExpandedSubnavs] = useState<string[]>([])
81
- const [verticalNavWidth, setVerticalNavWidth] = useState<number>(250) // Default width
80
+ const [verticalNavWidth] = useState<number>(250) // Default width, no longer using goobs-cache
82
81
 
83
82
  // Memoized navigation items
84
83
  const navs = useMemo(() => {
@@ -97,17 +96,6 @@ function Nav({
97
96
  return navs
98
97
  }, [items])
99
98
 
100
- // Effect to fetch vertical nav width from cache
101
- useEffect(() => {
102
- const fetchVerticalNavWidth = async () => {
103
- const result = await get('verticalNavWidth', 'client')
104
- if (result && typeof result === 'object' && 'value' in result) {
105
- setVerticalNavWidth((result as JSONValue).value as number)
106
- }
107
- }
108
- fetchVerticalNavWidth()
109
- }, [])
110
-
111
99
  // Render vertical or horizontal variant based on orientation
112
100
  if (orientation === 'vertical') {
113
101
  return (
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react'
1
+ import React from 'react'
2
2
  import { InputAdornment, Button, Box } from '@mui/material'
3
3
  import SearchIcon from '../../Icons/Search'
4
4
  import ShowHideEyeIcon from '../../Icons/ShowHideEye'
@@ -11,6 +11,7 @@ export interface AdornmentProps {
11
11
  componentvariant: string
12
12
  iconcolor?: string
13
13
  passwordVisible?: boolean
14
+ togglePasswordVisibility?: () => void
14
15
  marginRight?: number | string
15
16
  handleIncrement?: () => void
16
17
  handleDecrement?: () => void
@@ -43,18 +44,15 @@ export const EndAdornment: React.FC<AdornmentProps> = props => {
43
44
  const {
44
45
  componentvariant,
45
46
  passwordVisible,
47
+ togglePasswordVisibility,
46
48
  handleIncrement,
47
49
  handleDecrement,
48
50
  } = props
49
- const [isPasswordVisible, setIsPasswordVisible] = useState(
50
- passwordVisible || false
51
- )
51
+
52
52
  const adornmentStyle = {
53
53
  cursor: 'pointer',
54
54
  }
55
- const togglePasswordVisibility = () => {
56
- setIsPasswordVisible(!isPasswordVisible)
57
- }
55
+
58
56
  // Render the show/hide eye icon for the password variant
59
57
  if (componentvariant === 'password') {
60
58
  return (
@@ -63,7 +61,7 @@ export const EndAdornment: React.FC<AdornmentProps> = props => {
63
61
  onClick={togglePasswordVisibility}
64
62
  style={adornmentStyle}
65
63
  >
66
- <ShowHideEyeIcon visible={isPasswordVisible} />
64
+ <ShowHideEyeIcon visible={passwordVisible} />
67
65
  </InputAdornment>
68
66
  )
69
67
  }