@moontra/moonui-pro 2.20.0 → 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.
- package/dist/index.d.ts +691 -261
- package/dist/index.mjs +7419 -4935
- package/package.json +4 -3
- package/scripts/postbuild.js +27 -0
- package/src/components/advanced-chart/index.tsx +5 -1
- package/src/components/advanced-forms/index.tsx +175 -16
- package/src/components/calendar/event-dialog.tsx +18 -13
- package/src/components/calendar/index.tsx +197 -50
- package/src/components/dashboard/dashboard-grid.tsx +21 -3
- package/src/components/dashboard/types.ts +3 -0
- package/src/components/dashboard/widgets/activity-feed.tsx +6 -1
- package/src/components/dashboard/widgets/comparison-widget.tsx +177 -0
- package/src/components/dashboard/widgets/index.ts +5 -0
- package/src/components/dashboard/widgets/metric-card.tsx +21 -1
- package/src/components/dashboard/widgets/progress-widget.tsx +113 -0
- package/src/components/error-boundary/index.tsx +160 -37
- package/src/components/form-wizard/form-wizard-context.tsx +54 -26
- package/src/components/form-wizard/form-wizard-progress.tsx +33 -2
- package/src/components/form-wizard/types.ts +2 -1
- package/src/components/github-stars/hooks.ts +1 -0
- package/src/components/github-stars/variants.tsx +3 -1
- package/src/components/health-check/index.tsx +14 -14
- package/src/components/hover-card-3d/index.tsx +2 -3
- package/src/components/index.ts +5 -3
- package/src/components/kanban/kanban.tsx +23 -18
- package/src/components/license-error/index.tsx +2 -0
- package/src/components/magnetic-button/index.tsx +56 -7
- package/src/components/memory-efficient-data/index.tsx +117 -115
- package/src/components/navbar/index.tsx +781 -0
- package/src/components/performance-debugger/index.tsx +62 -38
- package/src/components/performance-monitor/index.tsx +47 -33
- package/src/components/phone-number-input/index.tsx +32 -27
- package/src/components/phone-number-input/phone-number-input-simple.tsx +167 -0
- package/src/components/rich-text-editor/index.tsx +26 -28
- package/src/components/rich-text-editor/slash-commands-extension.ts +15 -5
- package/src/components/sidebar/index.tsx +32 -13
- package/src/components/timeline/index.tsx +84 -49
- package/src/components/ui/accordion.tsx +550 -42
- package/src/components/ui/avatar.tsx +2 -0
- package/src/components/ui/badge.tsx +2 -0
- package/src/components/ui/breadcrumb.tsx +2 -0
- package/src/components/ui/button.tsx +39 -33
- package/src/components/ui/card.tsx +2 -0
- package/src/components/ui/collapsible.tsx +546 -50
- package/src/components/ui/command.tsx +790 -67
- package/src/components/ui/dialog.tsx +510 -92
- package/src/components/ui/dropdown-menu.tsx +540 -52
- package/src/components/ui/index.ts +37 -5
- package/src/components/ui/input.tsx +2 -0
- package/src/components/ui/magnetic-button.tsx +1 -1
- package/src/components/ui/media-gallery.tsx +1 -2
- package/src/components/ui/navigation-menu.tsx +130 -0
- package/src/components/ui/pagination.tsx +2 -0
- package/src/components/ui/select.tsx +6 -2
- package/src/components/ui/spotlight-card.tsx +1 -1
- package/src/components/ui/table.tsx +2 -0
- package/src/components/ui/tabs-pro.tsx +542 -0
- package/src/components/ui/tabs.tsx +23 -167
- package/src/components/ui/toggle.tsx +13 -13
- package/src/index.ts +11 -3
- package/src/styles/index.css +596 -0
- package/src/use-performance-optimizer.ts +1 -1
- package/src/utils/chart-helpers.ts +1 -1
- package/src/__tests__/use-intersection-observer.test.tsx +0 -216
- package/src/__tests__/use-local-storage.test.tsx +0 -174
- package/src/__tests__/use-pro-access.test.tsx +0 -183
- package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
- package/src/components/data-table/data-table.test.tsx +0 -187
- package/src/components/enhanced/badge.tsx +0 -191
- package/src/components/enhanced/button.tsx +0 -362
- package/src/components/enhanced/card.tsx +0 -266
- package/src/components/enhanced/dialog.tsx +0 -246
- package/src/components/enhanced/index.ts +0 -4
- package/src/components/file-upload/file-upload.test.tsx +0 -243
- package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
- 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
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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=
|
|
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-
|
|
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>
|