@moontra/moonui 3.0.0 → 3.1.0

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.
@@ -0,0 +1,140 @@
1
+ "use client"
2
+
3
+ import * as React from "react";
4
+ import { OTPInput, OTPInputContext } from "input-otp";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { Dot } from "lucide-react";
7
+
8
+ import { cn } from "../../lib/utils";
9
+
10
+ /**
11
+ * Premium InputOTP Component
12
+ *
13
+ * input-otp kütüphanesi tabanlı, erişilebilir tek kullanımlık şifre (OTP) girişi.
14
+ * Paste desteği, pattern doğrulama ve aktif slot vurgusu sunar.
15
+ */
16
+
17
+ // Pattern sabitleri — kullanım kolaylığı için input-otp'den yeniden export edilir
18
+ export {
19
+ REGEXP_ONLY_DIGITS,
20
+ REGEXP_ONLY_CHARS,
21
+ REGEXP_ONLY_DIGITS_AND_CHARS,
22
+ } from "input-otp";
23
+
24
+ /* -------------------------------------------------------------------------------------------------
25
+ * InputOTP Root
26
+ * -----------------------------------------------------------------------------------------------*/
27
+ export type InputOTPProps = React.ComponentPropsWithoutRef<typeof OTPInput>;
28
+
29
+ const InputOTP = React.forwardRef<
30
+ React.ElementRef<typeof OTPInput>,
31
+ InputOTPProps
32
+ >(({ className, containerClassName, ...props }, ref) => (
33
+ <OTPInput
34
+ ref={ref}
35
+ containerClassName={cn(
36
+ "moonui-theme",
37
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
38
+ containerClassName
39
+ )}
40
+ className={cn("disabled:cursor-not-allowed", className)}
41
+ {...props}
42
+ />
43
+ ));
44
+ InputOTP.displayName = "InputOTP";
45
+
46
+ /* -------------------------------------------------------------------------------------------------
47
+ * InputOTPGroup
48
+ * -----------------------------------------------------------------------------------------------*/
49
+ const InputOTPGroup = React.forwardRef<
50
+ HTMLDivElement,
51
+ React.HTMLAttributes<HTMLDivElement>
52
+ >(({ className, ...props }, ref) => (
53
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
54
+ ));
55
+ InputOTPGroup.displayName = "InputOTPGroup";
56
+
57
+ /* -------------------------------------------------------------------------------------------------
58
+ * InputOTPSlot
59
+ * -----------------------------------------------------------------------------------------------*/
60
+ const inputOTPSlotVariants = cva(
61
+ [
62
+ "relative flex h-10 w-10 items-center justify-center",
63
+ "border-y border-r border-input text-sm text-foreground",
64
+ "transition-all duration-200",
65
+ "first:rounded-l-md first:border-l last:rounded-r-md",
66
+ ],
67
+ {
68
+ variants: {
69
+ // Aktif slot vurgusu — token tabanlı ring stili
70
+ isActive: {
71
+ true: "z-10 ring-2 ring-ring ring-offset-background",
72
+ false: "",
73
+ },
74
+ },
75
+ defaultVariants: {
76
+ isActive: false,
77
+ },
78
+ }
79
+ );
80
+
81
+ export interface InputOTPSlotProps
82
+ extends React.HTMLAttributes<HTMLDivElement>,
83
+ Omit<VariantProps<typeof inputOTPSlotVariants>, "isActive"> {
84
+ /** Bu slotun temsil ettiği karakter index'i */
85
+ index: number;
86
+ }
87
+
88
+ const InputOTPSlot = React.forwardRef<HTMLDivElement, InputOTPSlotProps>(
89
+ ({ index, className, ...props }, ref) => {
90
+ const inputOTPContext = React.useContext(OTPInputContext);
91
+ const slot = inputOTPContext?.slots?.[index];
92
+ const char = slot?.char;
93
+ const hasFakeCaret = slot?.hasFakeCaret;
94
+ const isActive = slot?.isActive;
95
+
96
+ return (
97
+ <div
98
+ ref={ref}
99
+ data-active={isActive ? "" : undefined}
100
+ className={cn(inputOTPSlotVariants({ isActive: !!isActive }), className)}
101
+ {...props}
102
+ >
103
+ {char}
104
+ {hasFakeCaret && (
105
+ // Sahte imleç — gerçek input görünmez olduğu için aktif slotta yanıp söner
106
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
107
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
108
+ </div>
109
+ )}
110
+ </div>
111
+ );
112
+ }
113
+ );
114
+ InputOTPSlot.displayName = "InputOTPSlot";
115
+
116
+ /* -------------------------------------------------------------------------------------------------
117
+ * InputOTPSeparator
118
+ * -----------------------------------------------------------------------------------------------*/
119
+ const InputOTPSeparator = React.forwardRef<
120
+ HTMLDivElement,
121
+ React.HTMLAttributes<HTMLDivElement>
122
+ >(({ className, ...props }, ref) => (
123
+ <div
124
+ ref={ref}
125
+ role="separator"
126
+ className={cn("text-muted-foreground", className)}
127
+ {...props}
128
+ >
129
+ <Dot aria-hidden="true" />
130
+ </div>
131
+ ));
132
+ InputOTPSeparator.displayName = "InputOTPSeparator";
133
+
134
+ export {
135
+ InputOTP,
136
+ InputOTPGroup,
137
+ InputOTPSlot,
138
+ InputOTPSeparator,
139
+ inputOTPSlotVariants,
140
+ };
@@ -133,6 +133,7 @@ module.exports = {
133
133
  "accordion-up": "accordion-up 0.2s ease-out",
134
134
  shake: "shake 0.5s ease-in-out",
135
135
  rotate: "rotate 0.5s ease-in-out",
136
+ "caret-blink": "caret-blink 1.25s ease-out infinite",
136
137
  },
137
138
  keyframes: {
138
139
  "accordion-down": {
@@ -152,6 +153,11 @@ module.exports = {
152
153
  "0%": { transform: "rotate(0deg)" },
153
154
  "100%": { transform: "rotate(360deg)" },
154
155
  },
156
+ // InputOTP sahte imleç animasyonu
157
+ "caret-blink": {
158
+ "0%,70%,100%": { opacity: "1" },
159
+ "20%,50%": { opacity: "0" },
160
+ },
155
161
  },
156
162
  boxShadow: {
157
163
  "3xl": "0 35px 60px -15px rgba(0, 0, 0, 0.3)",
package/src/use-paddle.ts DELETED
@@ -1,138 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { getPaddleInstance } from '@/lib/paddle';
3
- import type { Paddle } from '@paddle/paddle-js';
4
-
5
- interface UsePaddleReturn {
6
- paddle: Paddle | null;
7
- isLoading: boolean;
8
- error: string | null;
9
- openCheckout: (planId: string) => Promise<void>;
10
- }
11
-
12
- /**
13
- * Custom hook for Paddle integration
14
- * Handles Paddle initialization and checkout operations
15
- */
16
- export function usePaddle(): UsePaddleReturn {
17
- const [paddle, setPaddle] = useState<Paddle | null>(null);
18
- const [isLoading, setIsLoading] = useState(true);
19
- const [error, setError] = useState<string | null>(null);
20
-
21
- // Initialize Paddle on mount
22
- useEffect(() => {
23
- let mounted = true;
24
-
25
- const initPaddle = async () => {
26
- try {
27
- setIsLoading(true);
28
- setError(null);
29
-
30
- const paddleInstance = await getPaddleInstance();
31
-
32
- if (mounted) {
33
- if (paddleInstance) {
34
- setPaddle(paddleInstance);
35
- } else {
36
- setError('Failed to initialize Paddle');
37
- }
38
- }
39
- } catch (err) {
40
- if (mounted) {
41
- setError(err instanceof Error ? err.message : 'Unknown error');
42
- }
43
- } finally {
44
- if (mounted) {
45
- setIsLoading(false);
46
- }
47
- }
48
- };
49
-
50
- initPaddle();
51
-
52
- return () => {
53
- mounted = false;
54
- };
55
- }, []);
56
-
57
- /**
58
- * Open Paddle checkout for a specific plan
59
- */
60
- const openCheckout = async (planId: string) => {
61
- if (!paddle) {
62
- throw new Error('Paddle not initialized');
63
- }
64
-
65
- try {
66
- // Call our API to handle the checkout
67
- const response = await fetch('/api/paddle', {
68
- method: 'POST',
69
- headers: {
70
- 'Content-Type': 'application/json',
71
- },
72
- body: JSON.stringify({ plan: planId }),
73
- });
74
-
75
- if (!response.ok) {
76
- const errorText = await response.text();
77
- throw new Error(errorText || 'Failed to create checkout');
78
- }
79
-
80
- const result = await response.json();
81
-
82
- if (!result.success) {
83
- throw new Error(result.message || 'Checkout failed');
84
- }
85
-
86
- // For mock mode, we need to manually trigger checkout
87
- if (process.env.NEXT_PUBLIC_USE_MOCK_PADDLE === 'true') {
88
- console.log('🧪 [Hook] Mock mode detected, triggering checkout');
89
-
90
- // Import paddle config to get price ID
91
- const { PADDLE_CONFIG } = await import('@/lib/paddle');
92
- const priceId = PADDLE_CONFIG.priceIds[planId as keyof typeof PADDLE_CONFIG.priceIds];
93
-
94
- console.log('🧪 [Hook] Using price ID:', priceId, 'for plan:', planId);
95
- console.log('🧪 [Hook] Paddle instance:', paddle);
96
-
97
- // Trigger mock checkout
98
- const checkoutData = {
99
- items: [{ priceId, quantity: 1 }],
100
- settings: {
101
- displayMode: 'overlay' as const,
102
- theme: 'light' as const,
103
- locale: 'en' as const,
104
- successUrl: `${window.location.origin}/dashboard?success=true`
105
- },
106
- customData: {
107
- planId,
108
- userId: 'test_user',
109
- timestamp: new Date().toISOString()
110
- }
111
- };
112
-
113
- console.log('🧪 [Hook] Opening checkout with data:', checkoutData);
114
-
115
- try {
116
- paddle.Checkout.open(checkoutData);
117
- console.log('🧪 [Hook] Checkout.open called successfully');
118
- } catch (checkoutError) {
119
- console.error('🧪 [Hook] Checkout.open error:', checkoutError);
120
- }
121
- }
122
-
123
- // Checkout is opened inline by Paddle
124
- console.log('✅ Paddle checkout opened for plan:', planId);
125
-
126
- } catch (error) {
127
- console.error('❌ Checkout error:', error);
128
- throw error;
129
- }
130
- };
131
-
132
- return {
133
- paddle,
134
- isLoading,
135
- error,
136
- openCheckout,
137
- };
138
- }