@moontra/moonui-pro 2.20.1 → 2.20.2

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 (76) hide show
  1. package/dist/index.d.ts +691 -261
  2. package/dist/index.mjs +7418 -4934
  3. package/package.json +4 -3
  4. package/scripts/postbuild.js +27 -0
  5. package/src/components/advanced-chart/index.tsx +5 -1
  6. package/src/components/advanced-forms/index.tsx +175 -16
  7. package/src/components/calendar/event-dialog.tsx +18 -13
  8. package/src/components/calendar/index.tsx +197 -50
  9. package/src/components/dashboard/dashboard-grid.tsx +21 -3
  10. package/src/components/dashboard/types.ts +3 -0
  11. package/src/components/dashboard/widgets/activity-feed.tsx +6 -1
  12. package/src/components/dashboard/widgets/comparison-widget.tsx +177 -0
  13. package/src/components/dashboard/widgets/index.ts +5 -0
  14. package/src/components/dashboard/widgets/metric-card.tsx +21 -1
  15. package/src/components/dashboard/widgets/progress-widget.tsx +113 -0
  16. package/src/components/error-boundary/index.tsx +160 -37
  17. package/src/components/form-wizard/form-wizard-context.tsx +54 -26
  18. package/src/components/form-wizard/form-wizard-progress.tsx +33 -2
  19. package/src/components/form-wizard/types.ts +2 -1
  20. package/src/components/github-stars/hooks.ts +1 -0
  21. package/src/components/github-stars/variants.tsx +3 -1
  22. package/src/components/health-check/index.tsx +14 -14
  23. package/src/components/hover-card-3d/index.tsx +2 -3
  24. package/src/components/index.ts +5 -3
  25. package/src/components/kanban/kanban.tsx +23 -18
  26. package/src/components/license-error/index.tsx +2 -0
  27. package/src/components/magnetic-button/index.tsx +56 -7
  28. package/src/components/memory-efficient-data/index.tsx +117 -115
  29. package/src/components/navbar/index.tsx +781 -0
  30. package/src/components/performance-debugger/index.tsx +62 -38
  31. package/src/components/performance-monitor/index.tsx +47 -33
  32. package/src/components/phone-number-input/index.tsx +32 -27
  33. package/src/components/phone-number-input/phone-number-input-simple.tsx +167 -0
  34. package/src/components/rich-text-editor/index.tsx +26 -28
  35. package/src/components/rich-text-editor/slash-commands-extension.ts +15 -5
  36. package/src/components/sidebar/index.tsx +32 -13
  37. package/src/components/timeline/index.tsx +84 -49
  38. package/src/components/ui/accordion.tsx +550 -42
  39. package/src/components/ui/avatar.tsx +2 -0
  40. package/src/components/ui/badge.tsx +2 -0
  41. package/src/components/ui/breadcrumb.tsx +2 -0
  42. package/src/components/ui/button.tsx +39 -33
  43. package/src/components/ui/card.tsx +2 -0
  44. package/src/components/ui/collapsible.tsx +546 -50
  45. package/src/components/ui/command.tsx +790 -67
  46. package/src/components/ui/dialog.tsx +510 -92
  47. package/src/components/ui/dropdown-menu.tsx +540 -52
  48. package/src/components/ui/index.ts +37 -5
  49. package/src/components/ui/input.tsx +2 -0
  50. package/src/components/ui/magnetic-button.tsx +1 -1
  51. package/src/components/ui/media-gallery.tsx +1 -2
  52. package/src/components/ui/navigation-menu.tsx +130 -0
  53. package/src/components/ui/pagination.tsx +2 -0
  54. package/src/components/ui/select.tsx +6 -2
  55. package/src/components/ui/spotlight-card.tsx +1 -1
  56. package/src/components/ui/table.tsx +2 -0
  57. package/src/components/ui/tabs-pro.tsx +542 -0
  58. package/src/components/ui/tabs.tsx +23 -167
  59. package/src/components/ui/toggle.tsx +12 -12
  60. package/src/index.ts +11 -3
  61. package/src/styles/index.css +596 -0
  62. package/src/use-performance-optimizer.ts +1 -1
  63. package/src/utils/chart-helpers.ts +1 -1
  64. package/src/__tests__/use-intersection-observer.test.tsx +0 -216
  65. package/src/__tests__/use-local-storage.test.tsx +0 -174
  66. package/src/__tests__/use-pro-access.test.tsx +0 -183
  67. package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
  68. package/src/components/data-table/data-table.test.tsx +0 -187
  69. package/src/components/enhanced/badge.tsx +0 -191
  70. package/src/components/enhanced/button.tsx +0 -362
  71. package/src/components/enhanced/card.tsx +0 -266
  72. package/src/components/enhanced/dialog.tsx +0 -246
  73. package/src/components/enhanced/index.ts +0 -4
  74. package/src/components/file-upload/file-upload.test.tsx +0 -243
  75. package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
  76. package/src/types/moonui.d.ts +0 -22
@@ -0,0 +1,113 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import { motion } from 'framer-motion'
5
+ import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card'
6
+ import { Progress } from '../../ui/progress'
7
+ import { cn } from '../../../lib/utils'
8
+ import { ProgressData } from '../types'
9
+ import { Clock, TrendingUp, Target } from 'lucide-react'
10
+
11
+ interface ProgressWidgetProps {
12
+ data: ProgressData | ProgressData[]
13
+ title?: string
14
+ className?: string
15
+ glassmorphism?: boolean
16
+ }
17
+
18
+ export function ProgressWidget({
19
+ data,
20
+ title = "Progress",
21
+ className,
22
+ glassmorphism = false
23
+ }: ProgressWidgetProps) {
24
+ const items = Array.isArray(data) ? data : [data]
25
+ const [isMounted, setIsMounted] = React.useState(false)
26
+
27
+ React.useEffect(() => {
28
+ setIsMounted(true)
29
+ }, [])
30
+
31
+ return (
32
+ <Card className={cn(
33
+ "h-full flex flex-col",
34
+ glassmorphism && "bg-background/60 backdrop-blur-md border-white/10",
35
+ className
36
+ )}>
37
+ <CardHeader className="pb-3 space-y-0">
38
+ <CardTitle className="text-sm sm:text-base font-semibold flex items-center gap-2">
39
+ <Target className="h-4 w-4 text-muted-foreground" />
40
+ <span className="truncate">{title}</span>
41
+ </CardTitle>
42
+ </CardHeader>
43
+ <CardContent className="flex-1 space-y-3 overflow-auto">
44
+ {items.map((item, index) => {
45
+ const progress = (item.current / item.target) * 100
46
+ const isOverdue = item.deadline && new Date(item.deadline) < new Date()
47
+
48
+ return (
49
+ <motion.div
50
+ key={item.id}
51
+ initial={{ opacity: 0, y: 10 }}
52
+ animate={{ opacity: 1, y: 0 }}
53
+ transition={{ delay: index * 0.05 }}
54
+ className="space-y-2"
55
+ >
56
+ <div className="flex flex-col sm:flex-row sm:justify-between gap-1 sm:gap-2">
57
+ <div className="flex-1 min-w-0">
58
+ <h4 className="text-xs sm:text-sm font-medium truncate">{item.title}</h4>
59
+ {item.description && (
60
+ <p className="text-xs text-muted-foreground mt-0.5 truncate">{item.description}</p>
61
+ )}
62
+ </div>
63
+ <div className="text-left sm:text-right">
64
+ <p className="text-xs sm:text-sm font-semibold tabular-nums">
65
+ {item.current} / {item.target} {item.unit}
66
+ </p>
67
+ <p className="text-xs text-muted-foreground">
68
+ {progress.toFixed(1)}%
69
+ </p>
70
+ </div>
71
+ </div>
72
+
73
+ <div className="relative">
74
+ <Progress
75
+ value={Math.min(progress, 100)}
76
+ className={cn(
77
+ "h-1.5 sm:h-2",
78
+ isOverdue && "[&>div]:bg-destructive"
79
+ )}
80
+ />
81
+ {item.milestone && (
82
+ <div
83
+ className="absolute top-1/2 -translate-y-1/2 w-0.5 h-3 sm:h-4 bg-border"
84
+ style={{ left: `${(item.milestone / item.target) * 100}%` }}
85
+ />
86
+ )}
87
+ </div>
88
+
89
+ {item.deadline && (
90
+ <div className={cn(
91
+ "flex items-center gap-1 text-xs",
92
+ isOverdue ? "text-destructive" : "text-muted-foreground"
93
+ )}>
94
+ <Clock className="h-3 w-3" />
95
+ <span>
96
+ {isOverdue ? 'Overdue' : 'Due'}: {isMounted ? new Date(item.deadline).toLocaleDateString() : 'Loading...'}
97
+ </span>
98
+ </div>
99
+ )}
100
+
101
+ {item.trend && (
102
+ <div className="flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
103
+ <TrendingUp className="h-3 w-3" />
104
+ <span>+{item.trend}% this week</span>
105
+ </div>
106
+ )}
107
+ </motion.div>
108
+ )
109
+ })}
110
+ </CardContent>
111
+ </Card>
112
+ )
113
+ }
@@ -1,80 +1,202 @@
1
1
  "use client"
2
2
 
3
- import React, { Component, ErrorInfo, ReactNode } from "react"
3
+ import React, { Component, ErrorInfo, ReactNode, ComponentType, useEffect, useRef } from "react"
4
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
5
5
  import { Button } from "../ui/button"
6
6
  import { AlertTriangle, RefreshCw, Lock, Sparkles } from "lucide-react"
7
7
  import { cn } from "../../lib/utils"
8
8
  import { useSubscription } from "../../hooks/use-subscription"
9
9
 
10
+ // Types for error fallback component props
11
+ export interface ErrorFallbackProps {
12
+ error: Error
13
+ resetErrorBoundary: () => void
14
+ errorInfo?: ErrorInfo
15
+ }
16
+
10
17
  interface ErrorBoundaryProps {
11
18
  children: ReactNode
12
- fallback?: ReactNode
19
+ fallback?: ComponentType<ErrorFallbackProps> | ReactNode
13
20
  className?: string
14
21
  onError?: (error: Error, errorInfo: ErrorInfo) => void
22
+ onReset?: () => void
23
+ isolate?: boolean
24
+ resetKeys?: unknown[]
25
+ resetOnPropsChange?: boolean
26
+ showErrorDetails?: boolean
15
27
  }
16
28
 
17
29
  interface ErrorBoundaryState {
18
30
  hasError: boolean
19
31
  error?: Error
32
+ errorInfo?: ErrorInfo
20
33
  }
21
34
 
22
35
  class ErrorBoundaryInternal extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
36
+ resetTimeoutId: NodeJS.Timeout | null = null
37
+ previousResetKeys: unknown[] = []
38
+
23
39
  constructor(props: ErrorBoundaryProps) {
24
40
  super(props)
25
41
  this.state = { hasError: false }
42
+ this.previousResetKeys = props.resetKeys || []
26
43
  }
27
44
 
28
- static getDerivedStateFromError(error: Error): ErrorBoundaryState {
45
+ static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
29
46
  return { hasError: true, error }
30
47
  }
31
48
 
32
49
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
33
- this.props.onError?.(error, errorInfo)
34
- console.error('ErrorBoundary caught an error:', error, errorInfo)
50
+ const { onError, isolate } = this.props
51
+
52
+ // Store error info in state for fallback component
53
+ this.setState({ errorInfo })
54
+
55
+ // Call error handler if provided
56
+ onError?.(error, errorInfo)
57
+
58
+ // Log error (with isolation context if applicable)
59
+ if (isolate) {
60
+ console.error('[Isolated ErrorBoundary] caught an error:', error, errorInfo)
61
+ } else {
62
+ console.error('ErrorBoundary caught an error:', error, errorInfo)
63
+ }
35
64
  }
36
65
 
37
- render() {
38
- if (this.state.hasError) {
39
- if (this.props.fallback) {
40
- return this.props.fallback
66
+ componentDidUpdate(prevProps: ErrorBoundaryProps) {
67
+ const { resetKeys, resetOnPropsChange } = this.props
68
+ const { hasError } = this.state
69
+
70
+ // Check if we should reset based on resetKeys change
71
+ if (hasError && resetKeys) {
72
+ const hasResetKeyChanged = resetKeys.some(
73
+ (key, index) => key !== this.previousResetKeys[index]
74
+ )
75
+
76
+ if (hasResetKeyChanged) {
77
+ this.previousResetKeys = resetKeys
78
+ this.resetErrorBoundary()
79
+ }
80
+ }
81
+
82
+ // Reset on any props change if enabled
83
+ if (hasError && resetOnPropsChange && prevProps !== this.props) {
84
+ // Avoid resetting due to children changes
85
+ const propsWithoutChildren = { ...this.props, children: null }
86
+ const prevPropsWithoutChildren = { ...prevProps, children: null }
87
+
88
+ if (JSON.stringify(propsWithoutChildren) !== JSON.stringify(prevPropsWithoutChildren)) {
89
+ this.resetErrorBoundary()
90
+ }
91
+ }
92
+ }
93
+
94
+ resetErrorBoundary = () => {
95
+ const { onReset } = this.props
96
+ onReset?.()
97
+ this.setState({ hasError: false, error: undefined, errorInfo: undefined })
98
+ }
99
+
100
+ renderFallback() {
101
+ const { fallback, className, showErrorDetails } = this.props
102
+ const { error, errorInfo } = this.state
103
+
104
+ if (!error) return null
105
+
106
+ // If custom fallback is provided
107
+ if (fallback) {
108
+ // If fallback is a component
109
+ if (typeof fallback === 'function') {
110
+ const FallbackComponent = fallback as ComponentType<ErrorFallbackProps>
111
+ return (
112
+ <FallbackComponent
113
+ error={error}
114
+ resetErrorBoundary={this.resetErrorBoundary}
115
+ errorInfo={errorInfo}
116
+ />
117
+ )
41
118
  }
119
+ // If fallback is a ReactNode
120
+ return fallback
121
+ }
42
122
 
43
- return (
44
- <div className={cn("flex items-center justify-center min-h-[200px] p-4", this.props.className)}>
45
- <Card className="w-full max-w-md">
46
- <CardHeader className="text-center">
47
- <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
48
- <AlertTriangle className="h-6 w-6 text-red-600" />
123
+ // Default fallback UI
124
+ return (
125
+ <div className={cn("flex items-center justify-center min-h-[200px] p-4", className)}>
126
+ <Card className="w-full max-w-md">
127
+ <CardHeader className="text-center">
128
+ <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/20">
129
+ <AlertTriangle className="h-6 w-6 text-red-600 dark:text-red-400" />
130
+ </div>
131
+ <CardTitle>Something went wrong</CardTitle>
132
+ <CardDescription>
133
+ {showErrorDetails && process.env.NODE_ENV === 'development'
134
+ ? error.message
135
+ : "An error occurred while rendering this component"}
136
+ </CardDescription>
137
+ </CardHeader>
138
+ <CardContent className="text-center">
139
+ {showErrorDetails && process.env.NODE_ENV === 'development' && (
140
+ <div className="mb-4 p-3 bg-muted rounded-md text-left">
141
+ <pre className="text-xs overflow-auto">
142
+ <code>{error.stack}</code>
143
+ </pre>
49
144
  </div>
50
- <CardTitle>Something went wrong</CardTitle>
51
- <CardDescription>
52
- An error occurred while rendering this component
53
- </CardDescription>
54
- </CardHeader>
55
- <CardContent className="text-center">
56
- <Button
57
- onClick={() => this.setState({ hasError: false, error: undefined })}
58
- className="mt-4"
59
- >
60
- <RefreshCw className="mr-2 h-4 w-4" />
61
- Try again
62
- </Button>
63
- </CardContent>
64
- </Card>
65
- </div>
66
- )
145
+ )}
146
+ <Button
147
+ onClick={this.resetErrorBoundary}
148
+ className="mt-4"
149
+ >
150
+ <RefreshCw className="mr-2 h-4 w-4" />
151
+ Try again
152
+ </Button>
153
+ </CardContent>
154
+ </Card>
155
+ </div>
156
+ )
157
+ }
158
+
159
+ render() {
160
+ const { hasError } = this.state
161
+ const { children, isolate } = this.props
162
+
163
+ if (hasError) {
164
+ // If isolate is true, prevent error propagation
165
+ if (isolate) {
166
+ try {
167
+ return this.renderFallback()
168
+ } catch (fallbackError) {
169
+ // If fallback also fails, show minimal error UI
170
+ console.error('Error boundary fallback failed:', fallbackError)
171
+ return (
172
+ <div className="p-4 text-center text-red-600">
173
+ Critical error: Unable to recover
174
+ </div>
175
+ )
176
+ }
177
+ }
178
+ return this.renderFallback()
67
179
  }
68
180
 
69
- return this.props.children
181
+ return children
182
+ }
183
+
184
+ componentWillUnmount() {
185
+ if (this.resetTimeoutId) {
186
+ clearTimeout(this.resetTimeoutId)
187
+ }
70
188
  }
71
189
  }
72
190
 
191
+ // Functional wrapper to handle Pro subscription check
73
192
  function ErrorBoundaryWrapper(props: ErrorBoundaryProps) {
74
- // Check if we're in docs mode or have pro access
75
193
  const { hasProAccess, isLoading } = useSubscription()
76
-
77
- // In docs mode, always show the component
194
+ const prevResetKeysRef = useRef(props.resetKeys)
195
+
196
+ // Track resetKeys changes for functional component
197
+ useEffect(() => {
198
+ prevResetKeysRef.current = props.resetKeys
199
+ }, [props.resetKeys])
78
200
 
79
201
  // If not in docs mode and no pro access, show upgrade prompt
80
202
  if (!isLoading && !hasProAccess) {
@@ -106,4 +228,5 @@ function ErrorBoundaryWrapper(props: ErrorBoundaryProps) {
106
228
  return <ErrorBoundaryInternal {...props} />
107
229
  }
108
230
 
109
- export const ErrorBoundary = ErrorBoundaryWrapper
231
+ export const ErrorBoundary = ErrorBoundaryWrapper
232
+ export type { ErrorBoundaryProps }
@@ -96,36 +96,64 @@ export const FormWizardProvider: React.FC<FormWizardProviderProps> = ({
96
96
  const validateCurrentStep = useCallback(async (): Promise<boolean> => {
97
97
  const step = steps[currentStep]
98
98
 
99
- // First check HTML5 validation for required fields
100
- if (typeof window !== 'undefined') {
99
+ // Check if automatic validation should be performed
100
+ const shouldValidateFields = step.requiredFields !== false
101
+
102
+ // First check HTML5 validation for required fields if enabled
103
+ if (shouldValidateFields && typeof window !== 'undefined') {
101
104
  const stepElement = document.querySelector('[data-wizard-step-content]')
102
105
  if (stepElement) {
103
- const requiredInputs = stepElement.querySelectorAll('input[required], select[required], textarea[required]')
104
- const emptyFields: string[] = []
105
-
106
- requiredInputs.forEach((input: Element) => {
107
- const htmlInput = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
108
- if (!htmlInput.value || htmlInput.value.trim() === '') {
109
- const label = document.querySelector(`label[for="${htmlInput.id}"]`)
110
- const fieldName = label ? (label.textContent?.replace(' *', '') || 'Field') : htmlInput.name || htmlInput.id || 'Field'
111
- emptyFields.push(fieldName)
106
+ // If requiredFields is an array, only check those specific fields
107
+ if (Array.isArray(step.requiredFields)) {
108
+ const emptyFields: string[] = []
109
+
110
+ step.requiredFields.forEach((fieldName) => {
111
+ const field = stepElement.querySelector(`[name="${fieldName}"], [id="${fieldName}"]`)
112
+ if (field) {
113
+ const htmlField = field as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
114
+ if (field.tagName === 'INPUT' && (field as HTMLInputElement).type === 'checkbox') {
115
+ if (!(field as HTMLInputElement).checked) {
116
+ emptyFields.push(fieldName)
117
+ }
118
+ } else if (!htmlField.value || htmlField.value.trim() === '') {
119
+ emptyFields.push(fieldName)
120
+ }
121
+ }
122
+ })
123
+
124
+ if (emptyFields.length > 0) {
125
+ setError(`Please fill in the following required fields: ${emptyFields.join(', ')}`)
126
+ return false
112
127
  }
113
- })
114
-
115
- // Check for required checkboxes
116
- const requiredCheckboxes = stepElement.querySelectorAll('input[type="checkbox"][required]')
117
- requiredCheckboxes.forEach((checkbox: Element) => {
118
- const htmlCheckbox = checkbox as HTMLInputElement
119
- if (!htmlCheckbox.checked) {
120
- const label = document.querySelector(`label[for="${htmlCheckbox.id}"]`)
121
- const fieldName = label ? (label.textContent?.replace(' *', '') || 'Checkbox') : htmlCheckbox.name || htmlCheckbox.id || 'Checkbox'
122
- emptyFields.push(fieldName)
128
+ } else {
129
+ // Default behavior: check all required fields
130
+ const requiredInputs = stepElement.querySelectorAll('input[required], select[required], textarea[required]')
131
+ const emptyFields: string[] = []
132
+
133
+ requiredInputs.forEach((input: Element) => {
134
+ const htmlInput = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
135
+ if (!htmlInput.value || htmlInput.value.trim() === '') {
136
+ const label = document.querySelector(`label[for="${htmlInput.id}"]`)
137
+ const fieldName = label ? (label.textContent?.replace(' *', '') || 'Field') : htmlInput.name || htmlInput.id || 'Field'
138
+ emptyFields.push(fieldName)
139
+ }
140
+ })
141
+
142
+ // Check for required checkboxes
143
+ const requiredCheckboxes = stepElement.querySelectorAll('input[type="checkbox"][required]')
144
+ requiredCheckboxes.forEach((checkbox: Element) => {
145
+ const htmlCheckbox = checkbox as HTMLInputElement
146
+ if (!htmlCheckbox.checked) {
147
+ const label = document.querySelector(`label[for="${htmlCheckbox.id}"]`)
148
+ const fieldName = label ? (label.textContent?.replace(' *', '') || 'Checkbox') : htmlCheckbox.name || htmlCheckbox.id || 'Checkbox'
149
+ emptyFields.push(fieldName)
150
+ }
151
+ })
152
+
153
+ if (emptyFields.length > 0) {
154
+ setError(`Please fill in the following required fields: ${emptyFields.join(', ')}`)
155
+ return false
123
156
  }
124
- })
125
-
126
- if (emptyFields.length > 0) {
127
- setError(`Please fill in the following required fields: ${emptyFields.join(', ')}`)
128
- return false
129
157
  }
130
158
  }
131
159
  }
@@ -42,7 +42,10 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
42
42
  {orientation === 'horizontal' ? (
43
43
  <div className="relative w-full">
44
44
  {/* Steps Container with connecting lines */}
45
- <div className="relative flex items-start justify-between w-full">
45
+ <div className={cn(
46
+ "relative flex items-start justify-between w-full",
47
+ stepIconPosition === 'left' && "pl-14"
48
+ )}>
46
49
  {steps.map((step, index) => {
47
50
  const isActive = index === currentStep
48
51
  const isCompleted = isStepCompleted(index)
@@ -55,6 +58,34 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
55
58
 
56
59
  return (
57
60
  <div key={step.id} className="relative flex-1 flex flex-col items-center">
61
+ {/* Step Icon - Left Position */}
62
+ {stepIconPosition === 'left' && StepIcon && (
63
+ <div className="absolute -left-12 top-1/2 -translate-y-1/2 flex items-center justify-center">
64
+ <span className={cn(
65
+ "w-8 h-8 flex items-center justify-center transition-colors",
66
+ isActive && "text-primary",
67
+ isCompleted && !isActive && "text-primary",
68
+ !isActive && !isCompleted && "text-muted-foreground"
69
+ )}>
70
+ {StepIcon}
71
+ </span>
72
+ </div>
73
+ )}
74
+
75
+ {/* Step Icon - Top Position */}
76
+ {stepIconPosition === 'top' && StepIcon && (
77
+ <div className="flex items-center justify-center mb-2">
78
+ <span className={cn(
79
+ "w-8 h-8 flex items-center justify-center transition-colors",
80
+ isActive && "text-primary",
81
+ isCompleted && !isActive && "text-primary",
82
+ !isActive && !isCompleted && "text-muted-foreground"
83
+ )}>
84
+ {StepIcon}
85
+ </span>
86
+ </div>
87
+ )}
88
+
58
89
  {/* Step Title & Description - Above the circle */}
59
90
  {showStepTitles && (
60
91
  <div className="text-center mb-3 min-h-[40px] px-2">
@@ -119,7 +150,7 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
119
150
  completedStepIcon
120
151
  ) : isActive && activeStepIcon ? (
121
152
  activeStepIcon
122
- ) : StepIcon ? (
153
+ ) : StepIcon && stepIconPosition === 'inside' ? (
123
154
  <span className="w-5 h-5 flex items-center justify-center">{StepIcon}</span>
124
155
  ) : showStepNumbers ? (
125
156
  <span className="text-sm font-semibold">{index + 1}</span>
@@ -6,7 +6,8 @@ export interface WizardStep {
6
6
  description?: string
7
7
  icon?: ReactNode | ((props: { isActive: boolean; isCompleted: boolean }) => ReactNode)
8
8
  content: ReactNode | ((props: WizardStepContentProps) => ReactNode)
9
- validation?: () => boolean | Promise<boolean>
9
+ validation?: () => boolean | Promise<boolean> | { isValid: boolean; error?: string; errors?: string[] }
10
+ requiredFields?: string[] | boolean // Array of field names or boolean to enable/disable automatic validation
10
11
  isOptional?: boolean
11
12
  isDisabled?: boolean | ((currentStep: number, steps: WizardStep[]) => boolean)
12
13
  onEnter?: () => void | Promise<void>
@@ -462,6 +462,7 @@ function getMockGitHubData(
462
462
  full_name: `${username || "moonui"}/${repository || "awesome-project"}`,
463
463
  description: "An amazing open source project with great features",
464
464
  html_url: `https://github.com/${username || "moonui"}/${repository || "awesome-project"}`,
465
+ private: false,
465
466
  homepage: "https://awesome-project.dev",
466
467
  stargazers_count: 12453,
467
468
  watchers_count: 543,
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import React from "react"
2
4
  import { motion } from "framer-motion"
3
5
  import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
@@ -296,7 +298,7 @@ export const DetailedVariant: React.FC<BaseVariantProps & { onExport?: () => voi
296
298
  {stats.mostStarredRepo && (
297
299
  <Card
298
300
  className="hover:shadow-md transition-shadow cursor-pointer"
299
- onClick={() => window.open(`https://github.com/${stats.mostStarredRepo.owner?.login}/${stats.mostStarredRepo.name}`, '_blank')}
301
+ onClick={() => stats.mostStarredRepo && window.open(`https://github.com/${stats.mostStarredRepo.owner?.login}/${stats.mostStarredRepo.name}`, '_blank')}
300
302
  >
301
303
  <CardContent className="p-6">
302
304
  <div className="flex items-center justify-between">
@@ -233,7 +233,7 @@ const HealthCheckInternal: React.FC<HealthCheckProps> = ({
233
233
 
234
234
  return (
235
235
  <Card className={cn("w-full", className)}>
236
- <CardHeader>
236
+ <CardHeader className="p-6">
237
237
  <div className="flex items-center justify-between">
238
238
  <div className="flex items-center gap-3">
239
239
  <div className="flex items-center gap-2">
@@ -266,7 +266,7 @@ const HealthCheckInternal: React.FC<HealthCheckProps> = ({
266
266
  {showUptime && (
267
267
  <div className="text-right">
268
268
  <div className="text-sm font-medium">{overallUptime.toFixed(1)}%</div>
269
- <div className="text-xs text-muted-foreground">Uptime</div>
269
+ <div className="text-xs text-muted-foreground mt-0.5">Uptime</div>
270
270
  </div>
271
271
  )}
272
272
 
@@ -282,19 +282,19 @@ const HealthCheckInternal: React.FC<HealthCheckProps> = ({
282
282
  </div>
283
283
 
284
284
  {/* Overall Stats */}
285
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
286
- <div className="text-center">
285
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-6">
286
+ <div className="p-6 text-center bg-muted/10 rounded-lg">
287
287
  <div className="text-2xl font-bold text-green-500">{healthyCount}</div>
288
- <div className="text-xs text-muted-foreground">Healthy</div>
288
+ <div className="text-xs text-muted-foreground mt-1">Healthy</div>
289
289
  </div>
290
- <div className="text-center">
290
+ <div className="p-6 text-center bg-muted/10 rounded-lg">
291
291
  <div className="text-2xl font-bold text-red-500">
292
292
  {results.filter(r => r.status === "unhealthy").length}
293
293
  </div>
294
- <div className="text-xs text-muted-foreground">Unhealthy</div>
294
+ <div className="text-xs text-muted-foreground mt-1">Unhealthy</div>
295
295
  </div>
296
296
  {showResponseTime && (
297
- <div className="text-center">
297
+ <div className="p-6 text-center bg-muted/10 rounded-lg">
298
298
  <div className="text-lg font-bold">
299
299
  {results.length > 0
300
300
  ? formatResponseTime(
@@ -303,17 +303,17 @@ const HealthCheckInternal: React.FC<HealthCheckProps> = ({
303
303
  : "0ms"
304
304
  }
305
305
  </div>
306
- <div className="text-xs text-muted-foreground">Avg Response</div>
306
+ <div className="text-xs text-muted-foreground mt-1">Avg Response</div>
307
307
  </div>
308
308
  )}
309
- <div className="text-center">
309
+ <div className="p-6 text-center bg-muted/10 rounded-lg">
310
310
  <div className="text-lg font-bold">{results.length}</div>
311
- <div className="text-xs text-muted-foreground">Services</div>
311
+ <div className="text-xs text-muted-foreground mt-1">Services</div>
312
312
  </div>
313
313
  </div>
314
314
  </CardHeader>
315
315
 
316
- <CardContent>
316
+ <CardContent className="p-6 pt-0">
317
317
  <div className="space-y-4">
318
318
  <AnimatePresence>
319
319
  {results.map((result, index) => (
@@ -348,7 +348,7 @@ const HealthCheckInternal: React.FC<HealthCheckProps> = ({
348
348
  <div className="font-medium">
349
349
  {formatResponseTime(result.responseTime)}
350
350
  </div>
351
- <div className="text-xs text-muted-foreground">Response</div>
351
+ <div className="text-xs text-muted-foreground mt-0.5">Response</div>
352
352
  </div>
353
353
  )}
354
354
 
@@ -357,7 +357,7 @@ const HealthCheckInternal: React.FC<HealthCheckProps> = ({
357
357
  <div className="font-medium">
358
358
  {calculateUptime(result.id).toFixed(1)}%
359
359
  </div>
360
- <div className="text-xs text-muted-foreground">Uptime</div>
360
+ <div className="text-xs text-muted-foreground mt-0.5">Uptime</div>
361
361
  </div>
362
362
  )}
363
363
  </div>
@@ -10,7 +10,7 @@ import { useSubscription } from "../../hooks/use-subscription"
10
10
  import { cva, type VariantProps } from "class-variance-authority"
11
11
 
12
12
  const hoverCard3DVariants = cva(
13
- "relative rounded-lg border bg-card text-card-foreground shadow-sm transition-all duration-200",
13
+ "relative rounded-lg border bg-card text-card-foreground shadow-sm transition-all duration-200 isolate",
14
14
  {
15
15
  variants: {
16
16
  variant: {
@@ -475,10 +475,9 @@ const HoverCard3DInternal = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
475
475
  {/* Glow effect */}
476
476
  {glowEffect !== 'none' && (
477
477
  <motion.div
478
- className="absolute inset-0 rounded-lg pointer-events-none"
478
+ className="absolute inset-0 rounded-lg pointer-events-none -z-10"
479
479
  animate={glowStyle}
480
480
  transition={{ duration: 0.3 * (1 / animationSpeed) }}
481
- style={{ zIndex: -1 }}
482
481
  />
483
482
  )}
484
483
  </motion.div>