goobs-frontend 0.9.28 → 0.9.30

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.9.28",
3
+ "version": "0.9.30",
4
4
  "type": "module",
5
5
  "description": "A comprehensive React-based libary that extends the functionality of Material-UI",
6
6
  "license": "MIT",
@@ -28,6 +28,7 @@
28
28
  "@mui/icons-material": "^7",
29
29
  "@mui/material": "^7",
30
30
  "@storybook/addon-links": "^9",
31
+ "@storybook/test": "^8.6.14",
31
32
  "@types/lodash": "^4",
32
33
  "formik": "^2",
33
34
  "highlight.js": "^11",
@@ -8,8 +8,10 @@ import {
8
8
  alpha,
9
9
  keyframes,
10
10
  } from '@mui/material'
11
- import { Typography } from './../../components/Typography'
12
- import { TypographyPropsVariantOverrides } from '@mui/material'
11
+ import {
12
+ Typography,
13
+ CustomTypographyVariant,
14
+ } from './../../components/Typography'
13
15
 
14
16
  const glowPulse = keyframes`
15
17
  0% { box-shadow: 0 0 5px rgba(255, 215, 0, 0.5); }
@@ -23,7 +25,7 @@ const glowPulse = keyframes`
23
25
  export interface RadioOption {
24
26
  label: string
25
27
  fontColor?: string
26
- fontVariant?: string
28
+ fontVariant?: CustomTypographyVariant
27
29
  }
28
30
 
29
31
  /**
@@ -34,7 +36,7 @@ export interface RadioGroupProps {
34
36
  options: RadioOption[]
35
37
  defaultValue?: string
36
38
  name: string
37
- labelFontVariant?: keyof TypographyPropsVariantOverrides
39
+ labelFontVariant?: CustomTypographyVariant
38
40
  labelFontColor?: string
39
41
  labelText?: string
40
42
  sacredTheme?: boolean
@@ -91,9 +93,7 @@ const RadioGroup: React.FC<RadioGroupProps> = ({
91
93
  <Typography
92
94
  text={labelText || label}
93
95
  fontcolor={sacredTheme ? '#FFD700' : labelFontColor}
94
- fontvariant={
95
- labelFontVariant as keyof TypographyPropsVariantOverrides
96
- }
96
+ fontvariant={labelFontVariant}
97
97
  />
98
98
  </FormLabel>
99
99
  {/* Render the radio group */}
@@ -130,9 +130,7 @@ const RadioGroup: React.FC<RadioGroupProps> = ({
130
130
  fontcolor={
131
131
  sacredTheme ? alpha('#FFD700', 0.9) : option.fontColor
132
132
  }
133
- fontvariant={
134
- option.fontVariant as keyof TypographyPropsVariantOverrides
135
- }
133
+ fontvariant={option.fontVariant}
136
134
  sx={
137
135
  sacredTheme
138
136
  ? {
@@ -1,7 +1,6 @@
1
1
  // src/components/RadioGroup/radiogroup.stories.tsx
2
2
 
3
3
  import type { Meta, StoryObj } from '@storybook/react'
4
- import { within, userEvent, expect } from '@storybook/test'
5
4
  import RadioGroup, { RadioOption } from './index'
6
5
 
7
6
  /**
@@ -31,7 +30,6 @@ type Story = StoryObj<typeof RadioGroup>
31
30
 
32
31
  /**
33
32
  * 1) Basic usage
34
- * No user interactions => remove `async`.
35
33
  */
36
34
  export const Basic: Story = {
37
35
  args: {
@@ -39,21 +37,10 @@ export const Basic: Story = {
39
37
  labelText: 'Choose an Option',
40
38
  options: sampleOptions,
41
39
  },
42
- play: ({ canvasElement }) => {
43
- const canvas = within(canvasElement)
44
- // Verify label text
45
- expect(canvas.getByText('Choose an Option')).toBeInTheDocument()
46
-
47
- // The group has three radio options: Option A, B, C
48
- expect(canvas.getByText('Option A')).toBeInTheDocument()
49
- expect(canvas.getByText('Option B')).toBeInTheDocument()
50
- expect(canvas.getByText('Option C')).toBeInTheDocument()
51
- },
52
40
  }
53
41
 
54
42
  /**
55
43
  * 2) With a default selected value
56
- * No user interactions => remove `async`.
57
44
  */
58
45
  export const WithDefaultValue: Story = {
59
46
  args: {
@@ -62,17 +49,10 @@ export const WithDefaultValue: Story = {
62
49
  defaultValue: 'Option B',
63
50
  options: sampleOptions,
64
51
  },
65
- play: ({ canvasElement }) => {
66
- const canvas = within(canvasElement)
67
- // Confirm "Option B" is selected by default
68
- const optionB = canvas.getByLabelText('Option B')
69
- expect(optionB).toBeChecked()
70
- },
71
52
  }
72
53
 
73
54
  /**
74
55
  * 3) Custom Label Styles
75
- * No user interactions => remove `async`.
76
56
  */
77
57
  export const CustomLabelStyles: Story = {
78
58
  args: {
@@ -92,21 +72,10 @@ export const CustomLabelStyles: Story = {
92
72
  },
93
73
  ],
94
74
  },
95
- play: ({ canvasElement }) => {
96
- const canvas = within(canvasElement)
97
- // The label text should be visible
98
- expect(canvas.getByText('Which flavor do you prefer?')).toBeInTheDocument()
99
-
100
- // Check presence of each custom-labeled item
101
- expect(canvas.getByText('Vanilla')).toBeInTheDocument()
102
- expect(canvas.getByText('Chocolate')).toBeInTheDocument()
103
- expect(canvas.getByText('Strawberry')).toBeInTheDocument()
104
- },
105
75
  }
106
76
 
107
77
  /**
108
78
  * 4) Label variant & color from the top-level props
109
- * No user interactions => remove `async`.
110
79
  */
111
80
  export const TopLevelLabelStyling: Story = {
112
81
  args: {
@@ -115,16 +84,10 @@ export const TopLevelLabelStyling: Story = {
115
84
  labelFontColor: '#673ab7', // Deep Purple
116
85
  options: sampleOptions,
117
86
  },
118
- play: ({ canvasElement }) => {
119
- const canvas = within(canvasElement)
120
- // Confirm the top-level label text
121
- expect(canvas.getByText('Survey Question')).toBeInTheDocument()
122
- },
123
87
  }
124
88
 
125
89
  /**
126
90
  * 5) Interaction: Selecting an Option
127
- * Uses userEvent => keep `async`.
128
91
  */
129
92
  export const SelectingOption: Story = {
130
93
  args: {
@@ -132,24 +95,4 @@ export const SelectingOption: Story = {
132
95
  labelText: 'Pick a letter',
133
96
  options: sampleOptions,
134
97
  },
135
- play: async ({ canvasElement }) => {
136
- const canvas = within(canvasElement)
137
-
138
- // Initially none are selected
139
- const optionA = canvas.getByLabelText('Option A')
140
- const optionB = canvas.getByLabelText('Option B')
141
- const optionC = canvas.getByLabelText('Option C')
142
-
143
- expect(optionA).not.toBeChecked()
144
- expect(optionB).not.toBeChecked()
145
- expect(optionC).not.toBeChecked()
146
-
147
- // Click on Option C
148
- await userEvent.click(optionC)
149
- expect(optionC).toBeChecked()
150
-
151
- // Option A & B remain unchecked
152
- expect(optionA).not.toBeChecked()
153
- expect(optionB).not.toBeChecked()
154
- },
155
98
  }
@@ -0,0 +1,343 @@
1
+ 'use client'
2
+
3
+ import React, { ReactNode } from 'react'
4
+ import {
5
+ Box,
6
+ Card,
7
+ CardContent,
8
+ CardHeader,
9
+ CardActions,
10
+ alpha,
11
+ keyframes,
12
+ Fade,
13
+ } from '@mui/material'
14
+ import Typography from '../Typography'
15
+
16
+ // Sacred theming constants
17
+ const SACRED_GLYPHS = [
18
+ '𓁟',
19
+ '𓂀',
20
+ '𓃀',
21
+ '𓄿',
22
+ '𓊖',
23
+ '𓊗',
24
+ '𓋴',
25
+ '𓏏',
26
+ '𓊨',
27
+ '𓁦',
28
+ '𓅓',
29
+ '𓆄',
30
+ '𓇳',
31
+ '𓈖',
32
+ '𓊹',
33
+ '𓊺',
34
+ '𓊻',
35
+ '𓋹',
36
+ '𓌻',
37
+ '𓍿',
38
+ '𓅨',
39
+ '𓂋',
40
+ '𓏭',
41
+ '𓊵',
42
+ ]
43
+
44
+ const sacredFloat = keyframes`
45
+ 0% { transform: translateY(0px) scale(1); opacity: 0.6; }
46
+ 50% { transform: translateY(-3px) scale(1.05); opacity: 0.8; }
47
+ 100% { transform: translateY(0px) scale(1); opacity: 0.6; }
48
+ `
49
+
50
+ const sacredGlowPulse = keyframes`
51
+ 0% {
52
+ box-shadow: 0 0 5px rgba(255, 215, 0, 0.5), 0 0 10px rgba(255, 215, 0, 0.3), inset 0 0 5px rgba(255, 215, 0, 0.2);
53
+ }
54
+ 50% {
55
+ box-shadow: 0 0 10px rgba(255, 215, 0, 0.8), 0 0 20px rgba(255, 215, 0, 0.5), inset 0 0 10px rgba(255, 215, 0, 0.3);
56
+ }
57
+ 100% {
58
+ box-shadow: 0 0 5px rgba(255, 215, 0, 0.5), 0 0 10px rgba(255, 215, 0, 0.3), inset 0 0 5px rgba(255, 215, 0, 0.2);
59
+ }
60
+ `
61
+
62
+ export interface WidgetProps {
63
+ /** The title of the widget */
64
+ title?: string
65
+ /** Optional icon to display in the header */
66
+ icon?: ReactNode
67
+ /** Widget variant for different styling */
68
+ variant?: 'standard' | 'highlighted' | 'temple'
69
+ /** Glow intensity for sacred theming */
70
+ glowIntensity?: 'low' | 'medium' | 'high'
71
+ /** Actions to display in the widget footer */
72
+ actions?: ReactNode
73
+ /** Corner glyphs for sacred theming */
74
+ cornerGlyphs?: string[]
75
+ /** Enable hieroglyphic decoration */
76
+ hieroglyphicDecoration?: boolean
77
+ /** Enable sacred theming */
78
+ sacredTheme?: boolean
79
+ /** Widget content */
80
+ children: ReactNode
81
+ /** Animation delay for entrance */
82
+ delay?: number
83
+ /** Full width */
84
+ fullWidth?: boolean
85
+ /** Custom height */
86
+ height?: string | number
87
+ /** Disable elevation */
88
+ flat?: boolean
89
+ }
90
+
91
+ const Widget: React.FC<WidgetProps> = ({
92
+ title,
93
+ icon,
94
+ variant = 'standard',
95
+ glowIntensity = 'medium',
96
+ actions,
97
+ cornerGlyphs,
98
+ hieroglyphicDecoration = false,
99
+ sacredTheme = false,
100
+ children,
101
+ delay = 0,
102
+ fullWidth = false,
103
+ height,
104
+ flat = false,
105
+ }) => {
106
+ // Generate random glyphs if not provided and sacred theme is enabled
107
+ const glyphs =
108
+ sacredTheme && hieroglyphicDecoration
109
+ ? cornerGlyphs || [
110
+ SACRED_GLYPHS[Math.floor(Math.random() * SACRED_GLYPHS.length)],
111
+ SACRED_GLYPHS[Math.floor(Math.random() * SACRED_GLYPHS.length)],
112
+ ]
113
+ : []
114
+
115
+ // Glow intensity levels
116
+ const glowLevels = {
117
+ low: {
118
+ borderOpacity: 0.3,
119
+ shadowOpacity: 0.2,
120
+ glowSize: '10px',
121
+ },
122
+ medium: {
123
+ borderOpacity: 0.5,
124
+ shadowOpacity: 0.3,
125
+ glowSize: '15px',
126
+ },
127
+ high: {
128
+ borderOpacity: 0.7,
129
+ shadowOpacity: 0.4,
130
+ glowSize: '20px',
131
+ },
132
+ }
133
+
134
+ // Sacred color scheme
135
+ const sacredColors = {
136
+ gold: '#FFD700',
137
+ temple: '#0a0a0a',
138
+ obsidian: '#1a1a1a',
139
+ }
140
+
141
+ // Base styles
142
+ const baseStyles = sacredTheme
143
+ ? {
144
+ backgroundColor: alpha(sacredColors.temple, 0.95),
145
+ border: `2px solid ${alpha(sacredColors.gold, glowLevels[glowIntensity].borderOpacity)}`,
146
+ borderRadius: '12px',
147
+ color: sacredColors.gold,
148
+ backgroundImage:
149
+ variant === 'highlighted'
150
+ ? `linear-gradient(135deg,
151
+ ${alpha(sacredColors.gold, 0.12)} 0%,
152
+ ${alpha(sacredColors.temple, 0.95)} 30%,
153
+ ${alpha(sacredColors.gold, 0.08)} 70%,
154
+ ${alpha(sacredColors.temple, 0.97)} 100%
155
+ )`
156
+ : variant === 'temple'
157
+ ? `radial-gradient(circle at top right, ${alpha(sacredColors.gold, 0.07)} 0%, transparent 60%),
158
+ radial-gradient(circle at bottom left, ${alpha(sacredColors.gold, 0.05)} 0%, transparent 60%),
159
+ linear-gradient(135deg,
160
+ ${alpha(sacredColors.gold, 0.03)} 0%,
161
+ ${alpha(sacredColors.temple, 0.97)} 50%,
162
+ ${alpha(sacredColors.gold, 0.02)} 100%
163
+ )`
164
+ : `linear-gradient(135deg,
165
+ ${alpha(sacredColors.gold, 0.05)} 0%,
166
+ ${alpha(sacredColors.temple, 0.98)} 50%,
167
+ ${alpha(sacredColors.gold, 0.03)} 100%
168
+ )`,
169
+ boxShadow: `0 4px 20px ${alpha(sacredColors.gold, glowLevels[glowIntensity].shadowOpacity)}`,
170
+ transition: 'all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
171
+ '&:hover': {
172
+ transform: 'translateY(-5px)',
173
+ borderColor: alpha(
174
+ sacredColors.gold,
175
+ glowLevels[glowIntensity].borderOpacity + 0.2
176
+ ),
177
+ boxShadow: `0 15px 30px ${alpha('#000000', 0.6)}, 0 0 30px ${alpha(sacredColors.gold, glowLevels[glowIntensity].shadowOpacity + 0.15)}`,
178
+ animation: `${sacredGlowPulse} 2s ease-in-out infinite`,
179
+ },
180
+ '&::before': hieroglyphicDecoration
181
+ ? {
182
+ content: '""',
183
+ position: 'absolute',
184
+ top: 0,
185
+ left: 0,
186
+ right: 0,
187
+ bottom: 0,
188
+ borderRadius: 'inherit',
189
+ background: `
190
+ conic-gradient(from 0deg at 50% 50%,
191
+ ${alpha(sacredColors.gold, 0.1)} 0deg,
192
+ transparent 60deg,
193
+ ${alpha(sacredColors.gold, 0.05)} 120deg,
194
+ transparent 180deg,
195
+ ${alpha(sacredColors.gold, 0.1)} 240deg,
196
+ transparent 300deg,
197
+ ${alpha(sacredColors.gold, 0.05)} 360deg
198
+ )
199
+ `,
200
+ opacity: 0.3,
201
+ zIndex: 0,
202
+ }
203
+ : {},
204
+ }
205
+ : {}
206
+
207
+ const cardContent = (
208
+ <Card
209
+ elevation={flat ? 0 : 3}
210
+ sx={{
211
+ position: 'relative',
212
+ overflow: 'visible',
213
+ width: fullWidth ? '100%' : 'auto',
214
+ height: height || 'auto',
215
+ ...baseStyles,
216
+ }}
217
+ >
218
+ {/* Sacred corner glyphs */}
219
+ {sacredTheme && hieroglyphicDecoration && glyphs.length >= 2 && (
220
+ <>
221
+ <Box
222
+ sx={{
223
+ position: 'absolute',
224
+ top: '10px',
225
+ left: '10px',
226
+ color: alpha(sacredColors.gold, 0.3),
227
+ fontSize: '18px',
228
+ animation: `${sacredFloat} 5s ease-in-out infinite`,
229
+ zIndex: 1,
230
+ }}
231
+ >
232
+ {glyphs[0]}
233
+ </Box>
234
+ <Box
235
+ sx={{
236
+ position: 'absolute',
237
+ top: '10px',
238
+ right: '10px',
239
+ color: alpha(sacredColors.gold, 0.3),
240
+ fontSize: '18px',
241
+ animation: `${sacredFloat} 6s ease-in-out infinite reverse`,
242
+ zIndex: 1,
243
+ }}
244
+ >
245
+ {glyphs[1]}
246
+ </Box>
247
+ </>
248
+ )}
249
+
250
+ {/* Header */}
251
+ {(title || icon) && (
252
+ <CardHeader
253
+ avatar={
254
+ icon && (
255
+ <Box
256
+ sx={{
257
+ color: sacredTheme ? sacredColors.gold : 'inherit',
258
+ display: 'flex',
259
+ alignItems: 'center',
260
+ justifyContent: 'center',
261
+ ...(sacredTheme && {
262
+ filter: 'drop-shadow(0 0 5px rgba(255, 215, 0, 0.6))',
263
+ transition: 'all 0.3s ease',
264
+ }),
265
+ }}
266
+ >
267
+ {icon}
268
+ </Box>
269
+ )
270
+ }
271
+ title={
272
+ title && (
273
+ <Typography
274
+ variant="h6"
275
+ sacredTheme={sacredTheme}
276
+ sx={{
277
+ fontSize: '1.25rem',
278
+ fontWeight: 600,
279
+ ...(sacredTheme && {
280
+ fontFamily: '"Cinzel", serif',
281
+ letterSpacing: '1px',
282
+ textTransform: 'uppercase',
283
+ }),
284
+ }}
285
+ >
286
+ {title}
287
+ </Typography>
288
+ )
289
+ }
290
+ sx={{
291
+ position: 'relative',
292
+ zIndex: 1,
293
+ borderBottom:
294
+ title && actions
295
+ ? `1px solid ${alpha(sacredTheme ? sacredColors.gold : '#000', 0.1)}`
296
+ : 'none',
297
+ }}
298
+ />
299
+ )}
300
+
301
+ {/* Content */}
302
+ <CardContent
303
+ sx={{
304
+ position: 'relative',
305
+ zIndex: 1,
306
+ ...(sacredTheme && {
307
+ '& *': {
308
+ fontFamily: '"Cinzel", serif',
309
+ },
310
+ }),
311
+ }}
312
+ >
313
+ {children}
314
+ </CardContent>
315
+
316
+ {/* Actions */}
317
+ {actions && (
318
+ <CardActions
319
+ sx={{
320
+ position: 'relative',
321
+ zIndex: 1,
322
+ borderTop: `1px solid ${alpha(sacredTheme ? sacredColors.gold : '#000', 0.1)}`,
323
+ justifyContent: 'center',
324
+ }}
325
+ >
326
+ {actions}
327
+ </CardActions>
328
+ )}
329
+ </Card>
330
+ )
331
+
332
+ if (delay > 0) {
333
+ return (
334
+ <Fade in timeout={1000 + delay}>
335
+ <Box>{cardContent}</Box>
336
+ </Fade>
337
+ )
338
+ }
339
+
340
+ return cardContent
341
+ }
342
+
343
+ export default Widget
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import Typography, {
4
4
  TypographyVariant,
5
5
  TypographyProps,
6
6
  } from './components/Typography'
7
+ import Widget, { WidgetProps } from './components/Widget'
7
8
  import ConfirmationCodeInput, {
8
9
  ConfirmationCodeInputsProps,
9
10
  } from './components/ConfirmationCodeInput'
@@ -173,6 +174,7 @@ import ShowTask, {
173
174
 
174
175
  export { CustomButton }
175
176
  export { Typography }
177
+ export { Widget }
176
178
  export { ConfirmationCodeInput }
177
179
  export { RadioGroup }
178
180
  export { Popup }
@@ -263,6 +265,7 @@ export type { DropdownProps }
263
265
  export type { CustomButtonProps }
264
266
  export type { ComplexTextEditorProps }
265
267
  export type { FontFamily, TypographyVariant, TypographyProps }
268
+ export type { WidgetProps }
266
269
  export type { ConfirmationCodeInputsProps }
267
270
  export type { RadioOption, RadioGroupProps }
268
271
  export type { PopupProps }