@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.
- package/dist/index.d.ts +691 -261
- package/dist/index.mjs +7418 -4934
- 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 +12 -12
- 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
|
@@ -348,8 +348,8 @@ const PerformanceDebuggerInternal: React.FC<PerformanceDebuggerProps> = ({
|
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
return (
|
|
351
|
-
<Card className={cn("w-full max-w-
|
|
352
|
-
<CardHeader>
|
|
351
|
+
<Card className={cn("w-full max-w-6xl", className)}>
|
|
352
|
+
<CardHeader className="pb-6">
|
|
353
353
|
<div className="flex items-center justify-between">
|
|
354
354
|
<div>
|
|
355
355
|
<CardTitle className="flex items-center gap-2">
|
|
@@ -362,48 +362,72 @@ const PerformanceDebuggerInternal: React.FC<PerformanceDebuggerProps> = ({
|
|
|
362
362
|
</div>
|
|
363
363
|
|
|
364
364
|
<div className="flex items-center gap-2">
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
<Button
|
|
379
|
-
variant="outline"
|
|
380
|
-
size="sm"
|
|
381
|
-
onClick={capture}
|
|
382
|
-
>
|
|
383
|
-
<RefreshCw className="h-4 w-4" />
|
|
384
|
-
</Button>
|
|
385
|
-
|
|
386
|
-
<Button
|
|
387
|
-
variant="outline"
|
|
388
|
-
size="sm"
|
|
389
|
-
onClick={exportData}
|
|
390
|
-
>
|
|
391
|
-
<Download className="h-4 w-4" />
|
|
392
|
-
</Button>
|
|
365
|
+
<div className={cn(
|
|
366
|
+
"inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium",
|
|
367
|
+
isCapturing
|
|
368
|
+
? "border border-border bg-background text-foreground"
|
|
369
|
+
: "bg-secondary text-secondary-foreground"
|
|
370
|
+
)}>
|
|
371
|
+
{isCapturing ? (
|
|
372
|
+
<Activity className="h-3 w-3 animate-pulse" />
|
|
373
|
+
) : (
|
|
374
|
+
<Clock className="h-3 w-3" />
|
|
375
|
+
)}
|
|
376
|
+
<span>{isCapturing ? "Live" : "Paused"}</span>
|
|
377
|
+
</div>
|
|
393
378
|
|
|
394
|
-
<
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
379
|
+
<div className="flex items-center gap-1">
|
|
380
|
+
<Button
|
|
381
|
+
variant="outline"
|
|
382
|
+
size="icon"
|
|
383
|
+
className="h-7 w-7"
|
|
384
|
+
onClick={() => setIsCapturing(!isCapturing)}
|
|
385
|
+
>
|
|
386
|
+
{isCapturing ? (
|
|
387
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
388
|
+
<rect x="6" y="4" width="4" height="16"></rect>
|
|
389
|
+
<rect x="14" y="4" width="4" height="16"></rect>
|
|
390
|
+
</svg>
|
|
391
|
+
) : (
|
|
392
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
393
|
+
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
|
394
|
+
</svg>
|
|
395
|
+
)}
|
|
396
|
+
</Button>
|
|
397
|
+
|
|
398
|
+
<Button
|
|
399
|
+
variant="outline"
|
|
400
|
+
size="icon"
|
|
401
|
+
className="h-7 w-7"
|
|
402
|
+
onClick={capture}
|
|
403
|
+
>
|
|
404
|
+
<RefreshCw className="h-3.5 w-3.5" />
|
|
405
|
+
</Button>
|
|
406
|
+
|
|
407
|
+
<Button
|
|
408
|
+
variant="outline"
|
|
409
|
+
size="icon"
|
|
410
|
+
className="h-7 w-7"
|
|
411
|
+
onClick={exportData}
|
|
412
|
+
>
|
|
413
|
+
<Download className="h-3.5 w-3.5" />
|
|
414
|
+
</Button>
|
|
415
|
+
|
|
416
|
+
<Button
|
|
417
|
+
variant="ghost"
|
|
418
|
+
size="icon"
|
|
419
|
+
className="h-7 w-7"
|
|
420
|
+
onClick={() => setIsVisible(false)}
|
|
421
|
+
>
|
|
422
|
+
<EyeOff className="h-3.5 w-3.5" />
|
|
423
|
+
</Button>
|
|
424
|
+
</div>
|
|
401
425
|
</div>
|
|
402
426
|
</div>
|
|
403
427
|
</CardHeader>
|
|
404
428
|
|
|
405
429
|
<CardContent>
|
|
406
|
-
<Tabs defaultValue="current" className="w-full">
|
|
430
|
+
<Tabs defaultValue="current" className="w-full mt-2">
|
|
407
431
|
<TabsList className="grid w-full grid-cols-3">
|
|
408
432
|
<TabsTrigger value="current">Current Metrics</TabsTrigger>
|
|
409
433
|
<TabsTrigger value="history">History</TabsTrigger>
|
|
@@ -351,7 +351,7 @@ const PerformanceMonitorInternal: React.FC<PerformanceMonitorProps> = ({
|
|
|
351
351
|
|
|
352
352
|
return (
|
|
353
353
|
<Card className={cn("w-full max-w-6xl", className)}>
|
|
354
|
-
<CardHeader>
|
|
354
|
+
<CardHeader className="pb-6">
|
|
355
355
|
<div className="flex items-center justify-between">
|
|
356
356
|
<div>
|
|
357
357
|
<CardTitle className="flex items-center gap-2">
|
|
@@ -364,48 +364,62 @@ const PerformanceMonitorInternal: React.FC<PerformanceMonitorProps> = ({
|
|
|
364
364
|
</div>
|
|
365
365
|
|
|
366
366
|
<div className="flex items-center gap-2">
|
|
367
|
-
<
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
367
|
+
<div className={cn(
|
|
368
|
+
"inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium",
|
|
369
|
+
autoRefresh
|
|
370
|
+
? "border border-border bg-background text-foreground"
|
|
371
|
+
: "bg-secondary text-secondary-foreground"
|
|
372
|
+
)}>
|
|
373
|
+
{autoRefresh ? (
|
|
374
|
+
<Activity className="h-3 w-3 animate-pulse" />
|
|
375
|
+
) : (
|
|
376
|
+
<Timer className="h-3 w-3" />
|
|
377
|
+
)}
|
|
378
|
+
<span>{autoRefresh ? "Live" : "Paused"}</span>
|
|
379
|
+
</div>
|
|
371
380
|
|
|
372
381
|
{showAlerts && alerts.length > 0 && (
|
|
373
|
-
<
|
|
382
|
+
<div className="inline-flex items-center gap-1 rounded-md bg-destructive px-2 py-0.5 text-xs font-medium text-destructive-foreground">
|
|
374
383
|
<AlertTriangle className="h-3 w-3" />
|
|
375
|
-
{alerts.length}
|
|
376
|
-
</
|
|
384
|
+
<span>{alerts.length}</span>
|
|
385
|
+
</div>
|
|
377
386
|
)}
|
|
378
387
|
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
388
|
+
<div className="flex items-center gap-1">
|
|
389
|
+
<Button
|
|
390
|
+
variant="outline"
|
|
391
|
+
size="icon"
|
|
392
|
+
className="h-7 w-7"
|
|
393
|
+
onClick={updateMetrics}
|
|
394
|
+
disabled={isRefreshing}
|
|
395
|
+
>
|
|
396
|
+
<RefreshCw className={cn("h-3.5 w-3.5", isRefreshing && "animate-spin")} />
|
|
397
|
+
</Button>
|
|
398
|
+
|
|
399
|
+
<Button
|
|
400
|
+
variant="outline"
|
|
401
|
+
size="icon"
|
|
402
|
+
className="h-7 w-7"
|
|
403
|
+
onClick={exportMetrics}
|
|
404
|
+
>
|
|
405
|
+
<Download className="h-3.5 w-3.5" />
|
|
406
|
+
</Button>
|
|
407
|
+
|
|
408
|
+
<Button
|
|
409
|
+
variant="ghost"
|
|
410
|
+
size="icon"
|
|
411
|
+
className="h-7 w-7"
|
|
412
|
+
onClick={() => setIsVisible(false)}
|
|
413
|
+
>
|
|
414
|
+
<EyeOff className="h-3.5 w-3.5" />
|
|
415
|
+
</Button>
|
|
416
|
+
</div>
|
|
403
417
|
</div>
|
|
404
418
|
</div>
|
|
405
419
|
</CardHeader>
|
|
406
420
|
|
|
407
421
|
<CardContent>
|
|
408
|
-
<Tabs defaultValue="overview" className="w-full">
|
|
422
|
+
<Tabs defaultValue="overview" className="w-full mt-2">
|
|
409
423
|
<TabsList className="grid w-full grid-cols-4">
|
|
410
424
|
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
411
425
|
<TabsTrigger value="details">Details</TabsTrigger>
|
|
@@ -100,8 +100,10 @@ function toInternationalFormat(countryCode: string, phoneNumber: string): string
|
|
|
100
100
|
return `${country.dialCode}${cleaned}`
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
const defaultValue = { country: 'US', number: '' }
|
|
104
|
+
|
|
103
105
|
export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneNumberInputProps>(({
|
|
104
|
-
value
|
|
106
|
+
value,
|
|
105
107
|
onChange,
|
|
106
108
|
defaultCountry = 'US',
|
|
107
109
|
countries: customCountries,
|
|
@@ -123,31 +125,31 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
123
125
|
allowInternationalFormat = true,
|
|
124
126
|
...props
|
|
125
127
|
}, ref) => {
|
|
126
|
-
|
|
128
|
+
// Stable default value
|
|
129
|
+
const phoneValue = React.useMemo(() => {
|
|
130
|
+
return value || defaultValue
|
|
131
|
+
}, [value])
|
|
127
132
|
const [isFocused, setIsFocused] = useState(false)
|
|
128
133
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
129
134
|
|
|
130
135
|
const countryList = customCountries || countries
|
|
131
|
-
const selectedCountry = countryList.find(c => c.code ===
|
|
136
|
+
const selectedCountry = countryList.find(c => c.code === phoneValue.country) || countryList[0]
|
|
132
137
|
|
|
133
|
-
// Doğrulama
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
setIsValid(valid)
|
|
138
|
-
}
|
|
139
|
-
}, [value, validateOnChange])
|
|
138
|
+
// Doğrulama - useEffect yerine doğrudan hesapla
|
|
139
|
+
const isValid = validateOnChange && phoneValue.number
|
|
140
|
+
? validatePhoneNumber(phoneValue.number, phoneValue.country)
|
|
141
|
+
: false
|
|
140
142
|
|
|
141
143
|
const handleCountryChange = (countryCode: string) => {
|
|
142
144
|
const fullNumber = allowInternationalFormat
|
|
143
|
-
? toInternationalFormat(countryCode,
|
|
144
|
-
:
|
|
145
|
+
? toInternationalFormat(countryCode, phoneValue.number)
|
|
146
|
+
: phoneValue.number
|
|
145
147
|
|
|
146
148
|
onChange?.({
|
|
147
149
|
country: countryCode,
|
|
148
|
-
number:
|
|
150
|
+
number: phoneValue.number,
|
|
149
151
|
fullNumber,
|
|
150
|
-
isValid: validatePhoneNumber(
|
|
152
|
+
isValid: validatePhoneNumber(phoneValue.number, countryCode)
|
|
151
153
|
})
|
|
152
154
|
}
|
|
153
155
|
|
|
@@ -161,13 +163,13 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
161
163
|
newValue = formatPhoneNumber(cleaned, selectedCountry.format)
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
const valid = validatePhoneNumber(newValue,
|
|
166
|
+
const valid = validatePhoneNumber(newValue, phoneValue.country)
|
|
165
167
|
const fullNumber = allowInternationalFormat
|
|
166
|
-
? toInternationalFormat(
|
|
168
|
+
? toInternationalFormat(phoneValue.country, newValue)
|
|
167
169
|
: newValue
|
|
168
170
|
|
|
169
171
|
onChange?.({
|
|
170
|
-
country:
|
|
172
|
+
country: phoneValue.country,
|
|
171
173
|
number: newValue,
|
|
172
174
|
fullNumber,
|
|
173
175
|
isValid: valid
|
|
@@ -182,13 +184,13 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
182
184
|
const cleaned = pastedText.replace(/\D/g, '')
|
|
183
185
|
const formatted = formatPhoneNumber(cleaned, selectedCountry.format)
|
|
184
186
|
|
|
185
|
-
const valid = validatePhoneNumber(formatted,
|
|
187
|
+
const valid = validatePhoneNumber(formatted, phoneValue.country)
|
|
186
188
|
const fullNumber = allowInternationalFormat
|
|
187
|
-
? toInternationalFormat(
|
|
189
|
+
? toInternationalFormat(phoneValue.country, formatted)
|
|
188
190
|
: formatted
|
|
189
191
|
|
|
190
192
|
onChange?.({
|
|
191
|
-
country:
|
|
193
|
+
country: phoneValue.country,
|
|
192
194
|
number: formatted,
|
|
193
195
|
fullNumber,
|
|
194
196
|
isValid: valid
|
|
@@ -196,7 +198,7 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
return (
|
|
199
|
-
<div ref={ref} className={cn("space-y-2", className)}
|
|
201
|
+
<div ref={ref} className={cn("space-y-2", className)}>
|
|
200
202
|
{label && (
|
|
201
203
|
<Label htmlFor="phone-number" className={labelClassName}>
|
|
202
204
|
{label}
|
|
@@ -207,7 +209,7 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
207
209
|
<div className="flex gap-2">
|
|
208
210
|
{/* Ülke Seçici */}
|
|
209
211
|
<Select
|
|
210
|
-
value={
|
|
212
|
+
value={phoneValue.country}
|
|
211
213
|
onValueChange={handleCountryChange}
|
|
212
214
|
disabled={disabled}
|
|
213
215
|
>
|
|
@@ -245,7 +247,7 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
245
247
|
ref={inputRef}
|
|
246
248
|
id="phone-number"
|
|
247
249
|
type="tel"
|
|
248
|
-
value={
|
|
250
|
+
value={phoneValue.number}
|
|
249
251
|
onChange={handleNumberChange}
|
|
250
252
|
onPaste={handlePaste}
|
|
251
253
|
onFocus={() => setIsFocused(true)}
|
|
@@ -260,7 +262,7 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
260
262
|
/>
|
|
261
263
|
|
|
262
264
|
{/* Doğrulama İkonu */}
|
|
263
|
-
{showValidationIcon &&
|
|
265
|
+
{showValidationIcon && phoneValue.number && (
|
|
264
266
|
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
|
265
267
|
<AnimatePresence mode="wait">
|
|
266
268
|
{isValid ? (
|
|
@@ -318,14 +320,14 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
318
320
|
)}
|
|
319
321
|
|
|
320
322
|
{/* Uluslararası Format Gösterimi */}
|
|
321
|
-
{allowInternationalFormat &&
|
|
323
|
+
{allowInternationalFormat && phoneValue.number && isValid && (
|
|
322
324
|
<motion.div
|
|
323
325
|
initial={{ opacity: 0, y: -10 }}
|
|
324
326
|
animate={{ opacity: 1, y: 0 }}
|
|
325
327
|
className="flex items-center gap-2 text-sm text-muted-foreground"
|
|
326
328
|
>
|
|
327
329
|
<Globe className="w-4 h-4" />
|
|
328
|
-
<span>International: {toInternationalFormat(
|
|
330
|
+
<span>International: {toInternationalFormat(phoneValue.country, phoneValue.number)}</span>
|
|
329
331
|
</motion.div>
|
|
330
332
|
)}
|
|
331
333
|
</div>
|
|
@@ -335,4 +337,7 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
335
337
|
MoonUIPhoneNumberInputPro.displayName = "MoonUIPhoneNumberInputPro"
|
|
336
338
|
|
|
337
339
|
// Ülke listesini de export edelim
|
|
338
|
-
export { countries as phoneCountries }
|
|
340
|
+
export { countries as phoneCountries }
|
|
341
|
+
|
|
342
|
+
// Basit versiyon
|
|
343
|
+
export { MoonUIPhoneNumberInputSimple } from "./phone-number-input-simple"
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useState, useRef } from "react"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
import { Input } from "../ui/input"
|
|
6
|
+
import { Label } from "../ui/label"
|
|
7
|
+
import { Phone, Check, X } from "lucide-react"
|
|
8
|
+
import { motion, AnimatePresence } from "framer-motion"
|
|
9
|
+
|
|
10
|
+
export interface PhoneNumberInputSimpleProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value' | 'size'> {
|
|
11
|
+
value?: string
|
|
12
|
+
onChange?: (value: string) => void
|
|
13
|
+
label?: string
|
|
14
|
+
error?: string
|
|
15
|
+
helperText?: string
|
|
16
|
+
required?: boolean
|
|
17
|
+
showValidationIcon?: boolean
|
|
18
|
+
inputClassName?: string
|
|
19
|
+
labelClassName?: string
|
|
20
|
+
errorClassName?: string
|
|
21
|
+
size?: 'sm' | 'md' | 'lg'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Basit telefon numarası doğrulama
|
|
25
|
+
function validatePhoneNumber(number: string): boolean {
|
|
26
|
+
if (!number) return false
|
|
27
|
+
const cleaned = number.replace(/\D/g, '')
|
|
28
|
+
return cleaned.length >= 10 && cleaned.length <= 15
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Telefon numarası formatlama
|
|
32
|
+
function formatPhoneNumber(number: string): string {
|
|
33
|
+
const cleaned = number.replace(/\D/g, '')
|
|
34
|
+
|
|
35
|
+
// Türkiye formatı için basit formatlama
|
|
36
|
+
if (cleaned.length <= 3) {
|
|
37
|
+
return cleaned
|
|
38
|
+
} else if (cleaned.length <= 6) {
|
|
39
|
+
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3)}`
|
|
40
|
+
} else if (cleaned.length <= 10) {
|
|
41
|
+
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)} ${cleaned.slice(6)}`
|
|
42
|
+
} else {
|
|
43
|
+
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)} ${cleaned.slice(6, 8)} ${cleaned.slice(8, 10)}`
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const MoonUIPhoneNumberInputSimple = React.forwardRef<HTMLInputElement, PhoneNumberInputSimpleProps>(({
|
|
48
|
+
value = '',
|
|
49
|
+
onChange,
|
|
50
|
+
label,
|
|
51
|
+
error,
|
|
52
|
+
helperText,
|
|
53
|
+
required,
|
|
54
|
+
showValidationIcon = true,
|
|
55
|
+
className,
|
|
56
|
+
inputClassName,
|
|
57
|
+
labelClassName,
|
|
58
|
+
errorClassName,
|
|
59
|
+
disabled,
|
|
60
|
+
placeholder = "(555) 123 45 67",
|
|
61
|
+
size,
|
|
62
|
+
...props
|
|
63
|
+
}, ref) => {
|
|
64
|
+
const [isFocused, setIsFocused] = useState(false)
|
|
65
|
+
const [localValue, setLocalValue] = useState(value)
|
|
66
|
+
const isValid = validatePhoneNumber(localValue)
|
|
67
|
+
|
|
68
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
69
|
+
const newValue = e.target.value
|
|
70
|
+
const formattedValue = formatPhoneNumber(newValue)
|
|
71
|
+
|
|
72
|
+
setLocalValue(formattedValue)
|
|
73
|
+
onChange?.(formattedValue)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className={cn("space-y-2", className)}>
|
|
78
|
+
{label && (
|
|
79
|
+
<Label htmlFor={props.id} className={labelClassName}>
|
|
80
|
+
{label}
|
|
81
|
+
{required && <span className="text-destructive ml-1">*</span>}
|
|
82
|
+
</Label>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
<div className="relative">
|
|
86
|
+
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
|
|
87
|
+
<Phone className="h-4 w-4" />
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<Input
|
|
91
|
+
ref={ref}
|
|
92
|
+
type="tel"
|
|
93
|
+
value={localValue}
|
|
94
|
+
onChange={handleChange}
|
|
95
|
+
onFocus={() => setIsFocused(true)}
|
|
96
|
+
onBlur={() => setIsFocused(false)}
|
|
97
|
+
placeholder={placeholder}
|
|
98
|
+
className={cn(
|
|
99
|
+
"pl-10 pr-10",
|
|
100
|
+
error && "border-destructive",
|
|
101
|
+
inputClassName
|
|
102
|
+
)}
|
|
103
|
+
disabled={disabled}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
{/* Doğrulama İkonu */}
|
|
108
|
+
{showValidationIcon && localValue && (
|
|
109
|
+
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
|
110
|
+
<AnimatePresence mode="wait">
|
|
111
|
+
{isValid ? (
|
|
112
|
+
<motion.div
|
|
113
|
+
key="valid"
|
|
114
|
+
initial={{ scale: 0, opacity: 0 }}
|
|
115
|
+
animate={{ scale: 1, opacity: 1 }}
|
|
116
|
+
exit={{ scale: 0, opacity: 0 }}
|
|
117
|
+
transition={{ duration: 0.2 }}
|
|
118
|
+
>
|
|
119
|
+
<Check className="w-4 h-4 text-green-500" />
|
|
120
|
+
</motion.div>
|
|
121
|
+
) : (
|
|
122
|
+
<motion.div
|
|
123
|
+
key="invalid"
|
|
124
|
+
initial={{ scale: 0, opacity: 0 }}
|
|
125
|
+
animate={{ scale: 1, opacity: 1 }}
|
|
126
|
+
exit={{ scale: 0, opacity: 0 }}
|
|
127
|
+
transition={{ duration: 0.2 }}
|
|
128
|
+
>
|
|
129
|
+
<X className="w-4 h-4 text-destructive" />
|
|
130
|
+
</motion.div>
|
|
131
|
+
)}
|
|
132
|
+
</AnimatePresence>
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{/* Helper Text veya Error */}
|
|
138
|
+
{(error || helperText) && (
|
|
139
|
+
<AnimatePresence mode="wait">
|
|
140
|
+
{error ? (
|
|
141
|
+
<motion.p
|
|
142
|
+
key="error"
|
|
143
|
+
initial={{ opacity: 0, height: 0 }}
|
|
144
|
+
animate={{ opacity: 1, height: 'auto' }}
|
|
145
|
+
exit={{ opacity: 0, height: 0 }}
|
|
146
|
+
className={cn("text-sm text-destructive", errorClassName)}
|
|
147
|
+
>
|
|
148
|
+
{error}
|
|
149
|
+
</motion.p>
|
|
150
|
+
) : (
|
|
151
|
+
<motion.p
|
|
152
|
+
key="helper"
|
|
153
|
+
initial={{ opacity: 0, height: 0 }}
|
|
154
|
+
animate={{ opacity: 1, height: 'auto' }}
|
|
155
|
+
exit={{ opacity: 0, height: 0 }}
|
|
156
|
+
className="text-sm text-muted-foreground"
|
|
157
|
+
>
|
|
158
|
+
{helperText}
|
|
159
|
+
</motion.p>
|
|
160
|
+
)}
|
|
161
|
+
</AnimatePresence>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
MoonUIPhoneNumberInputSimple.displayName = "MoonUIPhoneNumberInputSimple"
|