@moontra/moonui-pro 2.20.1 → 2.20.3
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 +11 -5
- package/plugin/index.d.ts +86 -0
- package/plugin/index.js +308 -0
- package/scripts/postbuild.js +27 -0
- package/scripts/postinstall.js +176 -23
- 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/advanced-chart/index.tsx +0 -1242
- package/src/components/advanced-forms/index.tsx +0 -426
- package/src/components/animated-button/index.tsx +0 -385
- package/src/components/calendar/event-dialog.tsx +0 -372
- package/src/components/calendar/index.tsx +0 -1073
- package/src/components/calendar-pro/index.tsx +0 -1697
- package/src/components/color-picker/index.tsx +0 -432
- package/src/components/credit-card-input/index.tsx +0 -406
- package/src/components/dashboard/dashboard-grid.tsx +0 -462
- package/src/components/dashboard/demo.tsx +0 -425
- package/src/components/dashboard/index.tsx +0 -1046
- package/src/components/dashboard/time-range-picker.tsx +0 -336
- package/src/components/dashboard/types.ts +0 -222
- package/src/components/dashboard/widgets/activity-feed.tsx +0 -344
- package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
- package/src/components/dashboard/widgets/metric-card.tsx +0 -343
- package/src/components/data-table/data-table-bulk-actions.tsx +0 -204
- package/src/components/data-table/data-table-column-toggle.tsx +0 -169
- package/src/components/data-table/data-table-export.ts +0 -156
- package/src/components/data-table/data-table-filter-drawer.tsx +0 -448
- package/src/components/data-table/data-table.test.tsx +0 -187
- package/src/components/data-table/index.tsx +0 -845
- package/src/components/draggable-list/index.tsx +0 -100
- 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/error-boundary/index.tsx +0 -109
- package/src/components/file-upload/file-upload.test.tsx +0 -243
- package/src/components/file-upload/index.tsx +0 -1660
- package/src/components/floating-action-button/index.tsx +0 -206
- package/src/components/form-wizard/form-wizard-context.tsx +0 -307
- package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
- package/src/components/form-wizard/form-wizard-progress.tsx +0 -298
- package/src/components/form-wizard/form-wizard-step.tsx +0 -111
- package/src/components/form-wizard/index.tsx +0 -102
- package/src/components/form-wizard/types.ts +0 -76
- package/src/components/gesture-drawer/index.tsx +0 -551
- package/src/components/github-stars/github-api.ts +0 -426
- package/src/components/github-stars/hooks.ts +0 -516
- package/src/components/github-stars/index.tsx +0 -375
- package/src/components/github-stars/types.ts +0 -148
- package/src/components/github-stars/variants.tsx +0 -513
- package/src/components/health-check/index.tsx +0 -439
- package/src/components/hover-card-3d/index.tsx +0 -530
- package/src/components/index.ts +0 -128
- package/src/components/internal/index.ts +0 -78
- package/src/components/kanban/add-card-modal.tsx +0 -502
- package/src/components/kanban/card-detail-modal.tsx +0 -761
- package/src/components/kanban/index.ts +0 -13
- package/src/components/kanban/kanban.tsx +0 -1684
- package/src/components/kanban/types.ts +0 -168
- package/src/components/lazy-component/index.tsx +0 -823
- package/src/components/license-error/index.tsx +0 -29
- package/src/components/magnetic-button/index.tsx +0 -167
- package/src/components/memory-efficient-data/index.tsx +0 -1016
- package/src/components/moonui-quiz-form/index.tsx +0 -817
- package/src/components/optimized-image/index.tsx +0 -425
- package/src/components/performance-debugger/index.tsx +0 -589
- package/src/components/performance-monitor/index.tsx +0 -794
- package/src/components/phone-number-input/index.tsx +0 -338
- package/src/components/pinch-zoom/index.tsx +0 -566
- package/src/components/quiz-form/index.tsx +0 -479
- package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
- package/src/components/rich-text-editor/index.tsx +0 -2324
- package/src/components/rich-text-editor/slash-commands-extension.ts +0 -220
- package/src/components/rich-text-editor/slash-commands.css +0 -35
- package/src/components/rich-text-editor/table-styles.css +0 -65
- package/src/components/sidebar/index.tsx +0 -865
- package/src/components/spotlight-card/index.tsx +0 -191
- package/src/components/swipeable-card/index.tsx +0 -100
- package/src/components/timeline/index.tsx +0 -1148
- package/src/components/ui/accordion.tsx +0 -73
- package/src/components/ui/alert-dialog.tsx +0 -141
- package/src/components/ui/alert.tsx +0 -141
- package/src/components/ui/aspect-ratio.tsx +0 -245
- package/src/components/ui/avatar.tsx +0 -153
- package/src/components/ui/badge.tsx +0 -228
- package/src/components/ui/breadcrumb.tsx +0 -214
- package/src/components/ui/button.tsx +0 -222
- package/src/components/ui/calendar.tsx +0 -387
- package/src/components/ui/card.tsx +0 -214
- package/src/components/ui/checkbox.tsx +0 -259
- package/src/components/ui/collapsible.tsx +0 -135
- package/src/components/ui/color-picker.tsx +0 -97
- package/src/components/ui/command.tsx +0 -225
- package/src/components/ui/dialog.tsx +0 -334
- package/src/components/ui/dropdown-menu.tsx +0 -218
- package/src/components/ui/gesture-drawer.tsx +0 -11
- package/src/components/ui/hover-card.tsx +0 -29
- package/src/components/ui/index.ts +0 -190
- package/src/components/ui/input.tsx +0 -222
- package/src/components/ui/label.tsx +0 -29
- package/src/components/ui/lightbox.tsx +0 -606
- package/src/components/ui/magnetic-button.tsx +0 -129
- package/src/components/ui/media-gallery.tsx +0 -612
- package/src/components/ui/pagination.tsx +0 -123
- package/src/components/ui/popover.tsx +0 -185
- package/src/components/ui/progress.tsx +0 -30
- package/src/components/ui/radio-group.tsx +0 -257
- package/src/components/ui/scroll-area.tsx +0 -47
- package/src/components/ui/select.tsx +0 -374
- package/src/components/ui/separator.tsx +0 -145
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/skeleton.tsx +0 -20
- package/src/components/ui/slider.tsx +0 -354
- package/src/components/ui/spotlight-card.tsx +0 -119
- package/src/components/ui/switch.tsx +0 -86
- package/src/components/ui/table.tsx +0 -329
- package/src/components/ui/tabs.tsx +0 -198
- package/src/components/ui/textarea.tsx +0 -28
- package/src/components/ui/toast.tsx +0 -317
- package/src/components/ui/toggle.tsx +0 -119
- package/src/components/ui/tooltip.tsx +0 -151
- package/src/components/virtual-list/index.tsx +0 -668
- package/src/hooks/use-chart.ts +0 -205
- package/src/hooks/use-data-table.ts +0 -182
- package/src/hooks/use-docs-pro-access.ts +0 -13
- package/src/hooks/use-license-check.ts +0 -65
- package/src/hooks/use-subscription.ts +0 -19
- package/src/hooks/use-toast.ts +0 -15
- package/src/index.ts +0 -14
- package/src/lib/ai-providers.ts +0 -377
- package/src/lib/component-metadata.ts +0 -18
- package/src/lib/micro-interactions.ts +0 -255
- package/src/lib/paddle.ts +0 -17
- package/src/lib/utils.ts +0 -6
- package/src/patterns/login-form/index.tsx +0 -276
- package/src/patterns/login-form/types.ts +0 -67
- package/src/setupTests.ts +0 -41
- package/src/styles/advanced-chart.css +0 -239
- package/src/styles/calendar.css +0 -35
- package/src/styles/design-system.css +0 -363
- package/src/styles/index.css +0 -85
- package/src/styles/tailwind.css +0 -7
- package/src/styles/tokens.css +0 -455
- package/src/types/moonui.d.ts +0 -22
- package/src/types/next-auth.d.ts +0 -21
- package/src/use-intersection-observer.tsx +0 -154
- package/src/use-local-storage.tsx +0 -71
- package/src/use-paddle.ts +0 -138
- package/src/use-performance-optimizer.ts +0 -389
- package/src/use-pro-access.ts +0 -141
- package/src/use-scroll-animation.ts +0 -219
- package/src/use-subscription.ts +0 -37
- package/src/use-toast.ts +0 -32
- package/src/utils/chart-helpers.ts +0 -357
- package/src/utils/cn.ts +0 -6
- package/src/utils/data-processing.ts +0 -151
- package/src/utils/license-validator.tsx +0 -183
|
@@ -1,566 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React, { useRef, useState, useCallback, useEffect } from "react"
|
|
4
|
-
import { motion, useMotionValue, useTransform, animate, AnimatePresence } from "framer-motion"
|
|
5
|
-
import { cn } from "../../lib/utils"
|
|
6
|
-
import { Card, CardContent } from "../ui/card"
|
|
7
|
-
import { Button } from "../ui/button"
|
|
8
|
-
import { Lock, Sparkles, ZoomIn, ZoomOut, Maximize2, RotateCw, Minimize2 } from "lucide-react"
|
|
9
|
-
import { useSubscription } from "../../hooks/use-subscription"
|
|
10
|
-
|
|
11
|
-
interface PinchZoomProps {
|
|
12
|
-
children: React.ReactNode
|
|
13
|
-
minZoom?: number
|
|
14
|
-
maxZoom?: number
|
|
15
|
-
initialZoom?: number
|
|
16
|
-
className?: string
|
|
17
|
-
contentClassName?: string
|
|
18
|
-
onZoomChange?: (zoom: number) => void
|
|
19
|
-
showControls?: boolean
|
|
20
|
-
showIndicator?: boolean
|
|
21
|
-
doubleTapBehavior?: 'zoom' | 'reset'
|
|
22
|
-
wheelScaling?: boolean
|
|
23
|
-
dragEnabled?: boolean
|
|
24
|
-
zoomStep?: number
|
|
25
|
-
smoothZoom?: boolean
|
|
26
|
-
boundaryConstraints?: boolean
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const PinchZoomInternal = React.forwardRef<HTMLDivElement, PinchZoomProps>(
|
|
30
|
-
({
|
|
31
|
-
children,
|
|
32
|
-
minZoom = 0.5,
|
|
33
|
-
maxZoom = 3,
|
|
34
|
-
initialZoom = 1,
|
|
35
|
-
className,
|
|
36
|
-
contentClassName,
|
|
37
|
-
onZoomChange,
|
|
38
|
-
showControls = true,
|
|
39
|
-
showIndicator = true,
|
|
40
|
-
doubleTapBehavior = 'zoom',
|
|
41
|
-
wheelScaling = true,
|
|
42
|
-
dragEnabled = true,
|
|
43
|
-
zoomStep = 0.5,
|
|
44
|
-
smoothZoom = true,
|
|
45
|
-
boundaryConstraints = true,
|
|
46
|
-
...props
|
|
47
|
-
}, ref) => {
|
|
48
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
49
|
-
const contentRef = useRef<HTMLDivElement>(null)
|
|
50
|
-
const [isDragging, setIsDragging] = useState(false)
|
|
51
|
-
const [showZoomIndicator, setShowZoomIndicator] = useState(false)
|
|
52
|
-
const [lastTap, setLastTap] = useState(0)
|
|
53
|
-
const [isHovered, setIsHovered] = useState(false)
|
|
54
|
-
const [isFullscreen, setIsFullscreen] = useState(false)
|
|
55
|
-
|
|
56
|
-
const scale = useMotionValue(initialZoom)
|
|
57
|
-
const x = useMotionValue(0)
|
|
58
|
-
const y = useMotionValue(0)
|
|
59
|
-
|
|
60
|
-
const constrainedScale = useTransform(scale, (value) => {
|
|
61
|
-
return Math.min(Math.max(value, minZoom), maxZoom)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
// Zoom yüzdesi için transform
|
|
65
|
-
const zoomPercentage = useTransform(scale, (value) => {
|
|
66
|
-
return Math.round(value * 100)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
// Sınırları hesapla
|
|
70
|
-
const calculateConstraints = useCallback(() => {
|
|
71
|
-
if (!containerRef.current || !contentRef.current || !boundaryConstraints) return { left: 0, right: 0, top: 0, bottom: 0 }
|
|
72
|
-
|
|
73
|
-
const containerRect = containerRef.current.getBoundingClientRect()
|
|
74
|
-
const currentScale = scale.get()
|
|
75
|
-
|
|
76
|
-
const scaledWidth = containerRect.width * currentScale
|
|
77
|
-
const scaledHeight = containerRect.height * currentScale
|
|
78
|
-
|
|
79
|
-
const maxX = Math.max(0, (scaledWidth - containerRect.width) / 2)
|
|
80
|
-
const maxY = Math.max(0, (scaledHeight - containerRect.height) / 2)
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
left: -maxX,
|
|
84
|
-
right: maxX,
|
|
85
|
-
top: -maxY,
|
|
86
|
-
bottom: maxY
|
|
87
|
-
}
|
|
88
|
-
}, [scale, boundaryConstraints])
|
|
89
|
-
|
|
90
|
-
// Browser zoom'unu engelle (sadece Ctrl/Cmd basılıyken)
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (!containerRef.current || !wheelScaling) return
|
|
93
|
-
|
|
94
|
-
const container = containerRef.current
|
|
95
|
-
const handleWheelPrevent = (e: WheelEvent) => {
|
|
96
|
-
// Sadece component üzerinde ve Ctrl/Cmd basılıyken preventDefault yap
|
|
97
|
-
if (isHovered && wheelScaling && (e.ctrlKey || e.metaKey)) {
|
|
98
|
-
e.preventDefault()
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Passive: false ile event listener ekle
|
|
103
|
-
container.addEventListener('wheel', handleWheelPrevent, { passive: false })
|
|
104
|
-
|
|
105
|
-
return () => {
|
|
106
|
-
container.removeEventListener('wheel', handleWheelPrevent)
|
|
107
|
-
}
|
|
108
|
-
}, [isHovered, wheelScaling])
|
|
109
|
-
|
|
110
|
-
const handleWheel = useCallback((event: React.WheelEvent) => {
|
|
111
|
-
if (!wheelScaling) return
|
|
112
|
-
|
|
113
|
-
// Ctrl (veya Mac'te Cmd) basılı değilse zoom yapma
|
|
114
|
-
if (!event.ctrlKey && !event.metaKey) return
|
|
115
|
-
|
|
116
|
-
event.preventDefault()
|
|
117
|
-
|
|
118
|
-
const delta = -event.deltaY / 1000
|
|
119
|
-
const currentScale = scale.get()
|
|
120
|
-
const newScale = Math.min(Math.max(currentScale + delta, minZoom), maxZoom)
|
|
121
|
-
|
|
122
|
-
// Zoom noktasını hesapla (mouse pozisyonuna göre)
|
|
123
|
-
if (containerRef.current) {
|
|
124
|
-
const rect = containerRef.current.getBoundingClientRect()
|
|
125
|
-
const centerX = (event.clientX - rect.left) / rect.width - 0.5
|
|
126
|
-
const centerY = (event.clientY - rect.top) / rect.height - 0.5
|
|
127
|
-
|
|
128
|
-
const scaleDiff = newScale - currentScale
|
|
129
|
-
const currentX = x.get()
|
|
130
|
-
const currentY = y.get()
|
|
131
|
-
|
|
132
|
-
// Zoom noktasına göre pozisyonu ayarla
|
|
133
|
-
const newX = currentX - centerX * rect.width * scaleDiff
|
|
134
|
-
const newY = currentY - centerY * rect.height * scaleDiff
|
|
135
|
-
|
|
136
|
-
if (smoothZoom) {
|
|
137
|
-
animate(x, newX, { duration: 0.2 })
|
|
138
|
-
animate(y, newY, { duration: 0.2 })
|
|
139
|
-
} else {
|
|
140
|
-
x.set(newX)
|
|
141
|
-
y.set(newY)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (smoothZoom) {
|
|
146
|
-
animate(scale, newScale, { duration: 0.2 })
|
|
147
|
-
} else {
|
|
148
|
-
scale.set(newScale)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
onZoomChange?.(newScale)
|
|
152
|
-
|
|
153
|
-
// Zoom göstergesini göster
|
|
154
|
-
if (showIndicator) {
|
|
155
|
-
setShowZoomIndicator(true)
|
|
156
|
-
setTimeout(() => setShowZoomIndicator(false), 1500)
|
|
157
|
-
}
|
|
158
|
-
}, [scale, x, y, minZoom, maxZoom, onZoomChange, wheelScaling, smoothZoom, showIndicator])
|
|
159
|
-
|
|
160
|
-
const handleDoubleClick = useCallback((event: React.MouseEvent) => {
|
|
161
|
-
event.preventDefault()
|
|
162
|
-
|
|
163
|
-
const currentScale = scale.get()
|
|
164
|
-
let newScale: number
|
|
165
|
-
|
|
166
|
-
if (doubleTapBehavior === 'reset') {
|
|
167
|
-
newScale = initialZoom
|
|
168
|
-
} else {
|
|
169
|
-
// Zoom to point functionality
|
|
170
|
-
newScale = currentScale > 1 ? 1 : 2
|
|
171
|
-
|
|
172
|
-
if (newScale > 1 && containerRef.current) {
|
|
173
|
-
const rect = containerRef.current.getBoundingClientRect()
|
|
174
|
-
const centerX = (event.clientX - rect.left) / rect.width - 0.5
|
|
175
|
-
const centerY = (event.clientY - rect.top) / rect.height - 0.5
|
|
176
|
-
|
|
177
|
-
// Tıklanan noktaya zoom yap
|
|
178
|
-
const targetX = -centerX * rect.width * (newScale - 1)
|
|
179
|
-
const targetY = -centerY * rect.height * (newScale - 1)
|
|
180
|
-
|
|
181
|
-
animate(x, targetX, {
|
|
182
|
-
duration: 0.3,
|
|
183
|
-
type: "spring",
|
|
184
|
-
stiffness: 300
|
|
185
|
-
})
|
|
186
|
-
animate(y, targetY, {
|
|
187
|
-
duration: 0.3,
|
|
188
|
-
type: "spring",
|
|
189
|
-
stiffness: 300
|
|
190
|
-
})
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
animate(scale, Math.min(Math.max(newScale, minZoom), maxZoom), {
|
|
195
|
-
duration: 0.3,
|
|
196
|
-
type: "spring",
|
|
197
|
-
stiffness: 300
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
if (newScale === 1 || doubleTapBehavior === 'reset') {
|
|
201
|
-
animate(x, 0, { duration: 0.3 })
|
|
202
|
-
animate(y, 0, { duration: 0.3 })
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
onZoomChange?.(newScale)
|
|
206
|
-
|
|
207
|
-
if (showIndicator) {
|
|
208
|
-
setShowZoomIndicator(true)
|
|
209
|
-
setTimeout(() => setShowZoomIndicator(false), 1500)
|
|
210
|
-
}
|
|
211
|
-
}, [scale, x, y, minZoom, maxZoom, initialZoom, onZoomChange, doubleTapBehavior, showIndicator])
|
|
212
|
-
|
|
213
|
-
// Touch gestures için çift dokunma
|
|
214
|
-
const handleTouchEnd = useCallback((event: React.TouchEvent) => {
|
|
215
|
-
const now = Date.now()
|
|
216
|
-
const timeSinceLastTap = now - lastTap
|
|
217
|
-
|
|
218
|
-
if (timeSinceLastTap < 300 && timeSinceLastTap > 0) {
|
|
219
|
-
// Çift dokunma algılandı
|
|
220
|
-
const touch = event.changedTouches[0]
|
|
221
|
-
const fakeMouseEvent = {
|
|
222
|
-
clientX: touch.clientX,
|
|
223
|
-
clientY: touch.clientY,
|
|
224
|
-
preventDefault: () => {}
|
|
225
|
-
} as React.MouseEvent
|
|
226
|
-
|
|
227
|
-
handleDoubleClick(fakeMouseEvent)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
setLastTap(now)
|
|
231
|
-
}, [lastTap, handleDoubleClick])
|
|
232
|
-
|
|
233
|
-
const resetZoom = useCallback(() => {
|
|
234
|
-
animate(scale, initialZoom, {
|
|
235
|
-
duration: 0.3,
|
|
236
|
-
type: "spring",
|
|
237
|
-
stiffness: 300
|
|
238
|
-
})
|
|
239
|
-
animate(x, 0, {
|
|
240
|
-
duration: 0.3,
|
|
241
|
-
type: "spring",
|
|
242
|
-
stiffness: 300
|
|
243
|
-
})
|
|
244
|
-
animate(y, 0, {
|
|
245
|
-
duration: 0.3,
|
|
246
|
-
type: "spring",
|
|
247
|
-
stiffness: 300
|
|
248
|
-
})
|
|
249
|
-
onZoomChange?.(initialZoom)
|
|
250
|
-
}, [scale, x, y, initialZoom, onZoomChange])
|
|
251
|
-
|
|
252
|
-
// Programmatik zoom kontrolleri
|
|
253
|
-
const zoomIn = useCallback(() => {
|
|
254
|
-
const currentScale = scale.get()
|
|
255
|
-
const newScale = Math.min(currentScale + zoomStep, maxZoom)
|
|
256
|
-
|
|
257
|
-
animate(scale, newScale, {
|
|
258
|
-
duration: 0.3,
|
|
259
|
-
type: "spring",
|
|
260
|
-
stiffness: 300
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
onZoomChange?.(newScale)
|
|
264
|
-
|
|
265
|
-
if (showIndicator) {
|
|
266
|
-
setShowZoomIndicator(true)
|
|
267
|
-
setTimeout(() => setShowZoomIndicator(false), 1500)
|
|
268
|
-
}
|
|
269
|
-
}, [scale, maxZoom, zoomStep, onZoomChange, showIndicator])
|
|
270
|
-
|
|
271
|
-
const zoomOut = useCallback(() => {
|
|
272
|
-
const currentScale = scale.get()
|
|
273
|
-
const newScale = Math.max(currentScale - zoomStep, minZoom)
|
|
274
|
-
|
|
275
|
-
animate(scale, newScale, {
|
|
276
|
-
duration: 0.3,
|
|
277
|
-
type: "spring",
|
|
278
|
-
stiffness: 300
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
// Zoom out yapınca sınırları kontrol et
|
|
282
|
-
if (newScale < scale.get()) {
|
|
283
|
-
const constraints = calculateConstraints()
|
|
284
|
-
const currentX = x.get()
|
|
285
|
-
const currentY = y.get()
|
|
286
|
-
|
|
287
|
-
if (currentX < constraints.left || currentX > constraints.right) {
|
|
288
|
-
animate(x, 0, { duration: 0.3 })
|
|
289
|
-
}
|
|
290
|
-
if (currentY < constraints.top || currentY > constraints.bottom) {
|
|
291
|
-
animate(y, 0, { duration: 0.3 })
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
onZoomChange?.(newScale)
|
|
296
|
-
|
|
297
|
-
if (showIndicator) {
|
|
298
|
-
setShowZoomIndicator(true)
|
|
299
|
-
setTimeout(() => setShowZoomIndicator(false), 1500)
|
|
300
|
-
}
|
|
301
|
-
}, [scale, minZoom, zoomStep, x, y, calculateConstraints, onZoomChange, showIndicator])
|
|
302
|
-
|
|
303
|
-
const fitToScreen = useCallback(() => {
|
|
304
|
-
resetZoom()
|
|
305
|
-
}, [resetZoom])
|
|
306
|
-
|
|
307
|
-
// Fullscreen toggle
|
|
308
|
-
const toggleFullscreen = useCallback(() => {
|
|
309
|
-
if (!containerRef.current) return
|
|
310
|
-
|
|
311
|
-
if (!document.fullscreenElement) {
|
|
312
|
-
// Fullscreen'e gir
|
|
313
|
-
const element = containerRef.current.parentElement || containerRef.current
|
|
314
|
-
element.requestFullscreen().then(() => {
|
|
315
|
-
setIsFullscreen(true)
|
|
316
|
-
}).catch((err) => {
|
|
317
|
-
console.error("Fullscreen error:", err)
|
|
318
|
-
})
|
|
319
|
-
} else {
|
|
320
|
-
// Fullscreen'den çık
|
|
321
|
-
document.exitFullscreen().then(() => {
|
|
322
|
-
setIsFullscreen(false)
|
|
323
|
-
}).catch((err) => {
|
|
324
|
-
console.error("Exit fullscreen error:", err)
|
|
325
|
-
})
|
|
326
|
-
}
|
|
327
|
-
}, [])
|
|
328
|
-
|
|
329
|
-
// Fullscreen değişikliklerini dinle
|
|
330
|
-
useEffect(() => {
|
|
331
|
-
const handleFullscreenChange = () => {
|
|
332
|
-
setIsFullscreen(!!document.fullscreenElement)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
336
|
-
document.addEventListener('webkitfullscreenchange', handleFullscreenChange)
|
|
337
|
-
document.addEventListener('mozfullscreenchange', handleFullscreenChange)
|
|
338
|
-
document.addEventListener('MSFullscreenChange', handleFullscreenChange)
|
|
339
|
-
|
|
340
|
-
return () => {
|
|
341
|
-
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
|
342
|
-
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
|
|
343
|
-
document.removeEventListener('mozfullscreenchange', handleFullscreenChange)
|
|
344
|
-
document.removeEventListener('MSFullscreenChange', handleFullscreenChange)
|
|
345
|
-
}
|
|
346
|
-
}, [])
|
|
347
|
-
|
|
348
|
-
// Keyboard zoom shortcuts (Ctrl/Cmd + ve -)
|
|
349
|
-
useEffect(() => {
|
|
350
|
-
if (!isHovered || !wheelScaling) return
|
|
351
|
-
|
|
352
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
353
|
-
if (!e.ctrlKey && !e.metaKey) return
|
|
354
|
-
|
|
355
|
-
if (e.key === '+' || e.key === '=') {
|
|
356
|
-
e.preventDefault()
|
|
357
|
-
zoomIn()
|
|
358
|
-
} else if (e.key === '-' || e.key === '_') {
|
|
359
|
-
e.preventDefault()
|
|
360
|
-
zoomOut()
|
|
361
|
-
} else if (e.key === '0') {
|
|
362
|
-
e.preventDefault()
|
|
363
|
-
resetZoom()
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
document.addEventListener('keydown', handleKeyDown)
|
|
368
|
-
|
|
369
|
-
return () => {
|
|
370
|
-
document.removeEventListener('keydown', handleKeyDown)
|
|
371
|
-
}
|
|
372
|
-
}, [isHovered, wheelScaling, zoomIn, zoomOut, resetZoom])
|
|
373
|
-
|
|
374
|
-
// Drag sınırlarını güncelle
|
|
375
|
-
useEffect(() => {
|
|
376
|
-
const unsubscribe = scale.onChange(() => {
|
|
377
|
-
const constraints = calculateConstraints()
|
|
378
|
-
const currentX = x.get()
|
|
379
|
-
const currentY = y.get()
|
|
380
|
-
|
|
381
|
-
// Sınırlar dışında mı kontrol et
|
|
382
|
-
if (boundaryConstraints) {
|
|
383
|
-
if (currentX < constraints.left || currentX > constraints.right) {
|
|
384
|
-
animate(x, Math.max(constraints.left, Math.min(constraints.right, currentX)), { duration: 0.2 })
|
|
385
|
-
}
|
|
386
|
-
if (currentY < constraints.top || currentY > constraints.bottom) {
|
|
387
|
-
animate(y, Math.max(constraints.top, Math.min(constraints.bottom, currentY)), { duration: 0.2 })
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
return unsubscribe
|
|
393
|
-
}, [scale, x, y, calculateConstraints, boundaryConstraints])
|
|
394
|
-
|
|
395
|
-
return (
|
|
396
|
-
<div
|
|
397
|
-
ref={ref}
|
|
398
|
-
className={cn(
|
|
399
|
-
"relative overflow-hidden touch-none select-none",
|
|
400
|
-
dragEnabled && "cursor-grab active:cursor-grabbing",
|
|
401
|
-
isFullscreen && "fixed inset-0 z-50 bg-background",
|
|
402
|
-
className
|
|
403
|
-
)}
|
|
404
|
-
onWheel={handleWheel}
|
|
405
|
-
onDoubleClick={handleDoubleClick}
|
|
406
|
-
onTouchEnd={handleTouchEnd}
|
|
407
|
-
onMouseEnter={() => setIsHovered(true)}
|
|
408
|
-
onMouseLeave={() => setIsHovered(false)}
|
|
409
|
-
{...props}
|
|
410
|
-
>
|
|
411
|
-
<div ref={containerRef} className="w-full h-full">
|
|
412
|
-
<motion.div
|
|
413
|
-
ref={contentRef}
|
|
414
|
-
drag={dragEnabled}
|
|
415
|
-
dragElastic={0.1}
|
|
416
|
-
dragMomentum={false}
|
|
417
|
-
dragConstraints={boundaryConstraints ? calculateConstraints() : false}
|
|
418
|
-
onDragStart={() => setIsDragging(true)}
|
|
419
|
-
onDragEnd={() => setIsDragging(false)}
|
|
420
|
-
style={{
|
|
421
|
-
scale: constrainedScale,
|
|
422
|
-
x,
|
|
423
|
-
y,
|
|
424
|
-
}}
|
|
425
|
-
className={cn(
|
|
426
|
-
"w-full h-full flex items-center justify-center",
|
|
427
|
-
isDragging && "cursor-grabbing",
|
|
428
|
-
contentClassName
|
|
429
|
-
)}
|
|
430
|
-
>
|
|
431
|
-
{children}
|
|
432
|
-
</motion.div>
|
|
433
|
-
</div>
|
|
434
|
-
|
|
435
|
-
{/* Zoom Indicator */}
|
|
436
|
-
<AnimatePresence>
|
|
437
|
-
{showIndicator && showZoomIndicator && (
|
|
438
|
-
<motion.div
|
|
439
|
-
initial={{ opacity: 0, scale: 0.8 }}
|
|
440
|
-
animate={{ opacity: 1, scale: 1 }}
|
|
441
|
-
exit={{ opacity: 0, scale: 0.8 }}
|
|
442
|
-
transition={{ duration: 0.2 }}
|
|
443
|
-
className="absolute top-4 left-1/2 -translate-x-1/2 px-3 py-1.5 bg-black/80 dark:bg-white/80 text-white dark:text-black rounded-full text-sm font-medium pointer-events-none"
|
|
444
|
-
>
|
|
445
|
-
{zoomPercentage.get()}%
|
|
446
|
-
</motion.div>
|
|
447
|
-
)}
|
|
448
|
-
</AnimatePresence>
|
|
449
|
-
|
|
450
|
-
{/* Control Buttons */}
|
|
451
|
-
{showControls && (
|
|
452
|
-
<div className="absolute bottom-4 right-4 flex gap-2">
|
|
453
|
-
<button
|
|
454
|
-
onClick={zoomOut}
|
|
455
|
-
disabled={scale.get() <= minZoom}
|
|
456
|
-
className={cn(
|
|
457
|
-
"p-2 bg-background/80 backdrop-blur-sm border rounded-md",
|
|
458
|
-
"hover:bg-background/90 transition-all duration-200",
|
|
459
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
460
|
-
"shadow-sm hover:shadow-md"
|
|
461
|
-
)}
|
|
462
|
-
aria-label="Zoom out"
|
|
463
|
-
>
|
|
464
|
-
<ZoomOut className="w-4 h-4" />
|
|
465
|
-
</button>
|
|
466
|
-
<button
|
|
467
|
-
onClick={zoomIn}
|
|
468
|
-
disabled={scale.get() >= maxZoom}
|
|
469
|
-
className={cn(
|
|
470
|
-
"p-2 bg-background/80 backdrop-blur-sm border rounded-md",
|
|
471
|
-
"hover:bg-background/90 transition-all duration-200",
|
|
472
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
473
|
-
"shadow-sm hover:shadow-md"
|
|
474
|
-
)}
|
|
475
|
-
aria-label="Zoom in"
|
|
476
|
-
>
|
|
477
|
-
<ZoomIn className="w-4 h-4" />
|
|
478
|
-
</button>
|
|
479
|
-
<button
|
|
480
|
-
onClick={resetZoom}
|
|
481
|
-
className={cn(
|
|
482
|
-
"p-2 bg-background/80 backdrop-blur-sm border rounded-md",
|
|
483
|
-
"hover:bg-background/90 transition-all duration-200",
|
|
484
|
-
"shadow-sm hover:shadow-md"
|
|
485
|
-
)}
|
|
486
|
-
aria-label="Reset zoom"
|
|
487
|
-
>
|
|
488
|
-
<RotateCw className="w-4 h-4" />
|
|
489
|
-
</button>
|
|
490
|
-
<button
|
|
491
|
-
onClick={toggleFullscreen}
|
|
492
|
-
className={cn(
|
|
493
|
-
"p-2 bg-background/80 backdrop-blur-sm border rounded-md",
|
|
494
|
-
"hover:bg-background/90 transition-all duration-200",
|
|
495
|
-
"shadow-sm hover:shadow-md"
|
|
496
|
-
)}
|
|
497
|
-
aria-label={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
|
|
498
|
-
>
|
|
499
|
-
{isFullscreen ? (
|
|
500
|
-
<Minimize2 className="w-4 h-4" />
|
|
501
|
-
) : (
|
|
502
|
-
<Maximize2 className="w-4 h-4" />
|
|
503
|
-
)}
|
|
504
|
-
</button>
|
|
505
|
-
</div>
|
|
506
|
-
)}
|
|
507
|
-
|
|
508
|
-
{/* Zoom Level Info */}
|
|
509
|
-
{showControls && (
|
|
510
|
-
<div className="absolute bottom-4 left-4 px-3 py-1.5 bg-background/80 backdrop-blur-sm border rounded-md text-sm">
|
|
511
|
-
<div className="flex items-center gap-2">
|
|
512
|
-
<span>{Math.round(scale.get() * 100)}%</span>
|
|
513
|
-
{wheelScaling && !showZoomIndicator && (
|
|
514
|
-
<span className="text-xs text-muted-foreground">
|
|
515
|
-
(Ctrl+Scroll to zoom)
|
|
516
|
-
</span>
|
|
517
|
-
)}
|
|
518
|
-
</div>
|
|
519
|
-
</div>
|
|
520
|
-
)}
|
|
521
|
-
</div>
|
|
522
|
-
)
|
|
523
|
-
}
|
|
524
|
-
)
|
|
525
|
-
|
|
526
|
-
PinchZoomInternal.displayName = "PinchZoomInternal"
|
|
527
|
-
|
|
528
|
-
export const PinchZoom = React.forwardRef<HTMLDivElement, PinchZoomProps>(
|
|
529
|
-
({ className, ...props }, ref) => {
|
|
530
|
-
// Check if we're in docs mode or have pro access
|
|
531
|
-
const { hasProAccess, isLoading } = useSubscription()
|
|
532
|
-
|
|
533
|
-
// In docs mode, always show the component
|
|
534
|
-
|
|
535
|
-
// If not in docs mode and no pro access, show upgrade prompt
|
|
536
|
-
if (!isLoading && !hasProAccess) {
|
|
537
|
-
return (
|
|
538
|
-
<Card className={cn("w-fit", className)}>
|
|
539
|
-
<CardContent className="py-6 text-center">
|
|
540
|
-
<div className="space-y-4">
|
|
541
|
-
<div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
|
|
542
|
-
<Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
543
|
-
</div>
|
|
544
|
-
<div>
|
|
545
|
-
<h3 className="font-semibold text-sm mb-2">Pro Feature</h3>
|
|
546
|
-
<p className="text-muted-foreground text-xs mb-4">
|
|
547
|
-
Pinch Zoom is available exclusively to MoonUI Pro subscribers.
|
|
548
|
-
</p>
|
|
549
|
-
<a href="/pricing">
|
|
550
|
-
<Button size="sm">
|
|
551
|
-
<Sparkles className="mr-2 h-4 w-4" />
|
|
552
|
-
Upgrade to Pro
|
|
553
|
-
</Button>
|
|
554
|
-
</a>
|
|
555
|
-
</div>
|
|
556
|
-
</div>
|
|
557
|
-
</CardContent>
|
|
558
|
-
</Card>
|
|
559
|
-
)
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
return <PinchZoomInternal className={className} ref={ref} {...props} />
|
|
563
|
-
}
|
|
564
|
-
)
|
|
565
|
-
|
|
566
|
-
PinchZoom.displayName = "PinchZoom"
|